about summary refs log tree commit diff
path: root/src/engine/shared/network_server.cpp
diff options
context:
space:
mode:
authoreeeee <eeeee@qwe123.info>2018-08-18 17:33:58 -0700
committerLearath <learath2@gmail.com>2018-08-20 15:33:34 +0300
commit2cd0223a8df54ae1c8715915a331dfc56a5a29d3 (patch)
treea5c3789c4aa2c93255d824b5eb343172d3642eac /src/engine/shared/network_server.cpp
parent3f3abebebf20e8901376b929e2afcc6bdb404a3b (diff)
downloadzcatch-2cd0223a8df54ae1c8715915a331dfc56a5a29d3.tar.gz
zcatch-2cd0223a8df54ae1c8715915a331dfc56a5a29d3.zip
Port antispoof
Diffstat (limited to 'src/engine/shared/network_server.cpp')
-rw-r--r--src/engine/shared/network_server.cpp568
1 files changed, 482 insertions, 86 deletions
diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp
index e97a8eba..cc97981c 100644
--- a/src/engine/shared/network_server.cpp
+++ b/src/engine/shared/network_server.cpp
@@ -4,9 +4,48 @@
 
 #include <engine/console.h>
 
+#include "config.h"
 #include "netban.h"
 #include "network.h"
-
+#include <engine/external/md5/md5.h>
+#include <engine/message.h>
+#include <engine/shared/protocol.h>
+
+const int DummyMapCrc = 0x6c760ac4;
+unsigned char g_aDummyMapData[] = {
+	0x44, 0x41, 0x54, 0x41, 0x04, 0x00, 0x00, 0x00, 0x22, 0x01, 0x00, 0x00,
+	0x14, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+	0x01, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+	0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
+	0x1c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
+	0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0a, 0xc3, 0x52,
+	0xff, 0x7f, 0x00, 0x00, 0x20, 0x5c, 0xf6, 0x01, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x05, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+	0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x20, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00,
+	0x7c, 0x00, 0x00, 0x00, 0x78, 0x9c, 0x63, 0x64, 0x60, 0x60, 0x60, 0x44,
+	0xc2, 0x00, 0x00, 0x38, 0x00, 0x05
+};
+
+
+static SECURITY_TOKEN ToSecurityToken(const unsigned char *pData)
+{
+	return (int)pData[0] | (pData[1] << 8) | (pData[2] << 16) | (pData[3] << 24);
+}
 
 bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int MaxClientsPerIP, int Flags)
 {
@@ -29,12 +68,22 @@ bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int Ma
 
 	m_MaxClientsPerIP = MaxClientsPerIP;
 
+	m_NumConAttempts = 0;
+	m_TimeNumConAttempts = time_get();
+
+	m_VConnHighLoad = false;
+	m_VConnNum = 0;
+	m_VConnFirst = 0;
+
+	secure_random_fill(m_SecurityTokenSeed, sizeof(m_SecurityTokenSeed));
+
 	for(int i = 0; i < NET_MAX_CLIENTS; i++)
 		m_aSlots[i].m_Connection.Init(m_Socket, true);
 
 	return true;
 }
 
+/*
 int CNetServer::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
 {
 	m_pfnNewClient = pfnNewClient;
@@ -42,6 +91,16 @@ int CNetServer::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT p
 	m_UserPtr = pUser;
 	return 0;
 }
+*/
+
+int CNetServer::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_NEWCLIENT_NOAUTH pfnNewClientNoAuth, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
+{
+	m_pfnNewClient = pfnNewClient;
+	m_pfnNewClientNoAuth = pfnNewClientNoAuth;
+	m_pfnDelClient = pfnDelClient;
+	m_UserPtr = pUser;
+	return 0;
+}
 
 int CNetServer::Close()
 {
@@ -69,22 +128,407 @@ int CNetServer::Drop(int ClientID, const char *pReason)
 
 int CNetServer::Update()
 {
-	int64 Now = time_get();
 	for(int i = 0; i < MaxClients(); i++)
 	{
 		m_aSlots[i].m_Connection.Update();
 		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR)
 		{
-			if(Now - m_aSlots[i].m_Connection.ConnectTime() < time_freq()/2 && NetBan())
-				NetBan()->BanAddr(ClientAddr(i), 60, "Stressing network");
-			else
-				Drop(i, m_aSlots[i].m_Connection.ErrorString());
+			Drop(i, m_aSlots[i].m_Connection.ErrorString());
 		}
 	}
 
 	return 0;
 }
 
+SECURITY_TOKEN CNetServer::GetToken(const NETADDR &Addr)
+{
+	md5_state_t md5;
+	md5_byte_t digest[16];
+	SECURITY_TOKEN SecurityToken;
+	md5_init(&md5);
+
+	md5_append(&md5, (unsigned char*)m_SecurityTokenSeed, sizeof(m_SecurityTokenSeed));
+	md5_append(&md5, (unsigned char*)&Addr, sizeof(Addr));
+
+	md5_finish(&md5, digest);
+	SecurityToken = ToSecurityToken(digest);
+
+	if (SecurityToken == NET_SECURITY_TOKEN_UNKNOWN ||
+		SecurityToken == NET_SECURITY_TOKEN_UNSUPPORTED)
+			SecurityToken = 1;
+
+	return SecurityToken;
+}
+
+void CNetServer::SendControl(NETADDR &Addr, int ControlMsg, const void *pExtra, int ExtraSize, SECURITY_TOKEN SecurityToken)
+{
+	CNetBase::SendControlMsg(m_Socket, &Addr, 0, ControlMsg, pExtra, ExtraSize, SecurityToken);
+}
+
+int CNetServer::NumClientsWithAddr(NETADDR Addr)
+{
+	NETADDR ThisAddr = Addr, OtherAddr;
+
+	int FoundAddr = 0;
+	ThisAddr.port = 0;
+
+	for(int i = 0; i < MaxClients(); ++i)
+	{
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE ||
+			m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR)
+			continue;
+
+		OtherAddr = *m_aSlots[i].m_Connection.PeerAddress();
+		OtherAddr.port = 0;
+		if(!net_addr_comp(&ThisAddr, &OtherAddr))
+			FoundAddr++;
+	}
+
+	return FoundAddr;
+}
+
+bool CNetServer::Connlimit(NETADDR Addr)
+{
+	int64 Now = time_get();
+	int Oldest = 0;
+
+	for(int i = 0; i < NET_CONNLIMIT_IPS; ++i)
+	{
+		if(!net_addr_comp(&m_aSpamConns[i].m_Addr, &Addr))
+		{
+			if(m_aSpamConns[i].m_Time > Now - time_freq() * g_Config.m_SvConnlimitTime)
+			{
+				if(m_aSpamConns[i].m_Conns >= g_Config.m_SvConnlimit)
+					return true;
+			}
+			else
+			{
+				m_aSpamConns[i].m_Time = Now;
+				m_aSpamConns[i].m_Conns = 0;
+			}
+			m_aSpamConns[i].m_Conns++;
+			return false;
+		}
+
+		if(m_aSpamConns[i].m_Time < m_aSpamConns[Oldest].m_Time)
+			Oldest = i;
+	}
+
+	m_aSpamConns[Oldest].m_Addr = Addr;
+	m_aSpamConns[Oldest].m_Time = Now;
+	m_aSpamConns[Oldest].m_Conns = 1;
+	return false;
+}
+
+int CNetServer::TryAcceptClient(NETADDR &Addr, SECURITY_TOKEN SecurityToken, bool VanillaAuth)
+{
+	if (Connlimit(Addr))
+	{
+		const char Msg[] = "Too many connections in a short time";
+		CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, Msg, sizeof(Msg), SecurityToken);
+		return -1; // failed to add client
+	}
+
+	// check for sv_max_clients_per_ip
+	if (NumClientsWithAddr(Addr) + 1 > m_MaxClientsPerIP)
+	{
+		char aBuf[128];
+		str_format(aBuf, sizeof(aBuf), "Only %d players with the same IP are allowed", m_MaxClientsPerIP);
+		CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf) + 1, SecurityToken);
+		return -1; // failed to add client
+	}
+
+	int Slot = -1;
+	for(int i = 0; i < MaxClients(); i++)
+	{
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
+		{
+			Slot = i;
+			break;
+		}
+	}
+
+	if (Slot == -1)
+	{
+		const char FullMsg[] = "This server is full";
+		CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, FullMsg, sizeof(FullMsg), SecurityToken);
+
+		return -1; // failed to add client
+	}
+
+	// init connection slot
+	m_aSlots[Slot].m_Connection.DirectInit(Addr, SecurityToken);
+
+	if (VanillaAuth)
+	{
+		// client sequence is unknown if the auth was done
+		// connection-less
+		m_aSlots[Slot].m_Connection.SetUnknownSeq();
+		// correct sequence
+		m_aSlots[Slot].m_Connection.SetSequence(6);
+	}
+
+	if (g_Config.m_Debug)
+	{
+		char aAddrStr[NETADDR_MAXSTRSIZE];
+		net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr), true);
+		dbg_msg("security", "client accepted %s", aAddrStr);
+	}
+
+
+	if (VanillaAuth)
+		m_pfnNewClientNoAuth(Slot, m_UserPtr);
+	else
+		m_pfnNewClient(Slot, m_UserPtr);
+
+	return Slot; // done
+}
+
+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);
+}
+
+// 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);
+			}
+		}
+	}
+}
+
+void CNetServer::OnTokenCtrlMsg(NETADDR &Addr, int ControlMsg, const CNetPacketConstruct &Packet)
+{
+	if (ClientExists(Addr))
+		return; // silently ignore
+
+
+	if (ControlMsg == NET_CTRLMSG_CONNECT)
+	{
+		bool SupportsToken = Packet.m_DataSize >=
+								(int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(SECURITY_TOKEN)) &&
+								!mem_comp(&Packet.m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC));
+
+		if (SupportsToken)
+		{
+			// response connection request with token
+			SECURITY_TOKEN Token = GetToken(Addr);
+			SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC), Token);
+		}
+	}
+	else if (ControlMsg == NET_CTRLMSG_ACCEPT && Packet.m_DataSize == 1 + sizeof(SECURITY_TOKEN))
+	{
+		SECURITY_TOKEN Token = ToSecurityToken(&Packet.m_aChunkData[1]);
+		if (Token == GetToken(Addr))
+		{
+			// correct token
+			// try to accept client
+			if (g_Config.m_Debug)
+				dbg_msg("security", "new client (0.6.5 token)");
+			TryAcceptClient(Addr, Token);
+		}
+		else
+		{
+			// invalid token
+			if (g_Config.m_Debug)
+				dbg_msg("security", "invalid token");
+		}
+	}
+}
+
+int CNetServer::GetClientSlot(const NETADDR &Addr)
+{
+	int Slot = -1;
+
+	for(int i = 0; i < MaxClients(); i++)
+	{
+		if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE &&
+			m_aSlots[i].m_Connection.State() != NET_CONNSTATE_ERROR &&
+			net_addr_comp(m_aSlots[i].m_Connection.PeerAddress(), &Addr) == 0)
+
+		{
+			Slot = i;
+		}
+	}
+
+	return Slot;
+}
+
 /*
 	TODO: chopp up this function into smaller working parts
 */
@@ -105,17 +549,17 @@ int CNetServer::Recv(CNetChunk *pChunk)
 		if(Bytes <= 0)
 			break;
 
-		if(CNetBase::UnpackPacket(m_RecvUnpacker.m_aBuffer, Bytes, &m_RecvUnpacker.m_Data) == 0)
+		// check if we just should drop the packet
+		char aBuf[128];
+		if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf)))
 		{
-			// check if we just should drop the packet
-			char aBuf[128];
-			if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf)))
-			{
-				// banned, reply with a message
-				CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf)+1);
-				continue;
-			}
+			// banned, reply with a message
+			CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf)+1, NET_SECURITY_TOKEN_UNSUPPORTED);
+			continue;
+		}
 
+		if(CNetBase::UnpackPacket(m_RecvUnpacker.m_aBuffer, Bytes, &m_RecvUnpacker.m_Data) == 0)
+		{
 			if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONNLESS)
 			{
 				pChunk->m_Flags = NETSENDFLAG_CONNLESS;
@@ -127,83 +571,35 @@ int CNetServer::Recv(CNetChunk *pChunk)
 			}
 			else
 			{
-				// TODO: check size here
-				if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT)
-				{
-					bool Found = false;
+				// drop invalid ctrl packets
+				if (m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL &&
+						m_RecvUnpacker.m_Data.m_DataSize == 0)
+					continue;
 
-					// check if we already got this client
-					for(int i = 0; i < MaxClients(); i++)
-					{
-						if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE &&
-							net_addr_comp(m_aSlots[i].m_Connection.PeerAddress(), &Addr) == 0)
-						{
-							Found = true; // silent ignore.. we got this client already
-							break;
-						}
-					}
+				// normal packet, find matching slot
+				int Slot = GetClientSlot(Addr);
 
-					// client that wants to connect
-					if(!Found)
-					{
+				if (Slot != -1)
+				{
+					// found
 
-						// only allow a specific number of players with the same ip
-						NETADDR ThisAddr = Addr, OtherAddr;
-						int FoundAddr = 1;
-						ThisAddr.port = 0;
-						for(int i = 0; i < MaxClients(); ++i)
-						{
-							if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
-								continue;
-
-							OtherAddr = *m_aSlots[i].m_Connection.PeerAddress();
-							OtherAddr.port = 0;
-							if(!net_addr_comp(&ThisAddr, &OtherAddr))
-							{
-								if(FoundAddr++ >= m_MaxClientsPerIP)
-								{
-									char aBuf[128];
-									str_format(aBuf, sizeof(aBuf), "Only %d players with the same IP are allowed", m_MaxClientsPerIP);
-									CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, sizeof(aBuf));
-									return 0;
-								}
-							}
-						}
-
-						for(int i = 0; i < MaxClients(); i++)
-						{
-							if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
-							{
-								Found = true;
-								m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr);
-								if(m_pfnNewClient)
-									m_pfnNewClient(i, m_UserPtr);
-
-								break;
-							}
-						}
-
-						if(!Found)
-						{
-							const char FullMsg[] = "This server is full";
-							CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, FullMsg, sizeof(FullMsg));
-						}
+					if(m_aSlots[Slot].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr))
+					{
+						if(m_RecvUnpacker.m_Data.m_DataSize)
+							m_RecvUnpacker.Start(&Addr, &m_aSlots[Slot].m_Connection, Slot);
 					}
 				}
 				else
 				{
-					// normal packet, find matching slot
-					for(int i = 0; i < MaxClients(); i++)
-					{
-						if(net_addr_comp(m_aSlots[i].m_Connection.PeerAddress(), &Addr) == 0)
-						{
-							if(m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr))
-							{
-								if(m_RecvUnpacker.m_Data.m_DataSize)
-									m_RecvUnpacker.Start(&Addr, &m_aSlots[i].m_Connection, i);
-							}
-						}
-					}
+					// not found, client that wants to connect
+
+					if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL &&
+						m_RecvUnpacker.m_Data.m_DataSize > 1)
+						// got control msg with extra size (should support token)
+						OnTokenCtrlMsg(Addr, m_RecvUnpacker.m_Data.m_aChunkData[0], m_RecvUnpacker.m_Data);
+					else
+						// got connection-less ctrl or sys msg
+						OnPreConnMsg(Addr, m_RecvUnpacker.m_Data);
 				}
 			}
 		}
@@ -240,7 +636,7 @@ int CNetServer::Send(CNetChunk *pChunk)
 		}
 		else
 		{
-			Drop(pChunk->m_ClientID, "Error sending data");
+			//Drop(pChunk->m_ClientID, "Error sending data");
 		}
 	}
 	return 0;