Permalink
Browse files

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.
  • Loading branch information...
heinrich5991 committed Oct 6, 2018
1 parent aababc6 commit f5fa1a92ed81ed8da721e803a036b1553a38e39e
Showing with 51 additions and 0 deletions.
  1. +5 −0 src/engine/shared/config_variables.h
  2. +5 −0 src/engine/shared/network.h
  3. +41 −0 src/engine/shared/network_server.cpp
@@ -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")
@@ -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);
@@ -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);

0 comments on commit f5fa1a9

Please sign in to comment.