about summary refs log tree commit diff
path: root/src/engine/shared/network_server.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/shared/network_server.cpp')
-rw-r--r--src/engine/shared/network_server.cpp413
1 files changed, 413 insertions, 0 deletions
diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp
new file mode 100644
index 00000000..32b08bf6
--- /dev/null
+++ b/src/engine/shared/network_server.cpp
@@ -0,0 +1,413 @@
+#include <base/system.h>
+#include "network.h"
+
+#define MACRO_LIST_LINK_FIRST(Object, First, Prev, Next) \
+	{ if(First) First->Prev = Object; \
+	Object->Prev = (struct CBan *)0; \
+	Object->Next = First; \
+	First = Object; }
+	
+#define MACRO_LIST_LINK_AFTER(Object, After, Prev, Next) \
+	{ Object->Prev = After; \
+	Object->Next = After->Next; \
+	After->Next = Object; \
+	if(Object->Next) \
+		Object->Next->Prev = Object; \
+	}
+
+#define MACRO_LIST_UNLINK(Object, First, Prev, Next) \
+	{ if(Object->Next) Object->Next->Prev = Object->Prev; \
+	if(Object->Prev) Object->Prev->Next = Object->Next; \
+	else First = Object->Next; \
+	Object->Next = 0; Object->Prev = 0; }
+	
+#define MACRO_LIST_FIND(Start, Next, Expression) \
+	{ while(Start && !(Expression)) Start = Start->Next; }
+
+bool CNetServer::Open(NETADDR BindAddr, int MaxClients, int Flags)
+{
+	// zero out the whole structure
+	mem_zero(this, sizeof(*this));
+	
+	// open socket
+	m_Socket = net_udp_create(BindAddr);
+	if(m_Socket == NETSOCKET_INVALID)
+		return false;
+	
+	// clamp clients
+	m_MaxClients = MaxClients;
+	if(m_MaxClients > NET_MAX_CLIENTS)
+		m_MaxClients = NET_MAX_CLIENTS;
+	if(m_MaxClients < 1)
+		m_MaxClients = 1;
+	
+	for(int i = 0; i < NET_MAX_CLIENTS; i++)
+		m_aSlots[i].m_Connection.Init(m_Socket);
+	
+	// setup all pointers for bans
+	for(int i = 1; i < NET_SERVER_MAXBANS-1; i++)
+	{
+		m_BanPool[i].m_pNext = &m_BanPool[i+1];
+		m_BanPool[i].m_pPrev = &m_BanPool[i-1];
+	}
+	
+	m_BanPool[0].m_pNext = &m_BanPool[1];
+	m_BanPool[NET_SERVER_MAXBANS-1].m_pPrev = &m_BanPool[NET_SERVER_MAXBANS-2];
+	m_BanPool_FirstFree = &m_BanPool[0];
+
+	return true;
+}
+
+int CNetServer::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
+{
+	m_pfnNewClient = pfnNewClient;
+	m_pfnDelClient = pfnDelClient;
+	m_UserPtr = pUser;
+	return 0;
+}
+
+int CNetServer::Close()
+{
+	// TODO: implement me
+	return 0;
+}
+
+int CNetServer::Drop(int ClientID, const char *pReason)
+{
+	// TODO: insert lots of checks here
+	NETADDR Addr = ClientAddr(ClientID);
+
+	dbg_msg("net_server", "client dropped. cid=%d ip=%d.%d.%d.%d reason=\"%s\"",
+		ClientID,
+		Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3],
+		pReason
+		);
+		
+	m_aSlots[ClientID].m_Connection.Disconnect(pReason);
+
+	if(m_pfnDelClient)
+		m_pfnDelClient(ClientID, m_UserPtr);
+		
+	return 0;
+}
+
+int CNetServer::BanGet(int Index, CBanInfo *pInfo)
+{
+	CBan *pBan;
+	for(pBan = m_BanPool_FirstUsed; pBan && Index; pBan = pBan->m_pNext, Index--)
+		{}
+		
+	if(!pBan)
+		return 0;
+	*pInfo = pBan->m_Info;
+	return 1;
+}
+
+int CNetServer::BanNum()
+{
+	int Count = 0;
+	CBan *pBan;
+	for(pBan = m_BanPool_FirstUsed; pBan; pBan = pBan->m_pNext)
+		Count++;
+	return Count;
+}
+
+void CNetServer::BanRemoveByObject(CBan *pBan)
+{
+	int IpHash = (pBan->m_Info.m_Addr.ip[0]+pBan->m_Info.m_Addr.ip[1]+pBan->m_Info.m_Addr.ip[2]+pBan->m_Info.m_Addr.ip[3])&0xff;
+	dbg_msg("netserver", "removing ban on %d.%d.%d.%d",
+		pBan->m_Info.m_Addr.ip[0], pBan->m_Info.m_Addr.ip[1], pBan->m_Info.m_Addr.ip[2], pBan->m_Info.m_Addr.ip[3]);
+	MACRO_LIST_UNLINK(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext);
+	MACRO_LIST_UNLINK(pBan, m_aBans[IpHash], m_pHashPrev, m_pHashNext);
+	MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext);
+}
+
+int CNetServer::BanRemove(NETADDR Addr)
+{
+	int IpHash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3])&0xff;
+	CBan *pBan = m_aBans[IpHash];
+	
+	MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0);
+	
+	if(pBan)
+	{
+		BanRemoveByObject(pBan);
+		return 0;
+	}
+	
+	return -1;
+}
+
+int CNetServer::BanAdd(NETADDR Addr, int Seconds)
+{
+	int IpHash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3])&0xff;
+	int Stamp = -1;
+	CBan *pBan;
+	
+	// remove the port
+	Addr.port = 0;
+	
+	if(Seconds)
+		Stamp = time_timestamp() + Seconds;
+		
+	// search to see if it already exists
+	pBan = m_aBans[IpHash];
+	MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0);
+	if(pBan)
+	{
+		// adjust the ban
+		pBan->m_Info.m_Expires = Stamp;
+		return 0;
+	}
+	
+	if(!m_BanPool_FirstFree)
+		return -1;
+
+	// fetch and clear the new ban
+	pBan = m_BanPool_FirstFree;
+	MACRO_LIST_UNLINK(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext);
+	
+	// setup the ban info
+	pBan->m_Info.m_Expires = Stamp;
+	pBan->m_Info.m_Addr = Addr;
+	
+	// add it to the ban hash
+	MACRO_LIST_LINK_FIRST(pBan, m_aBans[IpHash], m_pHashPrev, m_pHashNext);
+	
+	// insert it into the used list
+	{
+		if(m_BanPool_FirstUsed)
+		{
+			CBan *pInsertAfter = m_BanPool_FirstUsed;
+			MACRO_LIST_FIND(pInsertAfter, m_pNext, Stamp < pInsertAfter->m_Info.m_Expires);
+			
+			if(pInsertAfter)
+				pInsertAfter = pInsertAfter->m_pPrev;
+			else
+			{
+				// add to last
+				pInsertAfter = m_BanPool_FirstUsed;
+				while(pInsertAfter->m_pNext)
+					pInsertAfter = pInsertAfter->m_pNext;
+			}
+			
+			if(pInsertAfter)
+			{
+				MACRO_LIST_LINK_AFTER(pBan, pInsertAfter, m_pPrev, m_pNext);
+			}
+			else
+			{
+				MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext);
+			}
+		}
+		else
+		{
+			MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext);
+		}
+	}
+
+	// drop banned clients
+	{
+		char Buf[128];
+		NETADDR BanAddr;
+		
+		if(Seconds)
+			str_format(Buf, sizeof(Buf), "you have been banned for %d minutes", Seconds/60);
+		else
+			str_format(Buf, sizeof(Buf), "you have been banned for life");
+		
+		for(int i = 0; i < MaxClients(); i++)
+		{
+			BanAddr = m_aSlots[i].m_Connection.PeerAddress();
+			BanAddr.port = 0;
+			
+			if(net_addr_comp(&Addr, &BanAddr) == 0)
+				Drop(i, Buf);
+		}
+	}
+	return 0;
+}
+
+int CNetServer::Update()
+{
+	int Now = time_timestamp();
+	for(int i = 0; i < MaxClients(); i++)
+	{
+		m_aSlots[i].m_Connection.Update();
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR)
+			Drop(i, m_aSlots[i].m_Connection.ErrorString());
+	}
+	
+	// remove expired bans
+	while(m_BanPool_FirstUsed && m_BanPool_FirstUsed->m_Info.m_Expires < Now)
+	{
+		CBan *pBan = m_BanPool_FirstUsed;
+		BanRemoveByObject(pBan);
+	}
+	
+	return 0;
+}
+
+/*
+	TODO: chopp up this function into smaller working parts
+*/
+int CNetServer::Recv(CNetChunk *pChunk)
+{
+	unsigned Now = time_timestamp();
+	
+	while(1)
+	{
+		NETADDR Addr;
+			
+		// check for a chunk
+		if(m_RecvUnpacker.FetchChunk(pChunk))
+			return 1;
+		
+		// TODO: empty the recvinfo
+		int Bytes = net_udp_recv(m_Socket, &Addr, m_RecvUnpacker.m_aBuffer, NET_MAX_PACKETSIZE);
+
+		// no more packets for now
+		if(Bytes <= 0)
+			break;
+		
+		if(CNetBase::UnpackPacket(m_RecvUnpacker.m_aBuffer, Bytes, &m_RecvUnpacker.m_Data) == 0)
+		{
+			CBan *pBan = 0;
+			NETADDR BanAddr = Addr;
+			int IpHash = (BanAddr.ip[0]+BanAddr.ip[1]+BanAddr.ip[2]+BanAddr.ip[3])&0xff;
+			int Found = 0;
+			BanAddr.port = 0;
+			
+			// search a ban
+			for(pBan = m_aBans[IpHash]; pBan; pBan = pBan->m_pHashNext)
+			{
+				if(net_addr_comp(&pBan->m_Info.m_Addr, &BanAddr) == 0)
+					break;
+			}
+			
+			// check if we just should drop the packet
+			if(pBan)
+			{
+				// banned, reply with a message
+				char BanStr[128];
+				if(pBan->m_Info.m_Expires)
+				{
+					int Mins = ((pBan->m_Info.m_Expires - Now)+59)/60;
+					if(Mins == 1)
+						str_format(BanStr, sizeof(BanStr), "banned for %d minute", Mins);
+					else
+						str_format(BanStr, sizeof(BanStr), "banned for %d minutes", Mins);
+				}
+				else
+					str_format(BanStr, sizeof(BanStr), "banned for life");
+				CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, BanStr, str_length(BanStr)+1);
+				continue;
+			}
+			
+			if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONNLESS)
+			{
+				pChunk->m_Flags = NETSENDFLAG_CONNLESS;
+				pChunk->m_ClientID = -1;
+				pChunk->m_Address = Addr;
+				pChunk->m_DataSize = m_RecvUnpacker.m_Data.m_DataSize;
+				pChunk->m_pData = m_RecvUnpacker.m_Data.m_aChunkData;
+				return 1;
+			}
+			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)
+				{
+					Found = 0;
+				
+					// check if we already got this client
+					for(int i = 0; i < MaxClients(); i++)
+					{
+						NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
+						if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE &&
+							net_addr_comp(&PeerAddr, &Addr) == 0)
+						{
+							Found = 1; // silent ignore.. we got this client already
+							break;
+						}
+					}
+					
+					// client that wants to connect
+					if(!Found)
+					{
+						for(int i = 0; i < MaxClients(); i++)
+						{
+							if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
+							{
+								Found = 1;
+								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[] = "server is full";
+							CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, FullMsg, sizeof(FullMsg));
+						}
+					}
+				}
+				else
+				{
+					// normal packet, find matching slot
+					for(int i = 0; i < MaxClients(); i++)
+					{
+						NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
+						if(net_addr_comp(&PeerAddr, &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);
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+	return 0;
+}
+
+int CNetServer::Send(CNetChunk *pChunk)
+{
+	if(pChunk->m_DataSize >= NET_MAX_PAYLOAD)
+	{
+		dbg_msg("netserver", "packet payload too big. %d. dropping packet", pChunk->m_DataSize);
+		return -1;
+	}
+	
+	if(pChunk->m_Flags&NETSENDFLAG_CONNLESS)
+	{
+		// send connectionless packet
+		CNetBase::SendPacketConnless(m_Socket, &pChunk->m_Address, pChunk->m_pData, pChunk->m_DataSize);
+	}
+	else
+	{
+		int Flags = 0;
+		dbg_assert(pChunk->m_ClientID >= 0, "errornous client id");
+		dbg_assert(pChunk->m_ClientID < MaxClients(), "errornous client id");
+		
+		if(pChunk->m_Flags&NETSENDFLAG_VITAL)
+			Flags = NET_CHUNKFLAG_VITAL;
+		
+		if(m_aSlots[pChunk->m_ClientID].m_Connection.QueueChunk(Flags, pChunk->m_DataSize, pChunk->m_pData) == 0)
+		{
+			if(pChunk->m_Flags&NETSENDFLAG_FLUSH)
+				m_aSlots[pChunk->m_ClientID].m_Connection.Flush();
+		}
+		else
+		{
+			Drop(pChunk->m_ClientID, "error sending data");
+		}
+	}
+	return 0;
+}
+