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. */
|
2019-04-06 00:46:56 +00:00
|
|
|
#include <base/hash_ctxt.h>
|
2020-09-26 19:41:58 +00:00
|
|
|
#include <base/system.h>
|
2009-10-27 14:38:53 +00:00
|
|
|
|
2015-03-04 08:38:34 +00:00
|
|
|
#include "config.h"
|
2011-12-29 22:36:53 +00:00
|
|
|
#include "netban.h"
|
|
|
|
#include "network.h"
|
2023-01-20 20:09:58 +00:00
|
|
|
#include <engine/shared/compression.h>
|
|
|
|
#include <engine/shared/packer.h>
|
2015-08-14 16:33:42 +00:00
|
|
|
#include <engine/shared/protocol.h>
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2023-01-17 21:18:16 +00:00
|
|
|
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,
|
2023-01-17 21:18:16 +00:00
|
|
|
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,
|
2023-01-17 21:18:16 +00:00
|
|
|
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,
|
2023-01-17 21:18:16 +00:00
|
|
|
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,
|
2023-01-17 21:18:16 +00:00
|
|
|
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,
|
2023-01-17 21:18:16 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int MaxClientsPerIp)
|
2009-10-27 14:38:53 +00:00
|
|
|
{
|
|
|
|
// zero out the whole structure
|
2023-01-08 15:27:25 +00:00
|
|
|
this->~CNetServer();
|
|
|
|
new(this) CNetServer{};
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2009-10-27 14:38:53 +00:00
|
|
|
// open socket
|
|
|
|
m_Socket = net_udp_create(BindAddr);
|
2022-03-01 16:34:42 +00:00
|
|
|
if(!m_Socket)
|
2009-10-27 14:38:53 +00:00
|
|
|
return false;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-08-27 16:54:44 +00:00
|
|
|
m_Address = BindAddr;
|
2011-12-29 22:36:53 +00:00
|
|
|
m_pNetBan = pNetBan;
|
|
|
|
|
2022-02-21 15:33:51 +00:00
|
|
|
m_MaxClients = clamp(MaxClients, 1, (int)NET_MAX_CLIENTS);
|
2024-03-05 14:44:09 +00:00
|
|
|
m_MaxClientsPerIp = MaxClientsPerIp;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2015-08-13 09:42:33 +00:00
|
|
|
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));
|
2015-03-05 23:53:59 +00:00
|
|
|
|
2020-10-26 14:14:07 +00:00
|
|
|
for(auto &Slot : m_aSlots)
|
|
|
|
Slot.m_Connection.Init(m_Socket, true);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
int CNetServer::Drop(int ClientId, const char *pReason)
|
2009-10-27 14:38:53 +00:00
|
|
|
{
|
2010-05-29 07:25:38 +00:00
|
|
|
// TODO: insert lots of checks here
|
2009-10-27 14:38:53 +00:00
|
|
|
|
2010-08-17 22:06:00 +00:00
|
|
|
if(m_pfnDelClient)
|
2024-03-05 14:44:09 +00:00
|
|
|
m_pfnDelClient(ClientId, pReason, m_pUser);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aSlots[ClientId].m_Connection.Disconnect(pReason);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
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 &&
|
2014-08-09 16:08:00 +00:00
|
|
|
(!m_aSlots[i].m_Connection.m_TimeoutProtected ||
|
2020-09-26 19:41:58 +00:00
|
|
|
!m_aSlots[i].m_Connection.m_TimeoutSituation))
|
2013-09-09 19:53:13 +00:00
|
|
|
{
|
2014-08-09 13:37:10 +00:00
|
|
|
Drop(i, m_aSlots[i].m_Connection.ErrorString());
|
2013-09-09 19:53:13 +00:00
|
|
|
}
|
2009-10-27 14:38:53 +00:00
|
|
|
}
|
2011-04-13 18:37:12 +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)
|
|
|
|
{
|
2019-04-06 00:47:00 +00:00
|
|
|
SHA256_CTX Sha256;
|
|
|
|
sha256_init(&Sha256);
|
2020-10-27 17:57:14 +00:00
|
|
|
sha256_update(&Sha256, (unsigned char *)m_aSecurityTokenSeed, sizeof(m_aSecurityTokenSeed));
|
2020-09-26 19:41:58 +00:00
|
|
|
sha256_update(&Sha256, (unsigned char *)&Addr, 20); // omit port, bad idea!
|
2015-08-12 20:43:37 +00:00
|
|
|
|
2019-04-06 00:47:00 +00:00
|
|
|
SECURITY_TOKEN SecurityToken = ToSecurityToken(sha256_finish(&Sha256).data);
|
2015-08-12 20:43:37 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(SecurityToken == NET_SECURITY_TOKEN_UNKNOWN ||
|
2015-08-12 20:43:37 +00:00
|
|
|
SecurityToken == NET_SECURITY_TOKEN_UNSUPPORTED)
|
2020-09-26 19:41:58 +00:00
|
|
|
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 ||
|
2016-01-03 00:42:28 +00:00
|
|
|
(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR &&
|
|
|
|
(!m_aSlots[i].m_Connection.m_TimeoutProtected ||
|
2020-09-26 19:41:58 +00:00
|
|
|
!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
|
|
|
{
|
2020-06-19 19:08:16 +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);
|
2020-06-19 19:08:16 +00:00
|
|
|
return -1; // failed to add client?
|
|
|
|
}
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
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
|
2024-03-05 14:44:09 +00:00
|
|
|
if(NumClientsWithAddr(Addr) + 1 > m_MaxClientsPerIp)
|
2015-08-12 20:43:37 +00:00
|
|
|
{
|
|
|
|
char aBuf[128];
|
2024-03-05 14:44:09 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
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
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(VanillaAuth)
|
2015-08-15 08:38:39 +00:00
|
|
|
{
|
2015-08-14 11:49:10 +00:00
|
|
|
// client sequence is unknown if the auth was done
|
|
|
|
// connection-less
|
|
|
|
m_aSlots[Slot].m_Connection.SetUnknownSeq();
|
2015-08-15 08:38:39 +00:00
|
|
|
// correct sequence
|
|
|
|
m_aSlots[Slot].m_Connection.SetSequence(6);
|
|
|
|
}
|
2015-08-14 11:49:10 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
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);
|
2016-05-02 19:35:32 +00:00
|
|
|
dbg_msg("security", "client accepted %s", aAddrStr);
|
2015-08-13 08:58:47 +00:00
|
|
|
}
|
|
|
|
|
2020-09-26 19:41:58 +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
|
|
|
|
}
|
|
|
|
|
2023-01-20 20:09:58 +00:00
|
|
|
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
|
|
|
{
|
2023-01-20 20:09:58 +00:00
|
|
|
const CPacker *pMsg = ppMsgs[i];
|
2015-08-13 08:58:47 +00:00
|
|
|
CNetChunkHeader Header;
|
2015-08-15 08:38:39 +00:00
|
|
|
Header.m_Flags = NET_CHUNKFLAG_VITAL;
|
2015-08-13 08:58:47 +00:00
|
|
|
Header.m_Size = pMsg->Size();
|
2020-09-26 19:41:58 +00:00
|
|
|
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
|
|
|
{
|
2020-09-26 19:41:58 +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];
|
|
|
|
|
2015-08-13 09:42:33 +00:00
|
|
|
// log flooding
|
|
|
|
//TODO: remove
|
2020-09-26 19:41:58 +00:00
|
|
|
if(g_Config.m_Debug)
|
2015-08-13 09:42:33 +00:00
|
|
|
{
|
2021-06-23 05:05:49 +00:00
|
|
|
int64_t Now = time_get();
|
2015-08-13 09:42:33 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(Now - m_TimeNumConAttempts > time_freq())
|
2015-08-13 09:42:33 +00:00
|
|
|
// reset
|
|
|
|
m_NumConAttempts = 0;
|
|
|
|
|
|
|
|
m_NumConAttempts++;
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(m_NumConAttempts > 100)
|
2015-08-13 09:42:33 +00:00
|
|
|
{
|
|
|
|
dbg_msg("security", "flooding detected");
|
|
|
|
|
|
|
|
m_TimeNumConAttempts = Now;
|
|
|
|
m_NumConAttempts = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(IsCtrl && CtrlMsg == NET_CTRLMSG_CONNECT)
|
2015-08-13 08:58:47 +00:00
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
if(g_Config.m_SvVanillaAntiSpoof && g_Config.m_Password[0] == '\0')
|
2015-08-14 12:49:12 +00:00
|
|
|
{
|
2019-06-11 16:12:41 +00:00
|
|
|
bool Flooding = false;
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(g_Config.m_SvVanConnPerSecond)
|
2016-04-23 15:23:01 +00:00
|
|
|
{
|
2019-06-11 16:12:41 +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();
|
2019-06-11 16:12:41 +00:00
|
|
|
|
|
|
|
if(Now <= m_VConnFirst + time_freq())
|
|
|
|
{
|
|
|
|
m_VConnNum++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_VConnNum = 1;
|
|
|
|
m_VConnFirst = Now;
|
|
|
|
}
|
2016-04-23 15:23:01 +00:00
|
|
|
}
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(g_Config.m_Debug && Flooding)
|
2016-04-23 15:23:01 +00:00
|
|
|
{
|
|
|
|
dbg_msg("security", "vanilla connection flooding detected");
|
|
|
|
}
|
|
|
|
|
2015-08-14 12:49:12 +00:00
|
|
|
// simulate accept
|
2016-06-08 07:57:17 +00:00
|
|
|
SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, NULL, 0, NET_SECURITY_TOKEN_UNSUPPORTED);
|
2015-08-14 12:49:12 +00:00
|
|
|
|
|
|
|
// 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.
|
2015-08-14 12:49:12 +00:00
|
|
|
|
|
|
|
// send mapchange + map data + con_ready + 3 x empty snap (with token)
|
2023-01-20 20:09:58 +00:00
|
|
|
CPacker MapChangeMsg;
|
|
|
|
MapChangeMsg.Reset();
|
|
|
|
MapChangeMsg.AddInt((NETMSG_MAP_CHANGE << 1) | 1);
|
2020-09-26 19:41:58 +00:00
|
|
|
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);
|
2023-01-17 21:18:16 +00:00
|
|
|
MapChangeMsg.AddInt(g_DummyMapCrc);
|
2016-04-23 15:23:01 +00:00
|
|
|
MapChangeMsg.AddInt(sizeof(g_aDummyMapData));
|
|
|
|
}
|
2015-08-14 12:49:12 +00:00
|
|
|
|
2023-01-20 20:09:58 +00:00
|
|
|
CPacker MapDataMsg;
|
|
|
|
MapDataMsg.Reset();
|
|
|
|
MapDataMsg.AddInt((NETMSG_MAP_DATA << 1) | 1);
|
2020-09-26 19:41:58 +00:00
|
|
|
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
|
2023-01-17 21:18:16 +00:00
|
|
|
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
|
|
|
|
}
|
2015-08-14 12:49:12 +00:00
|
|
|
|
2023-01-20 20:09:58 +00:00
|
|
|
CPacker ConReadyMsg;
|
|
|
|
ConReadyMsg.Reset();
|
|
|
|
ConReadyMsg.AddInt((NETMSG_CON_READY << 1) | 1);
|
2015-08-14 12:49:12 +00:00
|
|
|
|
2023-01-20 20:09:58 +00:00
|
|
|
CPacker SnapEmptyMsg;
|
|
|
|
SnapEmptyMsg.Reset();
|
|
|
|
SnapEmptyMsg.AddInt((NETMSG_SNAPEMPTY << 1) | 1);
|
2015-08-14 12:49:12 +00:00
|
|
|
SECURITY_TOKEN SecurityToken = GetVanillaToken(Addr);
|
2019-04-11 10:21:42 +00:00
|
|
|
SnapEmptyMsg.AddInt(SecurityToken);
|
|
|
|
SnapEmptyMsg.AddInt(SecurityToken + 1);
|
2015-08-14 12:49:12 +00:00
|
|
|
|
|
|
|
// send all chunks/msgs in one packet
|
2023-01-20 20:09:58 +00:00
|
|
|
const CPacker *apMsgs[] = {&MapChangeMsg, &MapDataMsg, &ConReadyMsg,
|
2020-09-26 19:41:58 +00:00
|
|
|
&SnapEmptyMsg, &SnapEmptyMsg, &SnapEmptyMsg};
|
2023-01-17 21:18:16 +00:00
|
|
|
SendMsgs(Addr, apMsgs, std::size(apMsgs));
|
2015-08-14 12:49:12 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-07-10 09:29:02 +00:00
|
|
|
// accept client directly
|
2016-06-08 07:57:17 +00:00
|
|
|
SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, NULL, 0, NET_SECURITY_TOKEN_UNSUPPORTED);
|
2015-08-14 12:49:12 +00:00
|
|
|
|
|
|
|
TryAcceptClient(Addr, NET_SECURITY_TOKEN_UNSUPPORTED);
|
|
|
|
}
|
2015-08-13 08:58:47 +00:00
|
|
|
}
|
2015-08-17 15:05:37 +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;
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(Msg == NETMSG_INPUT)
|
2015-08-13 08:58:47 +00:00
|
|
|
{
|
|
|
|
SECURITY_TOKEN SecurityToken = Unpacker.GetInt();
|
2020-09-26 19:41:58 +00:00
|
|
|
if(SecurityToken == GetVanillaToken(Addr))
|
2015-08-13 08:58:47 +00:00
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
if(g_Config.m_Debug)
|
2015-08-14 12:49:12 +00:00
|
|
|
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);
|
|
|
|
}
|
2020-09-26 19:41:58 +00:00
|
|
|
else if(g_Config.m_Debug)
|
2015-08-13 08:58:47 +00:00
|
|
|
dbg_msg("security", "invalid token (vanilla handshake)");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
void CNetServer::OnConnCtrlMsg(NETADDR &Addr, int ClientId, int ControlMsg, const CNetPacketConstruct &Packet)
|
2015-08-23 15:01:01 +00:00
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
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 >=
|
2020-09-26 19:41:58 +00:00
|
|
|
(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
|
|
|
|
2020-09-26 19:41:58 +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);
|
|
|
|
}
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(g_Config.m_Debug)
|
2024-03-05 14:44:09 +00:00
|
|
|
dbg_msg("security", "client %d wants to reconnect", ClientId);
|
2015-08-23 15:01:01 +00:00
|
|
|
}
|
2020-09-26 19:41:58 +00:00
|
|
|
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]);
|
2020-09-26 19:41:58 +00:00
|
|
|
if(Token == GetToken(Addr))
|
2015-08-23 15:01:01 +00:00
|
|
|
{
|
|
|
|
// correct token
|
|
|
|
// try to accept client
|
2020-09-26 19:41:58 +00:00
|
|
|
if(g_Config.m_Debug)
|
2024-03-05 14:44:09 +00:00
|
|
|
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
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aSlots[ClientId].m_Connection.Reset(true);
|
|
|
|
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)
|
|
|
|
{
|
2018-10-14 06:18:32 +00:00
|
|
|
if(ClientExists(Addr))
|
2015-08-12 20:43:37 +00:00
|
|
|
return; // silently ignore
|
|
|
|
|
2018-10-14 06:18:32 +00:00
|
|
|
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);
|
|
|
|
}
|
2018-10-14 06:18:32 +00:00
|
|
|
else if(ControlMsg == NET_CTRLMSG_CONNECT)
|
2015-08-12 20:43:37 +00:00
|
|
|
{
|
2018-10-14 06:18:32 +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
|
|
|
}
|
2018-10-14 06:18:32 +00:00
|
|
|
else if(ControlMsg == NET_CTRLMSG_ACCEPT)
|
2015-08-12 20:43:37 +00:00
|
|
|
{
|
|
|
|
SECURITY_TOKEN Token = ToSecurityToken(&Packet.m_aChunkData[1]);
|
2018-10-14 06:18:32 +00:00
|
|
|
if(Token == GetToken(Addr))
|
2015-08-12 20:43:37 +00:00
|
|
|
{
|
|
|
|
// correct token
|
|
|
|
// try to accept client
|
2018-10-14 06:18:32 +00:00
|
|
|
if(g_Config.m_Debug)
|
2015-08-14 12:49:12 +00:00
|
|
|
dbg_msg("security", "new client (ddnet token)");
|
2015-08-12 20:43:37 +00:00
|
|
|
TryAcceptClient(Addr, Token);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// invalid token
|
2018-10-14 06:18:32 +00:00
|
|
|
if(g_Config.m_Debug)
|
2015-08-12 20:43:37 +00:00
|
|
|
dbg_msg("security", "invalid token");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-16 12:41:30 +00:00
|
|
|
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
|
|
|
{
|
2020-06-16 12:41:30 +00:00
|
|
|
if(m_RecvUnpacker.m_Data.m_DataSize < 5 || ClientExists(Addr))
|
|
|
|
return 0; // silently ignore
|
2020-03-29 02:36:38 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
mem_copy(&ResponseToken, Packet.m_aChunkData + 1, 4);
|
2020-03-29 02:36:38 +00:00
|
|
|
|
|
|
|
if(ControlMsg == 5)
|
|
|
|
{
|
2020-06-16 12:41:30 +00:00
|
|
|
if(m_RecvUnpacker.m_Data.m_DataSize >= 512)
|
|
|
|
{
|
|
|
|
SendTokenSixup(Addr, ResponseToken);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is this behaviour safe to rely on?
|
|
|
|
pChunk->m_Flags = 0;
|
2024-03-05 14:44:09 +00:00
|
|
|
pChunk->m_ClientId = -1;
|
2020-06-16 12:41:30 +00:00
|
|
|
pChunk->m_Address = Addr;
|
|
|
|
pChunk->m_DataSize = 0;
|
|
|
|
return 1;
|
2020-03-29 02:36:38 +00:00
|
|
|
}
|
|
|
|
else if(ControlMsg == NET_CTRLMSG_CONNECT)
|
|
|
|
{
|
2020-06-16 12:41:30 +00:00
|
|
|
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-06-16 12:41:30 +00:00
|
|
|
|
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);
|
|
|
|
}
|
2020-06-16 12:41:30 +00:00
|
|
|
|
|
|
|
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
|
|
|
{
|
2018-12-30 11:22:05 +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
|
|
|
{
|
2018-12-30 11:22:05 +00:00
|
|
|
Slot = i;
|
2015-08-12 20:43:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-30 11:22:05 +00:00
|
|
|
return Slot;
|
2015-08-12 20:43:37 +00:00
|
|
|
}
|
|
|
|
|
2018-10-14 06:18:32 +00:00
|
|
|
static bool IsDDNetControlMsg(const CNetPacketConstruct *pPacket)
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
if(!(pPacket->m_Flags & NET_PACKETFLAG_CONTROL) || pPacket->m_DataSize < 1)
|
2018-10-14 06:18:32 +00:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2020-09-26 19:41:58 +00:00
|
|
|
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)
|
2018-10-14 06:18:32 +00:00
|
|
|
{
|
|
|
|
// DDNet CONNECT
|
|
|
|
return true;
|
|
|
|
}
|
2020-09-26 19:41:58 +00:00
|
|
|
if(pPacket->m_aChunkData[0] == NET_CTRLMSG_ACCEPT && pPacket->m_DataSize >= 1 + (int)sizeof(SECURITY_TOKEN))
|
2018-10-14 06:18:32 +00:00
|
|
|
{
|
|
|
|
// 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;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
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;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// TODO: empty the recvinfo
|
2018-12-17 21:15:41 +00:00
|
|
|
unsigned char *pData;
|
2022-03-01 18:27:48 +00:00
|
|
|
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;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2013-07-31 03:36:52 +00:00
|
|
|
// check if we just should drop the packet
|
|
|
|
char aBuf[128];
|
|
|
|
if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf)))
|
|
|
|
{
|
|
|
|
// banned, reply with a message
|
2020-09-26 19:41:58 +00:00
|
|
|
CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf) + 1, NET_SECURITY_TOKEN_UNSUPPORTED);
|
2013-07-31 03:36:52 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-06-16 12:41:30 +00:00
|
|
|
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)
|
2013-07-31 03:36:52 +00:00
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
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())
|
2020-06-16 12:41:30 +00:00
|
|
|
continue;
|
|
|
|
|
2009-10-27 14:38:53 +00:00
|
|
|
pChunk->m_Flags = NETSENDFLAG_CONNLESS;
|
2024-03-05 14:44:09 +00:00
|
|
|
pChunk->m_ClientId = -1;
|
2009-10-27 14:38:53 +00:00
|
|
|
pChunk->m_Address = Addr;
|
|
|
|
pChunk->m_DataSize = m_RecvUnpacker.m_Data.m_DataSize;
|
|
|
|
pChunk->m_pData = m_RecvUnpacker.m_Data.m_aChunkData;
|
2020-09-26 19:41:58 +00:00
|
|
|
if(m_RecvUnpacker.m_Data.m_Flags & NET_PACKETFLAG_EXTENDED)
|
2017-03-29 10:56:13 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2018-12-14 16:12:31 +00:00
|
|
|
else
|
2018-12-13 15:24:37 +00:00
|
|
|
{
|
2018-12-14 16:12:31 +00:00
|
|
|
// drop invalid ctrl packets
|
2020-09-26 19:41:58 +00:00
|
|
|
if(m_RecvUnpacker.m_Data.m_Flags & NET_PACKETFLAG_CONTROL &&
|
|
|
|
m_RecvUnpacker.m_Data.m_DataSize == 0)
|
2018-12-14 16:12:31 +00:00
|
|
|
continue;
|
2015-08-23 15:01:01 +00:00
|
|
|
|
2018-12-30 11:22:05 +00:00
|
|
|
// normal packet, find matching slot
|
|
|
|
int Slot = GetClientSlot(Addr);
|
|
|
|
|
2020-06-16 12:41:30 +00:00
|
|
|
if(!Sixup && Slot != -1 && m_aSlots[Slot].m_Connection.m_Sixup)
|
2020-03-29 02:36:38 +00:00
|
|
|
{
|
|
|
|
Sixup = true;
|
2020-06-16 12:41:30 +00:00
|
|
|
if(CNetBase::UnpackPacket(pData, Bytes, &m_RecvUnpacker.m_Data, Sixup, &Token))
|
2020-03-29 02:36:38 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(Slot != -1)
|
2018-12-13 15:24:37 +00:00
|
|
|
{
|
2018-12-14 16:12:31 +00:00
|
|
|
// found
|
|
|
|
|
|
|
|
// control
|
2020-09-26 19:41:58 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
2018-12-14 16:12:31 +00:00
|
|
|
else
|
2009-10-27 14:38:53 +00:00
|
|
|
{
|
2018-12-14 16:12:31 +00:00
|
|
|
// not found, client that wants to connect
|
|
|
|
|
2020-03-29 02:36:38 +00:00
|
|
|
if(Sixup)
|
2020-06-16 12:41:30 +00:00
|
|
|
{
|
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)
|
2020-06-16 12:41:30 +00:00
|
|
|
return 1;
|
|
|
|
}
|
2020-03-29 02:36:38 +00:00
|
|
|
else if(IsDDNetControlMsg(&m_RecvUnpacker.m_Data))
|
2023-01-17 21:18:16 +00:00
|
|
|
{
|
2018-10-14 06:18:32 +00:00
|
|
|
// 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);
|
2023-01-17 21:18:16 +00:00
|
|
|
}
|
2015-08-12 20:43:37 +00:00
|
|
|
else
|
2023-01-17 21:18:16 +00:00
|
|
|
{
|
2015-08-12 20:43:37 +00:00
|
|
|
// got connection-less ctrl or sys msg
|
|
|
|
OnPreConnMsg(Addr, m_RecvUnpacker.m_Data);
|
2023-01-17 21:18:16 +00:00
|
|
|
}
|
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;
|
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
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
|
2017-03-29 10:56:13 +00:00
|
|
|
CNetBase::SendPacketConnless(m_Socket, &pChunk->m_Address, pChunk->m_pData, pChunk->m_DataSize,
|
2020-09-26 19:41:58 +00:00
|
|
|
pChunk->m_Flags & NETSENDFLAG_EXTENDED, pChunk->m_aExtraData);
|
2009-10-27 14:38:53 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int Flags = 0;
|
2024-03-05 14:44:09 +00:00
|
|
|
dbg_assert(pChunk->m_ClientId >= 0, "erroneous client id");
|
|
|
|
dbg_assert(pChunk->m_ClientId < MaxClients(), "erroneous client id");
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(pChunk->m_Flags & NETSENDFLAG_VITAL)
|
2009-10-27 14:38:53 +00:00
|
|
|
Flags = NET_CHUNKFLAG_VITAL;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
if(m_aSlots[pChunk->m_ClientId].m_Connection.QueueChunk(Flags, pChunk->m_DataSize, pChunk->m_pData) == 0)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
if(pChunk->m_Flags & NETSENDFLAG_FLUSH)
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aSlots[pChunk->m_ClientId].m_Connection.Flush();
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-03-05 14:44:09 +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;
|
|
|
|
}
|
|
|
|
|
2020-06-16 12:41:30 +00:00
|
|
|
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];
|
2020-09-26 19:41:58 +00:00
|
|
|
aBuffer[0] = NET_PACKETFLAG_CONNLESS << 2 | 1;
|
2020-06-16 12:41:30 +00:00
|
|
|
SECURITY_TOKEN Token = GetToken(pChunk->m_Address);
|
2020-09-26 19:41:58 +00:00
|
|
|
mem_copy(aBuffer + 1, &ResponseToken, 4);
|
|
|
|
mem_copy(aBuffer + 5, &Token, 4);
|
|
|
|
mem_copy(aBuffer + 9, pChunk->m_pData, pChunk->m_DataSize);
|
2020-06-16 12:41:30 +00:00
|
|
|
net_udp_send(m_Socket, &pChunk->m_Address, aBuffer, pChunk->m_DataSize + 9);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
void CNetServer::SetMaxClientsPerIp(int Max)
|
2010-06-03 12:48:32 +00:00
|
|
|
{
|
|
|
|
// clamp
|
|
|
|
if(Max < 1)
|
|
|
|
Max = 1;
|
|
|
|
else if(Max > NET_MAX_CLIENTS)
|
|
|
|
Max = NET_MAX_CLIENTS;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
m_MaxClientsPerIp = Max;
|
2010-06-03 12:48:32 +00:00
|
|
|
}
|
2014-08-09 15:25:29 +00:00
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
bool CNetServer::SetTimedOut(int ClientId, int OrigId)
|
2014-08-09 15:25:29 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
if(m_aSlots[ClientId].m_Connection.State() != NET_CONNSTATE_ERROR)
|
2014-08-09 15:25:29 +00:00
|
|
|
return false;
|
|
|
|
|
2024-03-05 14:44:09 +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);
|
|
|
|
m_aSlots[OrigId].m_Connection.Reset();
|
2014-08-09 15:25:29 +00:00
|
|
|
return true;
|
|
|
|
}
|
2014-08-09 16:08:00 +00:00
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
void CNetServer::SetTimeoutProtected(int ClientId)
|
2014-08-09 16:08:00 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aSlots[ClientId].m_Connection.m_TimeoutProtected = true;
|
2014-08-09 16:08:00 +00:00
|
|
|
}
|
2014-08-17 03:04:37 +00:00
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
int CNetServer::ResetErrorString(int ClientId)
|
2014-08-17 03:04:37 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aSlots[ClientId].m_Connection.ResetErrorString();
|
2014-08-17 03:04:37 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
const char *CNetServer::ErrorString(int ClientId)
|
2014-08-17 03:04:37 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
return m_aSlots[ClientId].m_Connection.ErrorString();
|
2014-08-17 03:04:37 +00:00
|
|
|
}
|