mirror of
https://github.com/UberGames/ioef.git
synced 2024-11-24 13:11:30 +00:00
* Rate limit getstatus and rcon connectionless requests
This commit is contained in:
parent
e7f9b8d193
commit
4056c90358
1 changed files with 207 additions and 10 deletions
|
@ -351,6 +351,182 @@ CONNECTIONLESS COMMANDS
|
||||||
==============================================================================
|
==============================================================================
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
typedef struct leakyBucket_s leakyBucket_t;
|
||||||
|
struct leakyBucket_s {
|
||||||
|
netadrtype_t type;
|
||||||
|
|
||||||
|
union {
|
||||||
|
byte _4[4];
|
||||||
|
byte _6[16];
|
||||||
|
} ipv;
|
||||||
|
|
||||||
|
int lastTime;
|
||||||
|
signed char burst;
|
||||||
|
|
||||||
|
long hash;
|
||||||
|
|
||||||
|
leakyBucket_t *prev, *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is deliberately quite large to make it more of an effort to DoS
|
||||||
|
#define MAX_BUCKETS 16384
|
||||||
|
#define MAX_HASHES 1024
|
||||||
|
|
||||||
|
static leakyBucket_t buckets[ MAX_BUCKETS ];
|
||||||
|
static leakyBucket_t *bucketHashes[ MAX_HASHES ];
|
||||||
|
|
||||||
|
/*
|
||||||
|
================
|
||||||
|
SVC_HashForAddress
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
static long SVC_HashForAddress( netadr_t address ) {
|
||||||
|
byte *ip;
|
||||||
|
size_t size;
|
||||||
|
int i;
|
||||||
|
long hash = 0;
|
||||||
|
|
||||||
|
switch ( address.type ) {
|
||||||
|
case NA_IP: ip = address.ip; size = 4; break;
|
||||||
|
case NA_IP6: ip = address.ip6; size = 16; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( i = 0; i < size; i++ ) {
|
||||||
|
hash += (long)( ip[ i ] ) * ( i + 119 );
|
||||||
|
}
|
||||||
|
|
||||||
|
hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) );
|
||||||
|
hash &= ( MAX_HASHES - 1 );
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
================
|
||||||
|
SVC_BucketForAddress
|
||||||
|
|
||||||
|
Find or allocate a bucket for an address
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
static leakyBucket_t *SVC_BucketForAddress( netadr_t address, int burst, int period ) {
|
||||||
|
leakyBucket_t *bucket = NULL;
|
||||||
|
int i;
|
||||||
|
long hash = SVC_HashForAddress( address );
|
||||||
|
int now = Sys_Milliseconds();
|
||||||
|
|
||||||
|
for ( bucket = bucketHashes[ hash ]; bucket; bucket = bucket->next ) {
|
||||||
|
switch ( bucket->type ) {
|
||||||
|
case NA_IP:
|
||||||
|
if ( memcmp( bucket->ipv._4, address.ip, 4 ) == 0 ) {
|
||||||
|
return bucket;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NA_IP6:
|
||||||
|
if ( memcmp( bucket->ipv._6, address.ip6, 16 ) == 0 ) {
|
||||||
|
return bucket;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( i = 0; i < MAX_BUCKETS; i++ ) {
|
||||||
|
int interval;
|
||||||
|
|
||||||
|
bucket = &buckets[ i ];
|
||||||
|
interval = now - bucket->lastTime;
|
||||||
|
|
||||||
|
// Reclaim expired buckets
|
||||||
|
if ( bucket->lastTime > 0 && interval > ( burst * period ) ) {
|
||||||
|
if ( bucket->prev != NULL ) {
|
||||||
|
bucket->prev->next = bucket->next;
|
||||||
|
} else {
|
||||||
|
bucketHashes[ bucket->hash ] = bucket->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( bucket->next != NULL ) {
|
||||||
|
bucket->next->prev = bucket->prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
Com_Memset( bucket, 0, sizeof( leakyBucket_t ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( bucket->type == NA_BAD ) {
|
||||||
|
bucket->type = address.type;
|
||||||
|
switch ( address.type ) {
|
||||||
|
case NA_IP: Com_Memcpy( bucket->ipv._4, address.ip, 4 ); break;
|
||||||
|
case NA_IP6: Com_Memcpy( bucket->ipv._6, address.ip6, 16 ); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket->lastTime = now;
|
||||||
|
bucket->burst = 0;
|
||||||
|
bucket->hash = hash;
|
||||||
|
|
||||||
|
// Add to the head of the relevant hash chain
|
||||||
|
bucket->next = bucketHashes[ hash ];
|
||||||
|
if ( bucketHashes[ hash ] != NULL ) {
|
||||||
|
bucketHashes[ hash ]->prev = bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket->prev = NULL;
|
||||||
|
bucketHashes[ hash ] = bucket;
|
||||||
|
|
||||||
|
return bucket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't allocate a bucket for this address
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
================
|
||||||
|
SVC_RateLimit
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
static qboolean SVC_RateLimit( leakyBucket_t *bucket, int burst, int period ) {
|
||||||
|
if ( bucket != NULL ) {
|
||||||
|
int now = Sys_Milliseconds();
|
||||||
|
int interval = now - bucket->lastTime;
|
||||||
|
int expired = interval / period;
|
||||||
|
int expiredRemainder = interval % period;
|
||||||
|
|
||||||
|
if ( expired > bucket->burst ) {
|
||||||
|
bucket->burst = 0;
|
||||||
|
bucket->lastTime = now;
|
||||||
|
} else {
|
||||||
|
bucket->burst -= expired;
|
||||||
|
bucket->lastTime = now - expiredRemainder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( bucket->burst < burst ) {
|
||||||
|
bucket->burst++;
|
||||||
|
|
||||||
|
return qfalse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
================
|
||||||
|
SVC_RateLimitAddress
|
||||||
|
|
||||||
|
Rate limit for a particular address
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
static qboolean SVC_RateLimitAddress( netadr_t from, int burst, int period ) {
|
||||||
|
leakyBucket_t *bucket = SVC_BucketForAddress( from, burst, period );
|
||||||
|
|
||||||
|
return SVC_RateLimit( bucket, burst, period );
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
================
|
================
|
||||||
SVC_Status
|
SVC_Status
|
||||||
|
@ -369,12 +545,27 @@ static void SVC_Status( netadr_t from ) {
|
||||||
int statusLength;
|
int statusLength;
|
||||||
int playerLength;
|
int playerLength;
|
||||||
char infostring[MAX_INFO_STRING];
|
char infostring[MAX_INFO_STRING];
|
||||||
|
static leakyBucket_t bucket;
|
||||||
|
|
||||||
// ignore if we are in single player
|
// ignore if we are in single player
|
||||||
if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) {
|
if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent using getstatus as an amplifier
|
||||||
|
if ( SVC_RateLimitAddress( from, 10, 1000 ) ) {
|
||||||
|
Com_DPrintf( "SVC_Status: rate limit from %s exceeded, dropping request\n",
|
||||||
|
NET_AdrToString( from ) );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow getstatus to be DoSed relatively easily, but prevent
|
||||||
|
// excess outbound bandwidth usage when being flooded inbound
|
||||||
|
if ( SVC_RateLimit( &bucket, 10, 100 ) ) {
|
||||||
|
Com_DPrintf( "SVC_Status: rate limit exceeded, dropping request\n" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );
|
strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );
|
||||||
|
|
||||||
// echo back the parameter to status. so master servers can use it as a challenge
|
// echo back the parameter to status. so master servers can use it as a challenge
|
||||||
|
@ -493,24 +684,30 @@ Redirect all printfs
|
||||||
*/
|
*/
|
||||||
static void SVC_RemoteCommand( netadr_t from, msg_t *msg ) {
|
static void SVC_RemoteCommand( netadr_t from, msg_t *msg ) {
|
||||||
qboolean valid;
|
qboolean valid;
|
||||||
unsigned int time;
|
|
||||||
char remaining[1024];
|
char remaining[1024];
|
||||||
// TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc.
|
// TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc.
|
||||||
// (OOB messages are the bottleneck here)
|
// (OOB messages are the bottleneck here)
|
||||||
#define SV_OUTPUTBUF_LENGTH (1024 - 16)
|
#define SV_OUTPUTBUF_LENGTH (1024 - 16)
|
||||||
char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
|
char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
|
||||||
static unsigned int lasttime = 0;
|
|
||||||
char *cmd_aux;
|
char *cmd_aux;
|
||||||
|
|
||||||
// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534
|
// Prevent using rcon as an amplifier and make dictionary attacks impractical
|
||||||
time = Com_Milliseconds();
|
if ( SVC_RateLimitAddress( from, 10, 1000 ) ) {
|
||||||
if ( (unsigned)( time - lasttime ) < 500u ) {
|
Com_DPrintf( "SVC_Status: rate limit from %s exceeded, dropping request\n",
|
||||||
|
NET_AdrToString( from ) );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lasttime = time;
|
|
||||||
|
|
||||||
if ( !strlen( sv_rconPassword->string ) ||
|
if ( !strlen( sv_rconPassword->string ) ||
|
||||||
strcmp (Cmd_Argv(1), sv_rconPassword->string) ) {
|
strcmp (Cmd_Argv(1), sv_rconPassword->string) ) {
|
||||||
|
static leakyBucket_t bucket;
|
||||||
|
|
||||||
|
// Make DoS via rcon impractical
|
||||||
|
if ( SVC_RateLimit( &bucket, 10, 1000 ) ) {
|
||||||
|
Com_DPrintf( "SVC_Status: rate limit exceeded, dropping request\n" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
valid = qfalse;
|
valid = qfalse;
|
||||||
Com_Printf ("Bad rcon from %s: %s\n", NET_AdrToString (from), Cmd_ArgsFrom(2) );
|
Com_Printf ("Bad rcon from %s: %s\n", NET_AdrToString (from), Cmd_ArgsFrom(2) );
|
||||||
} else {
|
} else {
|
||||||
|
@ -579,7 +776,7 @@ static void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
|
||||||
Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c);
|
Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c);
|
||||||
|
|
||||||
if (!Q_stricmp(c, "getstatus")) {
|
if (!Q_stricmp(c, "getstatus")) {
|
||||||
SVC_Status( from );
|
SVC_Status( from );
|
||||||
} else if (!Q_stricmp(c, "getinfo")) {
|
} else if (!Q_stricmp(c, "getinfo")) {
|
||||||
SVC_Info( from );
|
SVC_Info( from );
|
||||||
} else if (!Q_stricmp(c, "getchallenge")) {
|
} else if (!Q_stricmp(c, "getchallenge")) {
|
||||||
|
@ -597,8 +794,8 @@ static void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
|
||||||
// server disconnect messages when their new server sees our final
|
// server disconnect messages when their new server sees our final
|
||||||
// sequenced messages to the old client
|
// sequenced messages to the old client
|
||||||
} else {
|
} else {
|
||||||
Com_DPrintf ("bad connectionless packet from %s:\n%s\n"
|
Com_DPrintf ("bad connectionless packet from %s:\n%s\n",
|
||||||
, NET_AdrToString (from), s);
|
NET_AdrToString (from), s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -606,7 +803,7 @@ static void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
=================
|
=================
|
||||||
SV_ReadPackets
|
SV_PacketEvent
|
||||||
=================
|
=================
|
||||||
*/
|
*/
|
||||||
void SV_PacketEvent( netadr_t from, msg_t *msg ) {
|
void SV_PacketEvent( netadr_t from, msg_t *msg ) {
|
||||||
|
|
Loading…
Reference in a new issue