diff options
author | heinrich5991 <heinrich5991@gmail.com> | 2018-10-06 14:15:50 +0200 |
---|---|---|
committer | heinrich5991 <heinrich5991@gmail.com> | 2018-10-12 22:09:24 +0200 |
commit | f5fa1a92ed81ed8da721e803a036b1553a38e39e (patch) | |
tree | 46cd97852360b098352612df2477849c35eca836 | |
parent | aababc63eeeee1bc41672502ca6c7a1dd9f61d94 (diff) |
Add ratelimits for the legacy handshake
There are two sets of variables for this ratelimit
First, there are `sv_old_clients_per_interval` and
`sv_old_clients_interval` which specify an interval and the number of
clients that can join during that time without being ratelimited.
If you get ratelimited, you get a one in `sv_old_clients_skip` chance of
still continuing the handshake.
Together, this should maintain usability while not under attack and
still being able to connect while under attack.
The defaults are a maximum of 5 connections in 20 seconds without
ratelimit and then only accepting every twentieth connection attempt.
This comes from the following calculation:
A legacy connection request packet consists of 4 bytes UDP data bytes,
the response to that weighs at most 57 UDP data bytes. This results in a
reflection rate of ~15, so 20 should be rather safe from that.
If I add the other protocol headers, I get 42 extra bytes per packet for
IPv4 and 62 extra bytes per packet for IPv6 (determined with Wireshark).
In these cases, the reflection rate is just around 3 or around 2.7,
respectively. So perhaps one could lower `sv_old_clients_skip`
furtherly.
There's an expected waiting time of `sv_old_clients_skip` / 2 seconds
for legitimate clients because those send a connection attempt every
500ms, with a geometric distribution.
-rw-r--r-- | src/engine/shared/config_variables.h | 5 | ||||
-rw-r--r-- | src/engine/shared/network.h | 5 | ||||
-rw-r--r-- | src/engine/shared/network_server.cpp | 41 |
3 files changed, 51 insertions, 0 deletions
diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 3da55d41b..2973e4b77 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -83,7 +83,12 @@ MACRO_CONFIG_STR(SvName, sv_name, 128, "unnamed server", CFGFLAG_SERVER, "Server MACRO_CONFIG_STR(Bindaddr, bindaddr, 128, "", CFGFLAG_CLIENT|CFGFLAG_SERVER|CFGFLAG_MASTER, "Address to bind the client/server to") MACRO_CONFIG_INT(SvPort, sv_port, 8303, 0, 0, CFGFLAG_SERVER, "Port to use for the server") MACRO_CONFIG_INT(SvExternalPort, sv_external_port, 0, 0, 0, CFGFLAG_SERVER, "External port to report to the master servers") + MACRO_CONFIG_INT(SvAllowOldClients, sv_allow_old_clients, 1, 0, 1, CFGFLAG_SERVER, "Allow clients to connect that do not support the anti-spoof protocol (this presents a DoS risk)") +MACRO_CONFIG_INT(SvOldClientsPerInterval, sv_old_clients_per_interval, 5, 0, 0, CFGFLAG_SERVER, "Maximum number of clients that can connect per interval set by `sv_old_clients_interval`") +MACRO_CONFIG_INT(SvOldClientsInterval, sv_old_clients_interval, 20, 0, 0, CFGFLAG_SERVER, "Interval (in seconds) in which `sv_old_clients_per_interval` clients are allowed to connect") +MACRO_CONFIG_INT(SvOldClientsSkip, sv_old_clients_skip, 20, 0, 0, CFGFLAG_SERVER, "How many legacy connection attempts to ignore before sending a legacy handshake despite the rate limit being hit") + MACRO_CONFIG_STR(SvMap, sv_map, 128, "dm1", CFGFLAG_SERVER, "Map to use on the server") MACRO_CONFIG_INT(SvMaxClients, sv_max_clients, 8, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients that are allowed on a server") MACRO_CONFIG_INT(SvMaxClientsPerIP, sv_max_clients_per_ip, 4, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients with the same IP that can connect to the server") diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h index c265c1ab8..446f6ddaf 100644 --- a/src/engine/shared/network.h +++ b/src/engine/shared/network.h @@ -280,6 +280,9 @@ class CNetServer unsigned char m_aaSalts[2][16]; int64 m_LastSaltUpdate; + int64 m_LegacyRatelimitStart; + int m_LegacyRatelimitNum; + CNetRecvUnpacker m_RecvUnpacker; unsigned GetToken(const NETADDR &Addr) const; @@ -290,6 +293,8 @@ class CNetServer unsigned GetLegacyToken(const NETADDR &Addr, int SaltIndex) const; bool IsCorrectLegacyToken(const NETADDR &Addr, unsigned LegacyToken) const; + bool LegacyRatelimit(); + public: int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser); diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp index 877a25a07..577e8802d 100644 --- a/src/engine/shared/network_server.cpp +++ b/src/engine/shared/network_server.cpp @@ -1,5 +1,6 @@ /* (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/math.h> #include <base/system.h> #include <engine/console.h> @@ -9,6 +10,7 @@ #include "netban.h" #include "network.h" +#include <stdlib.h> bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int MaxClientsPerIP, int Flags) { @@ -34,6 +36,9 @@ bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int Ma secure_random_fill(m_aaSalts, sizeof(m_aaSalts)); m_LastSaltUpdate = time_get(); + m_LegacyRatelimitStart = -1; + m_LegacyRatelimitNum = 0; + for(int i = 0; i < NET_MAX_CLIENTS; i++) m_aSlots[i].m_Connection.Init(m_Socket, !g_Config.m_Debug); @@ -150,6 +155,34 @@ bool CNetServer::IsCorrectLegacyToken(const NETADDR &Addr, unsigned LegacyToken) return false; } +bool CNetServer::LegacyRatelimit() +{ + bool Accept = false; + + int Max = g_Config.m_SvOldClientsPerInterval; + int Interval = g_Config.m_SvOldClientsInterval; + bool UseRatelimit = Max > 0 && Interval > 0; + if(UseRatelimit) + { + int64 Now = time_get(); + int64 Freq = time_freq(); + if(m_LegacyRatelimitStart < 0 || m_LegacyRatelimitStart + Interval * Freq <= Now) + { + m_LegacyRatelimitStart = Now; + m_LegacyRatelimitNum = clamp(m_LegacyRatelimitNum - Max, 0, Max); + } + Accept = m_LegacyRatelimitNum < Max; + } + if(g_Config.m_SvOldClientsSkip > 0 && (!Accept || !UseRatelimit)) + { + Accept = rand() <= RAND_MAX / g_Config.m_SvOldClientsSkip; + } + if(Accept && UseRatelimit) + { + m_LegacyRatelimitNum++; + } + return !Accept; +} /* @@ -243,6 +276,14 @@ int CNetServer::Recv(CNetChunk *pChunk) // clients bypass the password check. else if(g_Config.m_SvAllowOldClients && !g_Config.m_Password[0]) { + if(LegacyRatelimit()) + { + if(g_Config.m_Debug) + { + dbg_msg("netserver", "dropping legacy connect due to ratelimit"); + } + continue; + } CNetPacketConstruct aPackets[2]; unsigned LegacyToken = GetLegacyToken(Addr); |