ddnet/src/engine/shared/network_server.cpp

823 lines
23 KiB
C++
Raw Normal View History

2010-11-20 10:37:14 +00:00
/* (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 <base/hash_ctxt.h>
#include <base/system.h>
2009-10-27 14:38:53 +00:00
#include "config.h"
2011-12-29 22:36:53 +00:00
#include "netban.h"
#include "network.h"
#include <engine/shared/compression.h>
#include <engine/shared/packer.h>
2015-08-14 16:33:42 +00:00
#include <engine/shared/protocol.h>
const int g_DummyMapCrc = 0xD6909B17;
const unsigned char g_aDummyMapData[] = {
0x44, 0x41, 0x54, 0x41, 0x04, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x00, 0x00,
0xEC, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
2016-05-25 21:45:32 +00:00
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, 0x68, 0x00, 0x00, 0x00,
2016-05-25 21:45:32 +00:00
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
0x1C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2016-05-25 21:45:32 +00:00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
2016-05-25 21:45:32 +00:00
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, 0x01, 0x00, 0x05, 0x00, 0x3C, 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,
2016-05-25 21:45:32 +00:00
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,
0x78, 0x9C, 0x63, 0x64, 0x60, 0x60, 0x60, 0x44, 0xC2, 0x00, 0x00, 0x38,
0x00, 0x05};
2015-08-13 08:58:47 +00:00
2017-03-21 10:24:44 +00:00
static SECURITY_TOKEN ToSecurityToken(const unsigned char *pData)
2015-08-12 20:43:37 +00:00
{
return (int)pData[0] | (pData[1] << 8) | (pData[2] << 16) | (pData[3] << 24);
}
bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int MaxClientsPerIP)
2009-10-27 14:38:53 +00:00
{
// zero out the whole structure
this->~CNetServer();
new(this) CNetServer{};
2009-10-27 14:38:53 +00:00
// open socket
m_Socket = net_udp_create(BindAddr);
if(!m_Socket)
2009-10-27 14:38:53 +00:00
return false;
m_Address = BindAddr;
2011-12-29 22:36:53 +00:00
m_pNetBan = pNetBan;
m_MaxClients = clamp(MaxClients, 1, (int)NET_MAX_CLIENTS);
m_MaxClientsPerIP = MaxClientsPerIP;
m_NumConAttempts = 0;
m_TimeNumConAttempts = time_get();
2016-04-23 15:23:01 +00:00
m_VConnNum = 0;
m_VConnFirst = 0;
2020-10-27 17:57:14 +00:00
secure_random_fill(m_aSecurityTokenSeed, sizeof(m_aSecurityTokenSeed));
2020-10-26 14:14:07 +00:00
for(auto &Slot : m_aSlots)
Slot.m_Connection.Init(m_Socket, true);
2009-10-27 14:38:53 +00:00
return true;
}
int CNetServer::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
{
m_pfnNewClient = pfnNewClient;
m_pfnDelClient = pfnDelClient;
2020-10-27 17:57:14 +00:00
m_pUser = pUser;
2009-10-27 14:38:53 +00:00
return 0;
}
2015-08-23 15:51:28 +00:00
int CNetServer::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_NEWCLIENT_NOAUTH pfnNewClientNoAuth, NETFUNC_CLIENTREJOIN pfnClientRejoin, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
2015-08-13 08:58:47 +00:00
{
m_pfnNewClient = pfnNewClient;
m_pfnNewClientNoAuth = pfnNewClientNoAuth;
2015-08-23 15:51:28 +00:00
m_pfnClientRejoin = pfnClientRejoin;
2015-08-13 08:58:47 +00:00
m_pfnDelClient = pfnDelClient;
2020-10-27 17:57:14 +00:00
m_pUser = pUser;
2015-08-13 08:58:47 +00:00
return 0;
}
2009-10-27 14:38:53 +00:00
int CNetServer::Close()
{
2022-04-14 09:50:10 +00:00
if(!m_Socket)
return 0;
return net_udp_close(m_Socket);
2009-10-27 14:38:53 +00:00
}
int CNetServer::Drop(int ClientID, const char *pReason)
{
2010-05-29 07:25:38 +00:00
// TODO: insert lots of checks here
2009-10-27 14:38:53 +00:00
if(m_pfnDelClient)
2020-10-27 17:57:14 +00:00
m_pfnDelClient(ClientID, pReason, m_pUser);
2009-10-27 14:38:53 +00:00
m_aSlots[ClientID].m_Connection.Disconnect(pReason);
2009-10-27 14:38:53 +00:00
return 0;
}
int CNetServer::Update()
{
for(int i = 0; i < MaxClients(); i++)
{
m_aSlots[i].m_Connection.Update();
2014-08-09 15:25:29 +00:00
if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR &&
(!m_aSlots[i].m_Connection.m_TimeoutProtected ||
!m_aSlots[i].m_Connection.m_TimeoutSituation))
{
2014-08-09 13:37:10 +00:00
Drop(i, m_aSlots[i].m_Connection.ErrorString());
}
2009-10-27 14:38:53 +00:00
}
2009-10-27 14:38:53 +00:00
return 0;
}
Add HTTP masterserver registering and HTTP masterserver Registering ----------- The idea is that game servers push their server info to the masterservers every 15 seconds or when the server info changes, but not more than once per second. The game servers do not support the old registering protocol anymore, the backward compatibility is handled by the masterserver. The register call is a HTTP POST to a URL like `https://master1.ddnet.tw/ddnet/15/register` and looks like this: ```json POST /ddnet/15/register HTTP/1.1 Address: tw-0.6+udp://connecting-address.invalid:8303 Secret: 81fa3955-6f83-4290-818d-31c0906b1118 Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6 Info-Serial: 0 { "max_clients": 64, "max_players": 64, "passworded": false, "game_type": "TestDDraceNetwork", "name": "My DDNet server", "map": { "name": "dm1", "sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf", "size": 5805 }, "version": "0.6.4, 16.0.3", "clients": [] } ``` The `Address` header declares that the server wants to register itself as a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible protocol. The free-form `Secret` header is used as a server identity, the server list will be deduplicated via this secret. The free-form `Challenge-Secret` is sent back via UDP for a port forward check. This might have security implications as the masterserver can be asked to send a UDP packet containing some user-controlled bytes. This is somewhat mitigated by the fact that it can only go to an attacker-controlled IP address. The `Info-Serial` header is an integer field that should increase each time the server info (in the body) changes. The masterserver uses that field to ensure that it doesn't use old server infos. The body is a free-form JSON object set by the game server. It should contain certain keys in the correct form to be accepted by clients. The body is optional if the masterserver already confirmed the reception of the info with the given `Info-Serial`. Not shown in this payload is the `Connless-Token` header that is used for Teeworlds 0.7 style communication. Also not shown is the `Challenge-Token` that should be included once the server receives the challenge token via UDP. The masterserver responds with a `200 OK` with a body like this: ``` {"status":"success"} ``` The `status` field can be `success` if the server was successfully registered on the masterserver, `need_challenge` if the masterserver wants the correct `Challenge-Token` header before the register process is successful, `need_info` if the server sent an empty body but the masterserver doesn't actually know the server info. It can also be `error` if the request was malformed, only in this case an HTTP status code except `200 OK` is sent. Synchronization --------------- The masterserver keeps state and outputs JSON files every second. ```json { "servers": [ { "addresses": [ "tw-0.6+udp://127.0.0.1:8303", "tw-0.6+udp://[::1]:8303" ], "info_serial": 0, "info": { "max_clients": 64, "max_players": 64, "passworded": false, "game_type": "TestDDraceNetwork", "name": "My DDNet server", "map": { "name": "dm1", "sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf", "size": 5805 }, "version": "0.6.4, 16.0.3", "clients": [] } } ] } ``` `servers.json` (or configured by `--out`) is a server list that is compatible with DDNet 15.5+ clients. It is a JSON object containing a single key `servers` with a list of game servers. Each game server is represented by a JSON object with an `addresses` key containing a list of all known addresses of the server and an `info` key containing the free-form server info sent by the game server. The free-form `info` JSON object re-encoded by the master server and thus canonicalized and stripped of any whitespace characters outside strings. ```json { "kind": "mastersrv", "now": 1816002, "secrets": { "tw-0.6+udp://127.0.0.1:8303": { "ping_time": 1811999, "secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb" }, "tw-0.6+udp://[::1]:8303": { "ping_time": 1811999, "secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb" } }, "servers": { "42d8f991-f2fa-46e5-a9ae-ebcc93846feb": { "info_serial": 0, "info": { "max_clients": 64, "max_players": 64, "passworded": false, "game_type": "TestDDraceNetwork", "name": "My DDNet server", "map": { "name": "dm1", "sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf", "size": 5805 }, "version": "0.6.4, 16.0.3", "clients": [] } } } } ``` `--write-dump` outputs a JSON file compatible with `--read-dump-dir`, this can be used to synchronize servers across different masterservers. `--read-dump-dir` is also used to ingest servers from the backward compatibility layer that pings each server for their server info using the old protocol. The `kind` field describe that this is `mastersrv` output and not from a `backcompat`. This is used for prioritizing `mastersrv` information over `backcompat` information. The `now` field contains an integer describing the current time in milliseconds relative an unspecified epoch that is fixed for each JSON file. This is done instead of using the current time as the epoch for better compression of non-changing data. `secrets` is a map from each server address and to a JSON object containing the last ping time (`ping_time`) in milliseconds relative to the same epoch as before, and the server secret (`secret`) that is used to unify server infos from different addresses of the same logical server. `servers` is a map from the aforementioned `secret`s to the corresponding `info_serial` and `info`. ```json [ "tw-0.6+udp://127.0.0.1:8303", "tw-0.6+udp://[::1]:8303" ] ``` `--write-addresses` outputs a JSON file containing all addresses corresponding to servers that are registered to HTTP masterservers. It does not contain the servers that are obtained via backward compatibility measures. This file can be used by an old-style masterserver to also list new-style servers without the game servers having to register there. An implementation of this can be found at https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat for Teeworlds 0.5/0.6 masterservers and at https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat for Teeworlds 0.7 masterservers. All these JSON files can be sent over the network in an efficient way using https://github.com/heinrich5991/twmaster-collect. It establishes a zstd-compressed TCP connection authenticated by a string token that is sent in plain-text. It watches the specified file and transmits it every time it changes. Due to the zstd-compression, the data sent over the network is similar to the size of a diff. Implementation -------------- The masterserver implementation was done in Rust. The current gameserver register implementation doesn't support more than one masterserver for registering.
2022-05-19 20:03:17 +00:00
SECURITY_TOKEN CNetServer::GetGlobalToken()
{
static NETADDR NullAddr = {0};
return GetToken(NullAddr);
}
2015-08-12 20:43:37 +00:00
SECURITY_TOKEN CNetServer::GetToken(const NETADDR &Addr)
{
SHA256_CTX Sha256;
sha256_init(&Sha256);
2020-10-27 17:57:14 +00:00
sha256_update(&Sha256, (unsigned char *)m_aSecurityTokenSeed, sizeof(m_aSecurityTokenSeed));
sha256_update(&Sha256, (unsigned char *)&Addr, 20); // omit port, bad idea!
2015-08-12 20:43:37 +00:00
SECURITY_TOKEN SecurityToken = ToSecurityToken(sha256_finish(&Sha256).data);
2015-08-12 20:43:37 +00:00
if(SecurityToken == NET_SECURITY_TOKEN_UNKNOWN ||
2015-08-12 20:43:37 +00:00
SecurityToken == NET_SECURITY_TOKEN_UNSUPPORTED)
SecurityToken = 1;
2015-08-12 20:43:37 +00:00
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)
{
int FoundAddr = 0;
for(int i = 0; i < MaxClients(); ++i)
{
2015-08-23 10:29:41 +00:00
if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE ||
(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR &&
(!m_aSlots[i].m_Connection.m_TimeoutProtected ||
!m_aSlots[i].m_Connection.m_TimeoutSituation)))
2015-08-12 20:43:37 +00:00
continue;
2018-10-08 18:04:04 +00:00
if(!net_addr_comp_noport(&Addr, m_aSlots[i].m_Connection.PeerAddress()))
2015-08-12 20:43:37 +00:00
FoundAddr++;
}
return FoundAddr;
}
2016-04-27 20:09:18 +00:00
bool CNetServer::Connlimit(NETADDR Addr)
{
2021-06-23 05:05:49 +00:00
int64_t Now = time_get();
2016-04-27 20:09:18 +00:00
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;
}
2015-08-12 20:43:37 +00:00
2020-03-29 02:36:38 +00:00
int CNetServer::TryAcceptClient(NETADDR &Addr, SECURITY_TOKEN SecurityToken, bool VanillaAuth, bool Sixup, SECURITY_TOKEN Token)
2015-08-12 20:43:37 +00:00
{
if(Sixup && !g_Config.m_SvSixup)
{
2020-10-27 17:57:14 +00:00
const char aMsg[] = "0.7 connections are not accepted at this time";
CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aMsg, sizeof(aMsg), SecurityToken, Sixup);
return -1; // failed to add client?
}
if(Connlimit(Addr))
2016-04-27 20:09:18 +00:00
{
2020-10-27 17:57:14 +00:00
const char aMsg[] = "Too many connections in a short time";
CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aMsg, sizeof(aMsg), SecurityToken, Sixup);
2016-04-27 20:09:18 +00:00
return -1; // failed to add client
}
2015-08-12 20:43:37 +00:00
// check for sv_max_clients_per_ip
if(NumClientsWithAddr(Addr) + 1 > m_MaxClientsPerIP)
2015-08-12 20:43:37 +00:00
{
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "Only %d players with the same IP are allowed", m_MaxClientsPerIP);
2020-03-29 02:36:38 +00:00
CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf) + 1, SecurityToken, Sixup);
2015-08-12 20:43:37 +00:00
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)
2015-08-12 20:43:37 +00:00
{
2020-10-27 17:57:14 +00:00
const char aFullMsg[] = "This server is full";
CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aFullMsg, sizeof(aFullMsg), SecurityToken, Sixup);
2015-08-12 20:43:37 +00:00
return -1; // failed to add client
}
// init connection slot
2020-03-29 02:36:38 +00:00
m_aSlots[Slot].m_Connection.DirectInit(Addr, SecurityToken, Token, Sixup);
2015-08-12 20:43:37 +00:00
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)
2015-08-13 08:58:47 +00:00
{
char aAddrStr[NETADDR_MAXSTRSIZE];
net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr), true);
dbg_msg("security", "client accepted %s", aAddrStr);
2015-08-13 08:58:47 +00:00
}
if(VanillaAuth)
2020-10-27 17:57:14 +00:00
m_pfnNewClientNoAuth(Slot, m_pUser);
2015-08-13 08:58:47 +00:00
else
2020-10-27 17:57:14 +00:00
m_pfnNewClient(Slot, m_pUser, Sixup);
2015-08-12 20:43:37 +00:00
return Slot; // done
}
void CNetServer::SendMsgs(NETADDR &Addr, const CPacker **ppMsgs, int Num)
2015-08-13 08:58:47 +00:00
{
2020-10-27 17:57:14 +00:00
CNetPacketConstruct Construct;
mem_zero(&Construct, sizeof(Construct));
unsigned char *pChunkData = &Construct.m_aChunkData[Construct.m_DataSize];
2015-08-13 08:58:47 +00:00
2020-10-27 17:57:14 +00:00
for(int i = 0; i < Num; i++)
2015-08-13 08:58:47 +00:00
{
const CPacker *pMsg = ppMsgs[i];
2015-08-13 08:58:47 +00:00
CNetChunkHeader Header;
Header.m_Flags = NET_CHUNKFLAG_VITAL;
2015-08-13 08:58:47 +00:00
Header.m_Size = pMsg->Size();
Header.m_Sequence = i + 1;
2015-08-13 08:58:47 +00:00
pChunkData = Header.Pack(pChunkData);
mem_copy(pChunkData, pMsg->Data(), pMsg->Size());
pChunkData += pMsg->Size();
2020-10-27 17:57:14 +00:00
Construct.m_NumChunks++;
2015-08-13 08:58:47 +00:00
}
2020-10-27 17:57:14 +00:00
Construct.m_DataSize = (int)(pChunkData - Construct.m_aChunkData);
CNetBase::SendPacket(m_Socket, &Addr, &Construct, NET_SECURITY_TOKEN_UNSUPPORTED);
2015-08-13 08:58:47 +00:00
}
// connection-less msg packet without token-support
void CNetServer::OnPreConnMsg(NETADDR &Addr, CNetPacketConstruct &Packet)
2015-08-12 20:43:37 +00:00
{
bool IsCtrl = Packet.m_Flags & NET_PACKETFLAG_CONTROL;
2015-08-13 08:58:47 +00:00
int CtrlMsg = m_RecvUnpacker.m_Data.m_aChunkData[0];
// log flooding
//TODO: remove
if(g_Config.m_Debug)
{
2021-06-23 05:05:49 +00:00
int64_t 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)
2015-08-13 08:58:47 +00:00
{
if(g_Config.m_SvVanillaAntiSpoof && g_Config.m_Password[0] == '\0')
{
bool Flooding = false;
if(g_Config.m_SvVanConnPerSecond)
2016-04-23 15:23:01 +00:00
{
// detect flooding
Flooding = m_VConnNum > g_Config.m_SvVanConnPerSecond;
2021-06-23 05:05:49 +00:00
const int64_t Now = time_get();
if(Now <= m_VConnFirst + time_freq())
{
m_VConnNum++;
}
else
{
m_VConnNum = 1;
m_VConnFirst = Now;
}
2016-04-23 15:23:01 +00:00
}
if(g_Config.m_Debug && Flooding)
2016-04-23 15:23:01 +00:00
{
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
2018-07-10 09:29:02 +00:00
// to the client. Therefore a dummy map is sent in the same
2016-04-23 15:44:42 +00:00
// 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)
CPacker MapChangeMsg;
MapChangeMsg.Reset();
MapChangeMsg.AddInt((NETMSG_MAP_CHANGE << 1) | 1);
if(Flooding)
2016-04-23 15:23:01 +00:00
{
2016-04-23 15:44:42 +00:00
// Fallback to dm1
2016-04-23 15:23:01 +00:00
MapChangeMsg.AddString("dm1", 0);
MapChangeMsg.AddInt(0xf2159e6e);
MapChangeMsg.AddInt(5805);
}
else
{
2016-04-23 15:44:42 +00:00
// dummy map
2016-04-23 15:23:01 +00:00
MapChangeMsg.AddString("dummy", 0);
MapChangeMsg.AddInt(g_DummyMapCrc);
2016-04-23 15:23:01 +00:00
MapChangeMsg.AddInt(sizeof(g_aDummyMapData));
}
CPacker MapDataMsg;
MapDataMsg.Reset();
MapDataMsg.AddInt((NETMSG_MAP_DATA << 1) | 1);
if(Flooding)
2016-04-23 15:23:01 +00:00
{
// 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
{
2016-04-23 15:44:42 +00:00
// send dummy map data
2016-04-23 15:23:01 +00:00
MapDataMsg.AddInt(1); // last chunk
MapDataMsg.AddInt(g_DummyMapCrc); // crc
2016-04-23 15:23:01 +00:00
MapDataMsg.AddInt(0); // chunk index
MapDataMsg.AddInt(sizeof(g_aDummyMapData)); // map size
MapDataMsg.AddRaw(g_aDummyMapData, sizeof(g_aDummyMapData)); // map data
}
CPacker ConReadyMsg;
ConReadyMsg.Reset();
ConReadyMsg.AddInt((NETMSG_CON_READY << 1) | 1);
CPacker SnapEmptyMsg;
SnapEmptyMsg.Reset();
SnapEmptyMsg.AddInt((NETMSG_SNAPEMPTY << 1) | 1);
SECURITY_TOKEN SecurityToken = GetVanillaToken(Addr);
SnapEmptyMsg.AddInt(SecurityToken);
SnapEmptyMsg.AddInt(SecurityToken + 1);
// send all chunks/msgs in one packet
const CPacker *apMsgs[] = {&MapChangeMsg, &MapDataMsg, &ConReadyMsg,
&SnapEmptyMsg, &SnapEmptyMsg, &SnapEmptyMsg};
SendMsgs(Addr, apMsgs, std::size(apMsgs));
}
else
{
2018-07-10 09:29:02 +00:00
// accept client directly
SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, NULL, 0, NET_SECURITY_TOKEN_UNSUPPORTED);
TryAcceptClient(Addr, NET_SECURITY_TOKEN_UNSUPPORTED);
}
2015-08-13 08:58:47 +00:00
}
else if(!IsCtrl && g_Config.m_SvVanillaAntiSpoof && g_Config.m_Password[0] == '\0')
2015-08-13 08:58:47 +00:00
{
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)
2015-08-13 08:58:47 +00:00
{
SECURITY_TOKEN SecurityToken = Unpacker.GetInt();
if(SecurityToken == GetVanillaToken(Addr))
2015-08-13 08:58:47 +00:00
{
if(g_Config.m_Debug)
dbg_msg("security", "new client (vanilla handshake)");
2015-08-13 08:58:47 +00:00
// try to accept client skipping auth state
TryAcceptClient(Addr, NET_SECURITY_TOKEN_UNSUPPORTED, true);
}
else if(g_Config.m_Debug)
2015-08-13 08:58:47 +00:00
dbg_msg("security", "invalid token (vanilla handshake)");
}
else
{
if(g_Config.m_Debug)
2015-08-13 08:58:47 +00:00
{
dbg_msg("security", "invalid preconn msg %d", Msg);
}
}
}
2015-08-12 20:43:37 +00:00
}
2015-08-23 15:01:01 +00:00
void CNetServer::OnConnCtrlMsg(NETADDR &Addr, int ClientID, int ControlMsg, const CNetPacketConstruct &Packet)
{
if(ControlMsg == NET_CTRLMSG_CONNECT)
2015-08-23 15:01:01 +00:00
{
// got connection attempt inside of valid session
// the client probably wants to reconnect
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));
2015-08-23 15:01:01 +00:00
if(SupportsToken)
2015-08-23 15:01:01 +00:00
{
// response connection request with token
SECURITY_TOKEN Token = GetToken(Addr);
SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC), Token);
}
if(g_Config.m_Debug)
2015-08-23 15:01:01 +00:00
dbg_msg("security", "client %d wants to reconnect", ClientID);
}
else if(ControlMsg == NET_CTRLMSG_ACCEPT && Packet.m_DataSize == 1 + sizeof(SECURITY_TOKEN))
2015-08-23 15:01:01 +00:00
{
SECURITY_TOKEN Token = ToSecurityToken(&Packet.m_aChunkData[1]);
if(Token == GetToken(Addr))
2015-08-23 15:01:01 +00:00
{
// correct token
// try to accept client
if(g_Config.m_Debug)
dbg_msg("security", "client %d reconnect", ClientID);
2015-08-23 15:01:01 +00:00
2015-08-23 16:12:13 +00:00
// reset netconn and process rejoin
m_aSlots[ClientID].m_Connection.Reset(true);
2020-10-27 17:57:14 +00:00
m_pfnClientRejoin(ClientID, m_pUser);
2015-08-23 15:01:01 +00:00
}
}
}
2015-08-12 20:43:37 +00:00
void CNetServer::OnTokenCtrlMsg(NETADDR &Addr, int ControlMsg, const CNetPacketConstruct &Packet)
{
if(ClientExists(Addr))
2015-08-12 20:43:37 +00:00
return; // silently ignore
if(Addr.type == NETTYPE_WEBSOCKET_IPV4)
2015-08-21 11:08:40 +00:00
{
// websocket client doesn't send token
// direct accept
SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC), NET_SECURITY_TOKEN_UNSUPPORTED);
TryAcceptClient(Addr, NET_SECURITY_TOKEN_UNSUPPORTED);
}
else if(ControlMsg == NET_CTRLMSG_CONNECT)
2015-08-12 20:43:37 +00:00
{
// response connection request with token
SECURITY_TOKEN Token = GetToken(Addr);
SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC), Token);
2015-08-12 20:43:37 +00:00
}
else if(ControlMsg == NET_CTRLMSG_ACCEPT)
2015-08-12 20:43:37 +00:00
{
SECURITY_TOKEN Token = ToSecurityToken(&Packet.m_aChunkData[1]);
if(Token == GetToken(Addr))
2015-08-12 20:43:37 +00:00
{
// correct token
// try to accept client
if(g_Config.m_Debug)
dbg_msg("security", "new client (ddnet token)");
2015-08-12 20:43:37 +00:00
TryAcceptClient(Addr, Token);
}
else
{
// invalid token
if(g_Config.m_Debug)
2015-08-12 20:43:37 +00:00
dbg_msg("security", "invalid token");
}
}
}
int CNetServer::OnSixupCtrlMsg(NETADDR &Addr, CNetChunk *pChunk, int ControlMsg, const CNetPacketConstruct &Packet, SECURITY_TOKEN &ResponseToken, SECURITY_TOKEN Token)
2020-03-29 02:36:38 +00:00
{
if(m_RecvUnpacker.m_Data.m_DataSize < 5 || ClientExists(Addr))
return 0; // silently ignore
2020-03-29 02:36:38 +00:00
mem_copy(&ResponseToken, Packet.m_aChunkData + 1, 4);
2020-03-29 02:36:38 +00:00
if(ControlMsg == 5)
{
if(m_RecvUnpacker.m_Data.m_DataSize >= 512)
{
SendTokenSixup(Addr, ResponseToken);
return 0;
}
// Is this behaviour safe to rely on?
pChunk->m_Flags = 0;
pChunk->m_ClientID = -1;
pChunk->m_Address = Addr;
pChunk->m_DataSize = 0;
return 1;
2020-03-29 02:36:38 +00:00
}
else if(ControlMsg == NET_CTRLMSG_CONNECT)
{
SECURITY_TOKEN MyToken = GetToken(Addr);
2023-02-04 00:22:49 +00:00
unsigned char aToken[sizeof(SECURITY_TOKEN)];
mem_copy(aToken, &MyToken, sizeof(aToken));
2020-03-29 02:36:38 +00:00
CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CONNECTACCEPT, aToken, sizeof(aToken), ResponseToken, true);
if(Token == MyToken)
TryAcceptClient(Addr, ResponseToken, false, true, Token);
}
return 0;
2020-03-29 02:36:38 +00:00
}
2015-08-23 10:29:41 +00:00
int CNetServer::GetClientSlot(const NETADDR &Addr)
2015-08-12 20:43:37 +00:00
{
int Slot = -1;
2015-08-12 20:43:37 +00:00
for(int i = 0; i < MaxClients(); i++)
{
if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE &&
2015-08-23 10:29:41 +00:00
m_aSlots[i].m_Connection.State() != NET_CONNSTATE_ERROR &&
2015-08-12 20:43:37 +00:00
net_addr_comp(m_aSlots[i].m_Connection.PeerAddress(), &Addr) == 0)
2015-08-23 10:29:41 +00:00
2015-08-12 20:43:37 +00:00
{
Slot = i;
2015-08-12 20:43:37 +00:00
}
}
return Slot;
2015-08-12 20:43:37 +00:00
}
static bool IsDDNetControlMsg(const CNetPacketConstruct *pPacket)
{
if(!(pPacket->m_Flags & NET_PACKETFLAG_CONTROL) || pPacket->m_DataSize < 1)
{
return false;
}
if(pPacket->m_aChunkData[0] == NET_CTRLMSG_CONNECT && pPacket->m_DataSize >= (int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(SECURITY_TOKEN)) && mem_comp(&pPacket->m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)) == 0)
{
// DDNet CONNECT
return true;
}
if(pPacket->m_aChunkData[0] == NET_CTRLMSG_ACCEPT && pPacket->m_DataSize >= 1 + (int)sizeof(SECURITY_TOKEN))
{
// DDNet ACCEPT
return true;
}
return false;
}
2009-10-27 14:38:53 +00:00
/*
TODO: chopp up this function into smaller working parts
*/
2020-10-27 17:57:14 +00:00
int CNetServer::Recv(CNetChunk *pChunk, SECURITY_TOKEN *pResponseToken)
2009-10-27 14:38:53 +00:00
{
2022-02-14 23:12:52 +00:00
while(true)
2009-10-27 14:38:53 +00:00
{
NETADDR Addr;
2010-05-29 07:25:38 +00:00
// check for a chunk
2009-10-27 14:38:53 +00:00
if(m_RecvUnpacker.FetchChunk(pChunk))
return 1;
2010-05-29 07:25:38 +00:00
// TODO: empty the recvinfo
2018-12-17 21:15:41 +00:00
unsigned char *pData;
int Bytes = net_udp_recv(m_Socket, &Addr, &pData);
2009-10-27 14:38:53 +00:00
2010-05-29 07:25:38 +00:00
// no more packets for now
2009-10-27 14:38:53 +00:00
if(Bytes <= 0)
break;
// 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, NET_SECURITY_TOKEN_UNSUPPORTED);
continue;
}
SECURITY_TOKEN Token;
bool Sixup = false;
2020-10-27 17:57:14 +00:00
*pResponseToken = NET_SECURITY_TOKEN_UNKNOWN;
if(CNetBase::UnpackPacket(pData, Bytes, &m_RecvUnpacker.m_Data, Sixup, &Token, pResponseToken) == 0)
{
if(m_RecvUnpacker.m_Data.m_Flags & NET_PACKETFLAG_CONNLESS)
2009-10-27 14:38:53 +00:00
{
Add HTTP masterserver registering and HTTP masterserver Registering ----------- The idea is that game servers push their server info to the masterservers every 15 seconds or when the server info changes, but not more than once per second. The game servers do not support the old registering protocol anymore, the backward compatibility is handled by the masterserver. The register call is a HTTP POST to a URL like `https://master1.ddnet.tw/ddnet/15/register` and looks like this: ```json POST /ddnet/15/register HTTP/1.1 Address: tw-0.6+udp://connecting-address.invalid:8303 Secret: 81fa3955-6f83-4290-818d-31c0906b1118 Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6 Info-Serial: 0 { "max_clients": 64, "max_players": 64, "passworded": false, "game_type": "TestDDraceNetwork", "name": "My DDNet server", "map": { "name": "dm1", "sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf", "size": 5805 }, "version": "0.6.4, 16.0.3", "clients": [] } ``` The `Address` header declares that the server wants to register itself as a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible protocol. The free-form `Secret` header is used as a server identity, the server list will be deduplicated via this secret. The free-form `Challenge-Secret` is sent back via UDP for a port forward check. This might have security implications as the masterserver can be asked to send a UDP packet containing some user-controlled bytes. This is somewhat mitigated by the fact that it can only go to an attacker-controlled IP address. The `Info-Serial` header is an integer field that should increase each time the server info (in the body) changes. The masterserver uses that field to ensure that it doesn't use old server infos. The body is a free-form JSON object set by the game server. It should contain certain keys in the correct form to be accepted by clients. The body is optional if the masterserver already confirmed the reception of the info with the given `Info-Serial`. Not shown in this payload is the `Connless-Token` header that is used for Teeworlds 0.7 style communication. Also not shown is the `Challenge-Token` that should be included once the server receives the challenge token via UDP. The masterserver responds with a `200 OK` with a body like this: ``` {"status":"success"} ``` The `status` field can be `success` if the server was successfully registered on the masterserver, `need_challenge` if the masterserver wants the correct `Challenge-Token` header before the register process is successful, `need_info` if the server sent an empty body but the masterserver doesn't actually know the server info. It can also be `error` if the request was malformed, only in this case an HTTP status code except `200 OK` is sent. Synchronization --------------- The masterserver keeps state and outputs JSON files every second. ```json { "servers": [ { "addresses": [ "tw-0.6+udp://127.0.0.1:8303", "tw-0.6+udp://[::1]:8303" ], "info_serial": 0, "info": { "max_clients": 64, "max_players": 64, "passworded": false, "game_type": "TestDDraceNetwork", "name": "My DDNet server", "map": { "name": "dm1", "sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf", "size": 5805 }, "version": "0.6.4, 16.0.3", "clients": [] } } ] } ``` `servers.json` (or configured by `--out`) is a server list that is compatible with DDNet 15.5+ clients. It is a JSON object containing a single key `servers` with a list of game servers. Each game server is represented by a JSON object with an `addresses` key containing a list of all known addresses of the server and an `info` key containing the free-form server info sent by the game server. The free-form `info` JSON object re-encoded by the master server and thus canonicalized and stripped of any whitespace characters outside strings. ```json { "kind": "mastersrv", "now": 1816002, "secrets": { "tw-0.6+udp://127.0.0.1:8303": { "ping_time": 1811999, "secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb" }, "tw-0.6+udp://[::1]:8303": { "ping_time": 1811999, "secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb" } }, "servers": { "42d8f991-f2fa-46e5-a9ae-ebcc93846feb": { "info_serial": 0, "info": { "max_clients": 64, "max_players": 64, "passworded": false, "game_type": "TestDDraceNetwork", "name": "My DDNet server", "map": { "name": "dm1", "sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf", "size": 5805 }, "version": "0.6.4, 16.0.3", "clients": [] } } } } ``` `--write-dump` outputs a JSON file compatible with `--read-dump-dir`, this can be used to synchronize servers across different masterservers. `--read-dump-dir` is also used to ingest servers from the backward compatibility layer that pings each server for their server info using the old protocol. The `kind` field describe that this is `mastersrv` output and not from a `backcompat`. This is used for prioritizing `mastersrv` information over `backcompat` information. The `now` field contains an integer describing the current time in milliseconds relative an unspecified epoch that is fixed for each JSON file. This is done instead of using the current time as the epoch for better compression of non-changing data. `secrets` is a map from each server address and to a JSON object containing the last ping time (`ping_time`) in milliseconds relative to the same epoch as before, and the server secret (`secret`) that is used to unify server infos from different addresses of the same logical server. `servers` is a map from the aforementioned `secret`s to the corresponding `info_serial` and `info`. ```json [ "tw-0.6+udp://127.0.0.1:8303", "tw-0.6+udp://[::1]:8303" ] ``` `--write-addresses` outputs a JSON file containing all addresses corresponding to servers that are registered to HTTP masterservers. It does not contain the servers that are obtained via backward compatibility measures. This file can be used by an old-style masterserver to also list new-style servers without the game servers having to register there. An implementation of this can be found at https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat for Teeworlds 0.5/0.6 masterservers and at https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat for Teeworlds 0.7 masterservers. All these JSON files can be sent over the network in an efficient way using https://github.com/heinrich5991/twmaster-collect. It establishes a zstd-compressed TCP connection authenticated by a string token that is sent in plain-text. It watches the specified file and transmits it every time it changes. Due to the zstd-compression, the data sent over the network is similar to the size of a diff. Implementation -------------- The masterserver implementation was done in Rust. The current gameserver register implementation doesn't support more than one masterserver for registering.
2022-05-19 20:03:17 +00:00
if(Sixup && Token != GetToken(Addr) && Token != GetGlobalToken())
continue;
2009-10-27 14:38:53 +00:00
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;
if(m_RecvUnpacker.m_Data.m_Flags & NET_PACKETFLAG_EXTENDED)
{
pChunk->m_Flags |= NETSENDFLAG_EXTENDED;
mem_copy(pChunk->m_aExtraData, m_RecvUnpacker.m_Data.m_aExtraData, sizeof(pChunk->m_aExtraData));
}
2009-10-27 14:38:53 +00:00
return 1;
}
else
{
// drop invalid ctrl packets
if(m_RecvUnpacker.m_Data.m_Flags & NET_PACKETFLAG_CONTROL &&
m_RecvUnpacker.m_Data.m_DataSize == 0)
continue;
2015-08-23 15:01:01 +00:00
// normal packet, find matching slot
int Slot = GetClientSlot(Addr);
if(!Sixup && Slot != -1 && m_aSlots[Slot].m_Connection.m_Sixup)
2020-03-29 02:36:38 +00:00
{
Sixup = true;
if(CNetBase::UnpackPacket(pData, Bytes, &m_RecvUnpacker.m_Data, Sixup, &Token))
2020-03-29 02:36:38 +00:00
continue;
}
if(Slot != -1)
{
// found
// control
if(m_RecvUnpacker.m_Data.m_Flags & NET_PACKETFLAG_CONTROL)
2015-08-23 15:01:01 +00:00
OnConnCtrlMsg(Addr, Slot, m_RecvUnpacker.m_Data.m_aChunkData[0], m_RecvUnpacker.m_Data);
2020-03-29 02:36:38 +00:00
if(m_aSlots[Slot].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr, Token))
2009-10-27 14:38:53 +00:00
{
2015-08-23 10:29:41 +00:00
if(m_RecvUnpacker.m_Data.m_DataSize)
m_RecvUnpacker.Start(&Addr, &m_aSlots[Slot].m_Connection, Slot);
2009-10-27 14:38:53 +00:00
}
}
else
2009-10-27 14:38:53 +00:00
{
// not found, client that wants to connect
2020-03-29 02:36:38 +00:00
if(Sixup)
{
2020-03-29 02:36:38 +00:00
// got 0.7 control msg
2020-10-27 17:57:14 +00:00
if(OnSixupCtrlMsg(Addr, pChunk, m_RecvUnpacker.m_Data.m_aChunkData[0], m_RecvUnpacker.m_Data, *pResponseToken, Token) == 1)
return 1;
}
2020-03-29 02:36:38 +00:00
else if(IsDDNetControlMsg(&m_RecvUnpacker.m_Data))
{
// got ddnet control msg
2015-08-12 20:43:37 +00:00
OnTokenCtrlMsg(Addr, m_RecvUnpacker.m_Data.m_aChunkData[0], m_RecvUnpacker.m_Data);
}
2015-08-12 20:43:37 +00:00
else
{
2015-08-12 20:43:37 +00:00
// got connection-less ctrl or sys msg
OnPreConnMsg(Addr, m_RecvUnpacker.m_Data);
}
2009-10-27 14:38:53 +00:00
}
}
}
}
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)
2009-10-27 14:38:53 +00:00
{
2010-05-29 07:25:38 +00:00
// send connectionless packet
CNetBase::SendPacketConnless(m_Socket, &pChunk->m_Address, pChunk->m_pData, pChunk->m_DataSize,
pChunk->m_Flags & NETSENDFLAG_EXTENDED, pChunk->m_aExtraData);
2009-10-27 14:38:53 +00:00
}
else
{
int Flags = 0;
2022-02-21 14:54:55 +00:00
dbg_assert(pChunk->m_ClientID >= 0, "erroneous client id");
dbg_assert(pChunk->m_ClientID < MaxClients(), "erroneous client id");
if(pChunk->m_Flags & NETSENDFLAG_VITAL)
2009-10-27 14:38:53 +00:00
Flags = NET_CHUNKFLAG_VITAL;
2010-05-29 07:25:38 +00:00
if(m_aSlots[pChunk->m_ClientID].m_Connection.QueueChunk(Flags, pChunk->m_DataSize, pChunk->m_pData) == 0)
{
if(pChunk->m_Flags & NETSENDFLAG_FLUSH)
2010-05-29 07:25:38 +00:00
m_aSlots[pChunk->m_ClientID].m_Connection.Flush();
}
else
{
2013-11-17 01:27:28 +00:00
//Drop(pChunk->m_ClientID, "Error sending data");
2010-05-29 07:25:38 +00:00
}
2009-10-27 14:38:53 +00:00
}
return 0;
}
void CNetServer::SendTokenSixup(NETADDR &Addr, SECURITY_TOKEN Token)
{
SECURITY_TOKEN MyToken = GetToken(Addr);
unsigned char aBuf[512] = {};
mem_copy(aBuf, &MyToken, 4);
int Size = (Token == NET_SECURITY_TOKEN_UNKNOWN) ? 512 : 4;
CNetBase::SendControlMsg(m_Socket, &Addr, 0, 5, aBuf, Size, Token, true);
}
int CNetServer::SendConnlessSixup(CNetChunk *pChunk, SECURITY_TOKEN ResponseToken)
{
if(pChunk->m_DataSize > NET_MAX_PACKETSIZE - 9)
return -1;
unsigned char aBuffer[NET_MAX_PACKETSIZE];
aBuffer[0] = NET_PACKETFLAG_CONNLESS << 2 | 1;
SECURITY_TOKEN Token = GetToken(pChunk->m_Address);
mem_copy(aBuffer + 1, &ResponseToken, 4);
mem_copy(aBuffer + 5, &Token, 4);
mem_copy(aBuffer + 9, pChunk->m_pData, pChunk->m_DataSize);
net_udp_send(m_Socket, &pChunk->m_Address, aBuffer, pChunk->m_DataSize + 9);
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;
}
2014-08-09 15:25:29 +00:00
bool CNetServer::SetTimedOut(int ClientID, int OrigID)
{
if(m_aSlots[ClientID].m_Connection.State() != NET_CONNSTATE_ERROR)
2014-08-09 15:25:29 +00:00
return false;
2020-06-20 16:50:14 +00:00
m_aSlots[ClientID].m_Connection.SetTimedOut(ClientAddr(OrigID), m_aSlots[OrigID].m_Connection.SeqSequence(), m_aSlots[OrigID].m_Connection.AckSequence(), m_aSlots[OrigID].m_Connection.SecurityToken(), m_aSlots[OrigID].m_Connection.ResendBuffer(), m_aSlots[OrigID].m_Connection.m_Sixup);
2014-08-09 15:25:29 +00:00
m_aSlots[OrigID].m_Connection.Reset();
return true;
}
void CNetServer::SetTimeoutProtected(int ClientID)
{
m_aSlots[ClientID].m_Connection.m_TimeoutProtected = true;
}
2014-08-17 03:04:37 +00:00
int CNetServer::ResetErrorString(int ClientID)
{
m_aSlots[ClientID].m_Connection.ResetErrorString();
return 0;
}
const char *CNetServer::ErrorString(int ClientID)
{
return m_aSlots[ClientID].m_Connection.ErrorString();
}