ddnet/src/engine/e_network_server.cpp

409 lines
10 KiB
C++
Raw Normal View History

2009-10-27 14:38:53 +00:00
#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;
}