summaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/server')
-rw-r--r--src/server/sv_main.c217
1 files changed, 207 insertions, 10 deletions
diff --git a/src/server/sv_main.c b/src/server/sv_main.c
index 75e9dc72..4363e532 100644
--- a/src/server/sv_main.c
+++ b/src/server/sv_main.c
@@ -370,6 +370,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 = NULL;
+ size_t size = 0;
+ 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
@@ -388,6 +564,21 @@ static void SVC_Status( netadr_t from ) {
int statusLength;
int playerLength;
char infostring[MAX_INFO_STRING];
+ static leakyBucket_t bucket;
+
+ // 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 ) );
@@ -501,24 +692,30 @@ Redirect all printfs
*/
static void SVC_RemoteCommand( netadr_t from, msg_t *msg ) {
qboolean valid;
- unsigned int time;
char remaining[1024];
// TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc.
// (OOB messages are the bottleneck here)
#define SV_OUTPUTBUF_LENGTH (1024 - 16)
char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
- static unsigned int lasttime = 0;
char *cmd_aux;
- // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534
- time = Com_Milliseconds();
- if ( (unsigned)( time - lasttime ) < 500u ) {
+ // Prevent using rcon as an amplifier and make dictionary attacks impractical
+ if ( SVC_RateLimitAddress( from, 10, 1000 ) ) {
+ Com_DPrintf( "SVC_Status: rate limit from %s exceeded, dropping request\n",
+ NET_AdrToString( from ) );
return;
}
- lasttime = time;
if ( !strlen( 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;
Com_Printf ("Bad rcon from %s: %s\n", NET_AdrToString (from), Cmd_ArgsFrom(2) );
} else {
@@ -587,7 +784,7 @@ static void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c);
if (!Q_stricmp(c, "getstatus")) {
- SVC_Status( from );
+ SVC_Status( from );
} else if (!Q_stricmp(c, "getinfo")) {
SVC_Info( from );
} else if (!Q_stricmp(c, "getchallenge")) {
@@ -601,8 +798,8 @@ static void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
// server disconnect messages when their new server sees our final
// sequenced messages to the old client
} else {
- Com_DPrintf ("bad connectionless packet from %s:\n%s\n"
- , NET_AdrToString (from), s);
+ Com_DPrintf ("bad connectionless packet from %s:\n%s\n",
+ NET_AdrToString (from), s);
}
}
@@ -610,7 +807,7 @@ static void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
/*
=================
-SV_ReadPackets
+SV_PacketEvent
=================
*/
void SV_PacketEvent( netadr_t from, msg_t *msg ) {