void CNetServer::SendMsgs(NETADDR &Addr, const CMsgPacker *Msgs[], int num) { CNetPacketConstruct m_Construct; mem_zero(&m_Construct, sizeof(m_Construct)); unsigned char *pChunkData = &m_Construct.m_aChunkData[m_Construct.m_DataSize]; for (int i = 0; i < num; i++) { const CMsgPacker *pMsg = Msgs[i]; CNetChunkHeader Header; Header.m_Flags = NET_CHUNKFLAG_VITAL; Header.m_Size = pMsg->Size(); Header.m_Sequence = i+1; pChunkData = Header.Pack(pChunkData); mem_copy(pChunkData, pMsg->Data(), pMsg->Size()); *((unsigned char*)pChunkData) <<= 1; *((unsigned char*)pChunkData) |= 1; pChunkData += pMsg->Size(); m_Construct.m_NumChunks++; } // m_Construct.m_DataSize = (int)(pChunkData-m_Construct.m_aChunkData); CNetBase::SendPacket(m_Socket, &Addr, &m_Construct, NET_SECURITY_TOKEN_UNSUPPORTED); }
int CNetConnection::QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence) { if (m_State == NET_CONNSTATE_OFFLINE || m_State == NET_CONNSTATE_ERROR) return -1; unsigned char *pChunkData; // check if we have space for it, if not, flush the connection if(m_Construct.m_DataSize + DataSize + NET_MAX_CHUNKHEADERSIZE > (int)sizeof(m_Construct.m_aChunkData) - (int)sizeof(SECURITY_TOKEN)) Flush(); // pack all the data CNetChunkHeader Header; Header.m_Flags = Flags; Header.m_Size = DataSize; Header.m_Sequence = Sequence; pChunkData = &m_Construct.m_aChunkData[m_Construct.m_DataSize]; pChunkData = Header.Pack(pChunkData); mem_copy(pChunkData, pData, DataSize); pChunkData += DataSize; // m_Construct.m_NumChunks++; m_Construct.m_DataSize = (int)(pChunkData-m_Construct.m_aChunkData); // set packet flags aswell if(Flags&NET_CHUNKFLAG_VITAL && !(Flags&NET_CHUNKFLAG_RESEND)) { // save packet if we need to resend CNetChunkResend *pResend = m_Buffer.Allocate(sizeof(CNetChunkResend)+DataSize); if(pResend) { pResend->m_Sequence = Sequence; pResend->m_Flags = Flags; pResend->m_DataSize = DataSize; pResend->m_pData = (unsigned char *)(pResend+1); pResend->m_FirstSendTime = time_get(); pResend->m_LastSendTime = pResend->m_FirstSendTime; mem_copy(pResend->m_pData, pData, DataSize); } else { // out of buffer, don't save the packet and hope nobody will ask for resend return -1; } } return 0; }
int CNetConnection::QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence) { unsigned char *pChunkData; // check if we have space for it, if not, flush the connection if(m_Construct.m_DataSize + DataSize + NET_MAX_CHUNKHEADERSIZE > (int)sizeof(m_Construct.m_aChunkData)) Flush(); // pack all the data CNetChunkHeader Header; Header.m_Flags = Flags; Header.m_Size = DataSize; Header.m_Sequence = Sequence; pChunkData = &m_Construct.m_aChunkData[m_Construct.m_DataSize]; pChunkData = Header.Pack(pChunkData); mem_copy(pChunkData, pData, DataSize); pChunkData += DataSize; // m_Construct.m_NumChunks++; m_Construct.m_DataSize = (int)(pChunkData-m_Construct.m_aChunkData); // set packet flags aswell if(Flags&NET_CHUNKFLAG_VITAL && !(Flags&NET_CHUNKFLAG_RESEND)) { // save packet if we need to resend CNetChunkResend *pResend = m_Buffer.Allocate(sizeof(CNetChunkResend)+DataSize); if(pResend) { pResend->m_Sequence = Sequence; pResend->m_Flags = Flags; pResend->m_DataSize = DataSize; pResend->m_pData = (unsigned char *)(pResend+1); pResend->m_FirstSendTime = time_get(); pResend->m_LastSendTime = pResend->m_FirstSendTime; mem_copy(pResend->m_pData, pData, DataSize); } else { // out of buffer Disconnect("too weak connection (out of buffer)"); return -1; } } return 0; }
// connection-less msg packet without token-support void CNetServer::OnPreConnMsg(NETADDR &Addr, CNetPacketConstruct &Packet) { bool IsCtrl = Packet.m_Flags&NET_PACKETFLAG_CONTROL; int CtrlMsg = m_RecvUnpacker.m_Data.m_aChunkData[0]; // log flooding //TODO: remove if (g_Config.m_Debug) { int64 Now = time_get(); if (Now - m_TimeNumConAttempts > time_freq()) // reset m_NumConAttempts = 0; m_NumConAttempts++; if (m_NumConAttempts > 100) { dbg_msg("security", "flooding detected"); m_TimeNumConAttempts = Now; m_NumConAttempts = 0; } } if (IsCtrl && CtrlMsg == NET_CTRLMSG_CONNECT) { if (g_Config.m_SvVanillaAntiSpoof && g_Config.m_Password[0] == '\0') { // detect flooding int64 Now = time_get(); if(Now <= m_VConnFirst + time_freq()) { m_VConnNum++; } else { m_VConnHighLoad = m_VConnNum > g_Config.m_SvVanConnPerSecond; m_VConnNum = 1; m_VConnFirst = Now; } bool Flooding = m_VConnNum > g_Config.m_SvVanConnPerSecond || m_VConnHighLoad; if (g_Config.m_Debug && Flooding) { dbg_msg("security", "vanilla connection flooding detected"); } // simulate accept SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, NULL, 0, NET_SECURITY_TOKEN_UNSUPPORTED); // Begin vanilla compatible token handshake // The idea is to pack a security token in the gametick // parameter of NETMSG_SNAPEMPTY. The Client then will // return the token/gametick in NETMSG_INPUT, allowing // us to validate the token. // https://github.com/eeeee/ddnet/commit/b8e40a244af4e242dc568aa34854c5754c75a39a // Before we can send NETMSG_SNAPEMPTY, the client needs // to load a map, otherwise it might crash. The map // should be as small as is possible and directly available // to the client. Therefore a dummy map is sent in the same // packet. To reduce the traffic we'll fallback to a default // map if there are too many connection attempts at once. // send mapchange + map data + con_ready + 3 x empty snap (with token) CMsgPacker MapChangeMsg(NETMSG_MAP_CHANGE); if (Flooding) { // Fallback to dm1 MapChangeMsg.AddString("dm1", 0); MapChangeMsg.AddInt(0xf2159e6e); MapChangeMsg.AddInt(5805); } else { // dummy map MapChangeMsg.AddString("dummy", 0); MapChangeMsg.AddInt(DummyMapCrc); MapChangeMsg.AddInt(sizeof(g_aDummyMapData)); } CMsgPacker MapDataMsg(NETMSG_MAP_DATA); if (Flooding) { // send empty map data to keep 0.6.4 support MapDataMsg.AddInt(1); // last chunk MapDataMsg.AddInt(0); // crc MapDataMsg.AddInt(0); // chunk index MapDataMsg.AddInt(0); // map size MapDataMsg.AddRaw(NULL, 0); // map data } else { // send dummy map data MapDataMsg.AddInt(1); // last chunk MapDataMsg.AddInt(DummyMapCrc); // crc MapDataMsg.AddInt(0); // chunk index MapDataMsg.AddInt(sizeof(g_aDummyMapData)); // map size MapDataMsg.AddRaw(g_aDummyMapData, sizeof(g_aDummyMapData)); // map data } CMsgPacker ConReadyMsg(NETMSG_CON_READY); CMsgPacker SnapEmptyMsg(NETMSG_SNAPEMPTY); SECURITY_TOKEN SecurityToken = GetVanillaToken(Addr); SnapEmptyMsg.AddInt((int)SecurityToken); SnapEmptyMsg.AddInt((int)SecurityToken + 1); // send all chunks/msgs in one packet const CMsgPacker *Msgs[] = {&MapChangeMsg, &MapDataMsg, &ConReadyMsg, &SnapEmptyMsg, &SnapEmptyMsg, &SnapEmptyMsg}; SendMsgs(Addr, Msgs, 6); } else { // accept client directly SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, NULL, 0, NET_SECURITY_TOKEN_UNSUPPORTED); TryAcceptClient(Addr, NET_SECURITY_TOKEN_UNSUPPORTED); } } else if(!IsCtrl && g_Config.m_SvVanillaAntiSpoof && g_Config.m_Password[0] == '\0') { CNetChunkHeader h; unsigned char *pData = Packet.m_aChunkData; pData = h.Unpack(pData); CUnpacker Unpacker; Unpacker.Reset(pData, h.m_Size); int Msg = Unpacker.GetInt() >> 1; if (Msg == NETMSG_INPUT) { SECURITY_TOKEN SecurityToken = Unpacker.GetInt(); if (SecurityToken == GetVanillaToken(Addr)) { if (g_Config.m_Debug) dbg_msg("security", "new client (vanilla handshake)"); // try to accept client skipping auth state TryAcceptClient(Addr, NET_SECURITY_TOKEN_UNSUPPORTED, true); } else if (g_Config.m_Debug) dbg_msg("security", "invalid token (vanilla handshake)"); } else { if (g_Config.m_Debug) { dbg_msg("security", "invalid preconn msg %d", Msg); } } }