bool C4Network2ClientList::BroadcastMsgToClients(const C4NetIOPacket &rPkt) { // Send a msg to all clients, including clients that are not connected to // this computer (will get forwarded by host). C4PacketFwd Fwd; Fwd.SetListType(true); // lock pIO->BeginBroadcast(false); // select connections for broadcast for (C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext()) if (!pClient->isHost()) if (pClient->isConnected()) { pClient->getMsgConn()->SetBroadcastTarget(true); Fwd.AddClient(pClient->getID()); } // broadcast bool fSuccess = pIO->Broadcast(rPkt); // unlock pIO->EndBroadcast(); // clients: send forward request to host if (!fHost) { Fwd.SetData(rPkt); fSuccess &= SendMsgToHost(MkC4NetIOPacket(PID_FwdReq, Fwd)); } return fSuccess; }
void C4Network2IO::SendConnPackets() { CStdLock ConnListLock(&ConnListCSec); // exlusive conn? if (fExclusiveConn) // find a live connection for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) if (pConn->isAccepted() || (!pConn->isClosed() && pConn->isConnSent())) // do not sent additional conn packets - no other connection should succeed return; // sent pending welcome packet(s) for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) if (pConn->isOpen() && !pConn->isConnSent()) { // make packet CStdLock LCCoreLock(&LCCoreCSec); C4NetIOPacket Pkt = MkC4NetIOPacket(PID_Conn, C4PacketConn(LCCore, pConn->getID(), pConn->getPassword())); LCCoreLock.Clear(); // send if (!pConn->Send(Pkt)) pConn->Close(); else { // set flag pConn->SetConnSent(); // only one conn packet at a time if (fExclusiveConn) return; } } }
bool C4Network2Res::FinishDerive() // by main thread { // All changes have been made. Register this resource with a new ID. // security if (!isAnonymous()) return false; CStdLock FileLock(&FileCSec); // Save back data int32_t iDerID = Core.getDerID(); char szName[_MAX_PATH+1]; SCopy(Core.getFileName(), szName, _MAX_PATH); char szFileC[_MAX_PATH+1]; SCopy(szFile, szFileC, _MAX_PATH); // Set by file if (!SetByFile(szFileC, fTempFile, getType(), pParent->nextResID(), szName)) return false; // create standalone if (!GetStandalone(NULL, 0, true)) return false; // Set ID Core.SetDerived(iDerID); // announce derive pParent->getIOClass()->BroadcastMsg(MkC4NetIOPacket(PID_NetResDerive, Core)); // derivation is dirty bussines fDirty = true; // ok return true; }
void C4Network2IO::HandleFwdReq(const C4PacketFwd &rFwd, C4Network2IOConnection *pBy) { CStdLock ConnListLock(&ConnListCSec); // init packet C4PacketFwd nFwd; nFwd.SetListType(false); // find all clients the message should be forwarded to int iClientID; C4Network2IOConnection *pConn; for (pConn = pConnList; pConn; pConn = pConn->pNext) if (pConn->isAccepted()) if ((iClientID = pConn->getClientID()) >= 0) if (iClientID != pBy->getClientID()) if (rFwd.DoFwdTo(iClientID) && !nFwd.DoFwdTo(iClientID)) nFwd.AddClient(iClientID); // check count (hardcoded: broadcast for > 2 clients) if (nFwd.getClientCnt() <= 2) { C4NetIOPacket Pkt(rFwd.getData(), C4NetIO::addr_t()); for (int i = 0; i < nFwd.getClientCnt(); i++) if ((pConn = GetMsgConnection(nFwd.getClient(i)))) { pConn->Send(Pkt); pConn->DelRef(); } } else { // Temporarily unlock connection list for getting broadcast lock // (might lead to deathlocks otherwise, as the lock is often taken // in the opposite order) ConnListLock.Clear(); BeginBroadcast(); nFwd.SetData(rFwd.getData()); // add all clients CStdLock ConnListLock(&ConnListCSec); for (int i = 0; i < nFwd.getClientCnt(); i++) if ((pConn = GetMsgConnection(nFwd.getClient(i)))) { pConn->SetBroadcastTarget(true); pConn->DelRef(); } // broadcast Broadcast(MkC4NetIOPacket(PID_Fwd, nFwd)); EndBroadcast(); } // doing a callback here; don't lock! ConnListLock.Clear(); // forward to self? if (rFwd.DoFwdTo(LCCore.getID())) { C4NetIOPacket Packet(rFwd.getData(), pBy->getPeerAddr()); HandlePacket(Packet, pBy, true); } }
void C4Network2Client::SendAddresses(C4Network2IOConnection *pConn) { // send all addresses for (int32_t i = 0; i < iAddrCnt; i++) { C4NetIOPacket Pkt = MkC4NetIOPacket(PID_Addr, C4PacketAddr(getID(), Addr[i])); if (pConn) pConn->Send(Pkt); else pParent->BroadcastMsgToConnClients(Pkt); } }
void C4Network2Client::CloseConns(const char *szMsg) { C4PacketConnRe Pkt(false, false, szMsg); C4Network2IOConnection *pConn; while (pConn = pMsgConn) { // send packet, close if (pConn->isOpen()) { pConn->Send(MkC4NetIOPacket(PID_ConnRe, Pkt)); pConn->Close(); } // remove RemoveConn(pConn); } }
bool C4Network2IO::Ping() { bool fSuccess = true; // ping all connections for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) if (pConn->isOpen()) { C4PacketPing Ping(pConn->getInPacketCounter(), pConn->getOutPacketCounter()); fSuccess &= pConn->Send(MkC4NetIOPacket(PID_Ping, Ping)); pConn->OnPing(); } return fSuccess; }
bool C4Network2ClientList::SendMsgToClient(int32_t iClient, C4NetIOPacket RREF rPkt) { // find client C4Network2Client *pClient = GetClientByID(iClient); if (!pClient) return false; // connected? send directly if (pClient->isConnected()) return pClient->SendMsg(rPkt); // forward C4PacketFwd Fwd; Fwd.SetListType(false); Fwd.AddClient(iClient); Fwd.SetData(rPkt); return SendMsgToHost(MkC4NetIOPacket(PID_FwdReq, Fwd)); }
bool C4Network2ResList::SendDiscover(C4Network2IOConnection *pTo) // by both { // make packet C4PacketResDiscover Pkt; // add special retrieves CStdShareLock ResListLock(&ResListCSec); for (C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext) if (!pRes->isRemoved()) if (pRes->isLoading()) Pkt.AddDisID(pRes->getResID()); ResListLock.Clear(); // empty? if (!Pkt.getDisIDCnt()) return false; // broadcast? if (!pTo) { // save time iLastDiscover = time(NULL); // send return pIO->BroadcastMsg(MkC4NetIOPacket(PID_NetResDis, Pkt)); } else return pTo->Send(MkC4NetIOPacket(PID_NetResDis, Pkt)); }
void C4GameOptionsList::OptionScenarioParameter::DoDropdownSelChange(int32_t idNewSelection) { // runtime change needs to be synchronized if (!pForDlg->IsPreGame()) { // change possible? if (!::Control.isCtrlHost()) return; // Then initiate an update of the parameters on all clients C4GameLobby::C4PacketSetScenarioParameter pck(ParameterDef->GetID(), idNewSelection); ::Network.Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_SetScenarioParameter, pck)); } // also process on host (and standalone pre-game) pForDlg->GetParameters()->SetValue(ParameterDef->GetID(), idNewSelection, false); if (pForDlg->IsPreGame()) Update(); }
bool C4Network2Players::JoinLocalPlayer(const char *szLocalPlayerFilename, bool initial) { // ignore in replay // shouldn't even come here though assert(!Game.C4S.Head.Replay); if (Game.C4S.Head.Replay) return false; // if observing: don't try if (Game.Clients.getLocal()->isObserver()) return false; // network only assert(::Network.isEnabled()); // create join info packet C4ClientPlayerInfos JoinInfo(szLocalPlayerFilename, !initial); // league game: get authentication for players if (Game.Parameters.isLeague()) for (int i = 0; i < JoinInfo.GetPlayerCount(); i++) { C4PlayerInfo *pInfo = JoinInfo.GetPlayerInfo(i); if (!::Network.LeaguePlrAuth(pInfo)) { JoinInfo.RemoveIndexedInfo(i); i--; } } // host or client? if (::Network.isHost()) { // error joining players? Zero players is OK for initial packet; marks host as observer if (!initial && !JoinInfo.GetPlayerCount()) return false; // handle it as a direct request HandlePlayerInfoUpdRequest(&JoinInfo, true); } else { // clients request initial joins at host only // create player info for local player joins C4PacketPlayerInfoUpdRequest JoinRequest(JoinInfo); // any players to join? Zero players is OK for initial packet; marks client as observer // it's also necessary to send the empty player info packet, so the host will answer // with infos of all other clients if (!initial && !JoinRequest.Info.GetPlayerCount()) return false; ::Network.Clients.SendMsgToHost(MkC4NetIOPacket(PID_PlayerInfoUpdReq, JoinRequest)); // request activation if (JoinRequest.Info.GetPlayerCount() && !Game.Clients.getLocal()->isActivated()) ::Network.RequestActivate(); } // done, success return true; }
bool C4Network2Res::SendStatus(C4Network2IOConnection *pTo) { // pack status C4NetIOPacket Pkt = MkC4NetIOPacket(PID_NetResStat, C4PacketResStatus(Core.getID(), Chunks)); // to one client? if (pTo) return pTo->Send(Pkt); else { // reset dirty flag fDirty = false; // broadcast status assert(pParent && pParent->getIOClass()); return pParent->getIOClass()->BroadcastMsg(Pkt); } }
bool C4Network2Client::AddAddr(const C4Network2Address &addr, bool fAnnounce) { // checks if (iAddrCnt + 1 >= C4ClientMaxAddr) return false; if (hasAddr(addr)) return true; // add Addr[iAddrCnt] = addr; AddrAttempts[iAddrCnt] = 0; iAddrCnt++; // attempt to use this one if (!iNextConnAttempt) iNextConnAttempt = time(NULL); // announce if (fAnnounce) if (!pParent->BroadcastMsgToConnClients(MkC4NetIOPacket(PID_Addr, C4PacketAddr(getID(), addr)))) return false; // done return true; }
void C4Network2Players::RequestPlayerInfoUpdate(const class C4ClientPlayerInfos &rRequest) { // network only assert(::Network.isEnabled()); // host or client? if (::Network.isHost()) { // host processes directly HandlePlayerInfoUpdRequest(&rRequest, true); } else { // client sends request to host C4PacketPlayerInfoUpdRequest UpdateRequest(rRequest); ::Network.Clients.SendMsgToHost(MkC4NetIOPacket(PID_PlayerInfoUpdReq, UpdateRequest)); } }
void C4Network2Client::SendAddresses(C4Network2IOConnection *pConn) { // send all addresses for (int32_t i = 0; i < iAddrCnt; i++) { if (Addr[i].getAddr().GetScopeId() && (!pConn || pConn->getPeerAddr().GetScopeId() != Addr[i].getAddr().GetScopeId())) continue; C4Network2Address addr(Addr[i]); addr.getAddr().SetScopeId(0); C4NetIOPacket Pkt = MkC4NetIOPacket(PID_Addr, C4PacketAddr(getID(), addr)); if (pConn) pConn->Send(Pkt); else pParent->BroadcastMsgToConnClients(Pkt); } }
bool C4Network2Res::SendChunk(uint32_t iChunk, int32_t iToClient) { assert(pParent && pParent->getIOClass()); if (!szStandalone[0] || iChunk >= Core.getChunkCnt()) return false; // find connection for given client (one of the rare uses of the data connection) C4Network2IOConnection *pConn = pParent->getIOClass()->GetDataConnection(iToClient); if (!pConn) return false; // save last request time iLastReqTime = time(NULL); // create packet CStdLock FileLock(&FileCSec); C4Network2ResChunk ResChunk; ResChunk.Set(this, iChunk); // send bool fSuccess = pConn->Send(MkC4NetIOPacket(PID_NetResData, ResChunk)); pConn->DelRef(); return fSuccess; }
bool C4Network2Client::DoTCPSimultaneousOpen(class C4Network2IO *pIO, const C4Network2Address &addr) { if (!pIO->getNetIO(P_TCP)) return false; // Did we already bind a socket? if (TcpSimOpenSocket) { LogSilentF("Network: connecting client %s on %s with TCP simultaneous open...", getName(), addr.getAddr().ToString().getData()); return pIO->ConnectWithSocket(addr.getAddr(), addr.getProtocol(), pClient->getCore(), std::move(TcpSimOpenSocket)); } else { // No - bind one, inform peer, and schedule a connection attempt. auto NetIOTCP = dynamic_cast<C4NetIOTCP*>(pIO->getNetIO(P_TCP)); auto bindAddr = pParent->GetLocal()->IPv6AddrFromPuncher; // We need to know an address that works. if (bindAddr.IsNull()) return false; bindAddr.SetPort(0); TcpSimOpenSocket = NetIOTCP->Bind(bindAddr); auto boundAddr = TcpSimOpenSocket->GetAddress(); LogSilentF("Network: %s TCP simultaneous open request for client %s from %s...", addr.isIPNull() ? "initiating" : "responding to", getName(), boundAddr.ToString().getData()); // Send address we bound to to the client. if (!SendMsg(MkC4NetIOPacket(PID_TCPSimOpen, C4PacketTCPSimOpen(pParent->GetLocal()->getID(), C4Network2Address(boundAddr, P_TCP))))) return false; if (!addr.isIPNull()) { // We need to delay the connection attempt a bit. Unfortunately, // waiting for the next tick would usually take way too much time. // Instead, we block the main thread for a very short time and hope // that noone notices... int ping = getMsgConn()->getLag(); std::this_thread::sleep_for(std::chrono::milliseconds(std::min(ping / 2, 10))); DoTCPSimultaneousOpen(pIO, addr); } return true; } }
bool C4Network2Res::StartLoad(int32_t iFromClient, const C4Network2ResChunkData &Available) { assert(pParent && pParent->getIOClass()); // all slots used? ignore if (iLoadCnt + 1 >= C4NetResMaxLoad) return true; // is there already a load by this client? ignore for (C4Network2ResLoad *pPos = pLoads; pPos; pPos = pPos->Next()) if (pPos->getByClient() == iFromClient) return true; // find chunk to retrieve int32_t iLoads[C4NetResMaxLoad]; int32_t i = 0; for (C4Network2ResLoad *pLoad = pLoads; pLoad; pLoad = pLoad->Next()) iLoads[i++] = pLoad->getChunk(); int32_t iRetrieveChunk = Chunks.GetChunkToRetrieve(Available, i, iLoads); // nothing? ignore if (iRetrieveChunk < 0 || (uint32_t)iRetrieveChunk >= Core.getChunkCnt()) return true; // search message connection for client C4Network2IOConnection *pConn = pParent->getIOClass()->GetMsgConnection(iFromClient); if (!pConn) return false; // send request if (!pConn->Send(MkC4NetIOPacket(PID_NetResReq, C4PacketResRequest(Core.getID(), iRetrieveChunk)))) { pConn->DelRef(); return false; } pConn->DelRef(); #ifdef C4NET2RES_DEBUG_LOG // log Application.InteractiveThread.ThreadLogS("Network: Res: requesting chunk %d of %d:%s (%s) from client %d", iRetrieveChunk, Core.getID(), Core.getFileName(), szFile, iFromClient); #endif // create load class C4Network2ResLoad *pnLoad = new C4Network2ResLoad(iRetrieveChunk, iFromClient); // add to list pnLoad->pNext = pLoads; pLoads = pnLoad; iLoadCnt++; // ok return true; }
void C4Network2IO::HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn) { // security if (!pConn) return; #define GETPKT(type, name) \ assert(pPacket); const type &name = \ static_cast<const type &>(*pPacket); switch (cStatus) { case PID_Conn: // connection request { if (!pConn->isOpen()) break; // get packet GETPKT(C4PacketConn, rPkt) // set connection ID pConn->SetRemoteID(rPkt.getConnID()); // check auto-accept if (doAutoAccept(rPkt.getCCore(), *pConn)) { // send answer back C4PacketConnRe pcr(true, false, "auto accept"); if (!pConn->Send(MkC4NetIOPacket(PID_ConnRe, pcr))) pConn->Close(); // accept pConn->SetStatus(CS_HalfAccepted); pConn->SetCCore(rPkt.getCCore()); pConn->SetAutoAccepted(); } // note that this packet will get processed by C4Network2, too (main thread) } break; case PID_ConnRe: // connection request reply { if (!pConn->isOpen()) break; // conn not sent? That's fishy. // FIXME: Note this happens if the peer has exclusive connection mode on. if (!pConn->isConnSent()) { pConn->Close(); break; } // get packet GETPKT(C4PacketConnRe, rPkt) // auto accept connection if (rPkt.isOK()) { if (pConn->isHalfAccepted() && pConn->isAutoAccepted()) pConn->SetAccepted(); } } break; case PID_Ping: { if (!pConn->isOpen()) break; GETPKT(C4PacketPing, rPkt) // pong C4PacketPing PktPong = rPkt; pConn->Send(MkC4NetIOPacket(PID_Pong, PktPong)); // remove received packets from log pConn->ClearPacketLog(rPkt.getPacketCounter()); } break; case PID_Pong: { if (!pConn->isOpen()) break; GETPKT(C4PacketPing, rPkt); // save pConn->SetPingTime(rPkt.getTravelTime()); } break; case PID_FwdReq: { GETPKT(C4PacketFwd, rPkt); HandleFwdReq(rPkt, pConn); } break; case PID_Fwd: { GETPKT(C4PacketFwd, rPkt); // only received accidently? if (!rPkt.DoFwdTo(LCCore.getID())) break; // handle C4NetIOPacket Packet(rPkt.getData(), pConn->getPeerAddr()); HandlePacket(Packet, pConn, true); } break; case PID_PostMortem: { GETPKT(C4PacketPostMortem, rPkt); // Get connection C4Network2IOConnection *pConn = GetConnectionByID(rPkt.getConnID()); if (!pConn) return; // Handle all packets uint32_t iCounter; for (iCounter = pConn->getInPacketCounter(); ; iCounter++) { // Get packet const C4NetIOPacket *pPkt = rPkt.getPacket(iCounter); if (!pPkt) break; // Handle it HandlePacket(*pPkt, pConn, true); } // Log if (iCounter > pConn->getInPacketCounter()) Application.InteractiveThread.ThreadLogS("Network: Recovered %d packets", iCounter - pConn->getInPacketCounter()); // Remove the connection from our list if (!pConn->isClosed()) pConn->Close(); RemoveConnection(pConn); } break; } #undef GETPKT }