Skip to content

Commit

Permalink
engine: server: rewrite challenge generator to something more simple:…
Browse files Browse the repository at this point in the history
… salted MD5 of an IP address

The idea was taken from ReHLDS project.
  • Loading branch information
a1batross committed Feb 15, 2025
1 parent c5d4af8 commit 9c50288
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 65 deletions.
15 changes: 1 addition & 14 deletions engine/server/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
98 changes: 47 additions & 51 deletions engine/server/sv_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down Expand Up @@ -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;
}

/*
Expand Down Expand Up @@ -844,15 +840,15 @@ 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;
}

// don't go out of bounds
ofs = packetsize - svs.testpacket_filepos - 1;
if(( ofs < 0 ) || ( ofs > svs.testpacket_filelen ))
{
SV_GetChallenge( from );
SV_SendChallenge( from );
return;
}

Expand Down Expand Up @@ -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 ))
{
Expand Down
3 changes: 3 additions & 0 deletions engine/server/sv_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 ))
Expand Down

0 comments on commit 9c50288

Please sign in to comment.