From 9c50288bbb92a85268b860663522bd47f3028221 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 15 Feb 2025 08:01:11 +0300 Subject: [PATCH] engine: server: rewrite challenge generator to something more simple: salted MD5 of an IP address The idea was taken from ReHLDS project. --- engine/server/server.h | 15 +----- engine/server/sv_client.c | 98 +++++++++++++++++++-------------------- engine/server/sv_init.c | 3 ++ 3 files changed, 51 insertions(+), 65 deletions(-) diff --git a/engine/server/server.h b/engine/server/server.h index 6ec14e788..7a117cbfb 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -279,19 +279,6 @@ typedef struct sv_client_s a program error, like an overflowed reliable buffer ============================================================================= */ -// MAX_CHALLENGES is made large to prevent a denial -// of service attack that could cycle all of them -// out before legitimate users connected -#define MAX_CHALLENGES 1024 - -typedef struct -{ - netadr_t adr; - double time; - int challenge; - qboolean connected; -} challenge_t; - typedef struct { char name[32]; // in GoldSrc max name length is 12 @@ -385,7 +372,7 @@ typedef struct entity_state_t *baselines; // [GI->max_edicts] entity_state_t *static_entities; // [MAX_STATIC_ENTITIES]; - challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting + uint32_t challenge_salt[16]; // pregenerated random numbers for generating challenged based on IP's MD5 address sizebuf_t testpacket; // pregenerataed testpacket, only needs CRC32 patching byte *testpacket_buf; // check for NULL if testpacket is available diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index 9fa2ca578..cb49c5cb1 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -86,38 +86,53 @@ flood the server with invalid connection IPs. With a challenge, they must give a valid IP address. ================= */ -static void SV_GetChallenge( netadr_t from ) +static int SV_GetChallenge( netadr_t from, qboolean *error ) { - int i, oldest = 0; - double oldestTime; + const netadrtype_t type = NET_NetadrType( &from ); + MD5Context_t ctx; + byte digest[16]; - oldestTime = 0x7fffffff; + *error = false; - // see if we already have a challenge for this ip - for( i = 0; i < MAX_CHALLENGES; i++ ) - { - if( !svs.challenges[i].connected && NET_CompareAdr( from, svs.challenges[i].adr )) - break; + MD5Init( &ctx ); - if( svs.challenges[i].time < oldestTime ) - { - oldestTime = svs.challenges[i].time; - oldest = i; - } - } - - if( i == MAX_CHALLENGES ) + switch( type ) + { + case NA_IP: + MD5Update( &ctx, from.ip, sizeof( from.ip )); + break; + case NA_IPX: + MD5Update( &ctx, from.ipx, sizeof( from.ipx )); + break; + case NA_IP6: { - // this is the first time this client has asked for a challenge - svs.challenges[oldest].challenge = (COM_RandomLong( 0, 0x7FFF ) << 16) | COM_RandomLong( 0, 0xFFFF ); - svs.challenges[oldest].adr = from; - svs.challenges[oldest].time = host.realtime; - svs.challenges[oldest].connected = false; - i = oldest; + byte ip6[16]; + NET_NetadrToIP6Bytes( ip6, &from ); + MD5Update( &ctx, ip6, sizeof( ip6 )); + break; + } + case NA_LOOPBACK: + return 0; + default: + *error = true; } + MD5Update( &ctx, (byte *)svs.challenge_salt, sizeof( svs.challenge_salt )); + MD5Final( digest, &ctx ); + + return digest[0] | digest[1] << 8 | digest[2] << 16 | digest[3] << 24; +} + +static void SV_SendChallenge( netadr_t from ) +{ + qboolean error = false; + int challenge = SV_GetChallenge( from, &error ); + + if( error ) + return; + // send it back - Netchan_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, S2C_CHALLENGE" %i", svs.challenges[i].challenge ); + Netchan_OutOfBandPrint( NS_SERVER, from, S2C_CHALLENGE" %i", challenge ); } static int SV_GetFragmentSize( void *pcl, fragsize_t mode ) @@ -211,35 +226,16 @@ Make sure connecting client is not spoofing */ static int SV_CheckChallenge( netadr_t from, int challenge ) { - int i; - - // see if the challenge is valid - // don't care if it is a local address. - if( NET_IsLocalAddress( from )) - return 1; + qboolean error = false; + int challenge2 = SV_GetChallenge( from, &error ); - for( i = 0; i < MAX_CHALLENGES; i++ ) - { - if( NET_CompareAdr( from, svs.challenges[i].adr )) - { - if( challenge == svs.challenges[i].challenge ) - break; // valid challenge -#if 0 - // g-cont. this breaks multiple connections from single machine - SV_RejectConnection( from, "bad challenge %i\n", challenge ); - return 0; -#endif - } - } - - if( i == MAX_CHALLENGES ) + if( error || challenge2 != challenge ) { SV_RejectConnection( from, "no challenge for your address\n" ); - return 0; + return false; } - svs.challenges[i].connected = true; - return 1; + return true; } /* @@ -844,7 +840,7 @@ static void SV_TestBandWidth( netadr_t from ) ( packetsize > FRAGMENT_MAX_SIZE )) { // skip the test and just get challenge - SV_GetChallenge( from ); + SV_SendChallenge( from ); return; } @@ -852,7 +848,7 @@ static void SV_TestBandWidth( netadr_t from ) ofs = packetsize - svs.testpacket_filepos - 1; if(( ofs < 0 ) || ( ofs > svs.testpacket_filelen )) { - SV_GetChallenge( from ); + SV_SendChallenge( from ); return; } @@ -3166,7 +3162,7 @@ void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) } else if( !Q_strcmp( pcmd, C2S_GETCHALLENGE )) { - SV_GetChallenge( from ); + SV_SendChallenge( from ); } else if( !Q_strcmp( pcmd, C2S_CONNECT )) { diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index cf0e43f3f..315ccd939 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -1027,6 +1027,9 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba svs.timestart = Sys_DoubleTime(); svs.spawncount++; // any partially connected client will be restarted + for( i = 0; i < ARRAYSIZE( svs.challenge_salt ); i++ ) + svs.challenge_salt[i] = COM_RandomLong( 0, 0x7FFFFFFE ); + cycle = Cvar_VariableString( "mapchangecfgfile" ); if( COM_CheckString( cycle ))