/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include
#include
#include
#include
#include "netban.h"
#include "network.h"
CNetServer::CNetServer()
{
mem_zero(this, sizeof(*this));
init_rand();
m_Banmaster.m_pNet = this;
m_Banmaster.GenerateToken();
}
bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int MaxClientsPerIP, int Flags)
{
// zero out the whole structure
//mem_zero(this, sizeof(*this));
// open socket
m_Socket = net_udp_create(BindAddr);
if(!m_Socket.type)
return false;
m_pNetBan = pNetBan;
// clamp clients
m_MaxClients = MaxClients;
if(m_MaxClients > NET_MAX_CLIENTS)
m_MaxClients = NET_MAX_CLIENTS;
if(m_MaxClients < 1)
m_MaxClients = 1;
m_MaxClientsPerIP = MaxClientsPerIP;
for(int i = 0; i < NET_MAX_CLIENTS; i++)
m_aSlots[i].m_Connection.Init(m_Socket);
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
);*/
if(m_pfnDelClient)
m_pfnDelClient(ClientID, pReason, m_UserPtr);
m_aSlots[ClientID].m_Connection.Disconnect(pReason);
return 0;
}
int CNetServer::Update()
{
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());
}
return 0;
}
/*
TODO: chopp up this function into smaller working parts
*/
int CNetServer::Recv(CNetChunk *pChunk)
{
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)
{
// 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;
}
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)
{
bool Found = false;
// 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;
}
}
// client that wants to connect
if(!Found)
{
char aIP[NETADDR_MAXSTRSIZE];
net_addr_str(&Addr, aIP, sizeof(aIP), 0);
CPacker P;
P.Reset();
P.AddRaw(BANMASTER_IPCHECK, sizeof(BANMASTER_IPCHECK));
P.AddString(aIP, NETADDR_MAXSTRSIZE);
P.AddString(m_Banmaster.m_aBanmasterToken, CBanmaster::MAX_TOKEN_LENGTH);
CNetChunk Packet;
Packet.m_ClientID = -1;
Packet.m_Flags = NETSENDFLAG_CONNLESS;
Packet.m_DataSize = P.Size();
Packet.m_pData = P.Data();
m_Banmaster.SendToAll(&Packet);
// 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));
}
}
}
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);
}
}
}
}
}
}
}
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;
}
void CNetServer::SetMaxClientsPerIP(int Max)
{
// clamp
if(Max < 1)
Max = 1;
else if(Max > NET_MAX_CLIENTS)
Max = NET_MAX_CLIENTS;
m_MaxClientsPerIP = Max;
}
int CNetServer::CBanmaster::Add(const char *pAddrStr)
{
if(m_NumBanmasters >= MAX_BANMASTERS)
return 2;
NETADDR Addr;
if(net_host_lookup(pAddrStr, &Addr, NETTYPE_ALL))
return 1;
if(Addr.port == 0)
Addr.port = BANMASTER_PORT;
for(int i = 0; i < m_NumBanmasters; i++)
if(mem_comp(&Addr, &m_aBanmasters[i], sizeof(NETADDR)) == 0)
return 3;
m_aBanmasters[m_NumBanmasters] = Addr;
m_NumBanmasters++;
return 0;
}
int CNetServer::CBanmaster::Num() const
{
return m_NumBanmasters;
}
NETADDR *CNetServer::CBanmaster::Get(int Index)
{
if(Index < 0 || Index >= m_NumBanmasters)
return 0;
return &m_aBanmasters[Index];
}
int CNetServer::CBanmaster::CheckValidity(NETADDR *pAddr, const char* pToken)
{
if(str_comp(m_aBanmasterToken, pToken) == 0)
for(int i = 0; i < m_NumBanmasters; i++)
if(net_addr_comp(&m_aBanmasters[i], pAddr) == 0)
return i;
return -1;
}
void CNetServer::CBanmaster::Clear()
{
m_NumBanmasters = 0;
}
void CNetServer::CBanmaster::SendToAll(CNetChunk *pP)
{
for(int i = 0; i < m_NumBanmasters; i++)
{
pP->m_Address = m_aBanmasters[i];
m_pNet->Send(pP);
}
}
void CNetServer::CBanmaster::GenerateToken()
{
for(int i = 0; i < MAX_TOKEN_LENGTH-1; i++)
{
// 32 - 128
m_aBanmasterToken[i] = irand() % 97 + 32;
}
m_aBanmasterToken[MAX_TOKEN_LENGTH-1] = 0;
}