diff options
Diffstat (limited to 'src/engine/e_network_server.cpp')
| -rw-r--r-- | src/engine/e_network_server.cpp | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/src/engine/e_network_server.cpp b/src/engine/e_network_server.cpp new file mode 100644 index 00000000..995290ef --- /dev/null +++ b/src/engine/e_network_server.cpp @@ -0,0 +1,408 @@ +#include <base/system.h> +#include "e_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; + + m_aSlots[pChunk->m_ClientID].m_Connection.QueueChunk(Flags, pChunk->m_DataSize, pChunk->m_pData); + + if(pChunk->m_Flags&NETSENDFLAG_FLUSH) + m_aSlots[pChunk->m_ClientID].m_Connection.Flush(); + } + return 0; +} + |