Heya there!
Since my servers are becoming increasingly unplayable during these attacks, I took the liberty of writing my own patch to bridge the time until Rambetter codes something proper. It supersedes the patch created by
ipwnn00bs and defies all attack scenarios proposed so far.
I host several servers on a single dedicated box, which led to outging traffic peaks of 10MB/s and above during attacks - with the patch applied the outgoing traffic is less than the incoming traffc generated by the attackers queries.
While it lacks fancy features like whitelisting and adjustment via cvars yet, it does its job. The limit on getstatus and getinfo imposed by this patch is quite generous, so it should not affect B3 or other server management tools (it will ignore both getstatus and getinfo packets coming from a single IP address in excess of 5 in the past 2 seconds, with a maximum of 32 simultaneous queries from different IPs).
You can get the patch
here.
Edit: Archive now contains .patch files for both ioUrT and ioquake3 (based on Rambetters)
Code snipped from sv_main with the patch applied (ioUrT):
/*
================
SVC_Status
Responds with all the info that qplug or qspy can see about the server
and all connected players. Used for getting detailed information after
the simple info query.
================
*/
/* Added per ip timeout! Rough fix */
typedef struct cl_ip_tout
{
uint8_t count;
uint32_t time;
uint32_t ip;
} cl_ip_tout;
cl_ip_tout *cit_array = 0;
uint8_t cit_count = 0;
void SVC_Status( netadr_t from ) {
char player[1024];
char status[MAX_MSGLEN];
int i;
client_t *cl;
playerState_t *ps;
int statusLength;
int playerLength;
char infostring[MAX_INFO_STRING];
// ignore if we are in single player
if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) {
return;
}
uint32_t from_ip_as_int = *((uint32_t*)from.ip);
cl_ip_tout *match = NULL;
// check for timed out clients.
// also check if the ip is already known (multiple queries or DRDOS)
for (i=0; i < cit_count; i++)
{
if (cit_array[i].time < svs.time) // timeout?
{
cit_array[i].ip = 0; // -> drop
}
else if (cit_array[i].ip == from_ip_as_int) // match found.
{
match = &cit_array[i];
break;
}
}
if (cit_count >= 32) // Prevent mass flooding. Max allowed packets: 32*5 per 2 seconds.
return;
// is the last entry of our array empty? If yes, drop.
if (cit_count > 0 && cit_array[cit_count-1].ip == 0) // resize our array.
{
cit_count--;
cit_array = realloc(cit_array, cit_count*sizeof(cl_ip_tout));
}
if (match != NULL) // now that we've found our match.
{
match->count++;
if (match->count > 5)
{
return; // ignore spammers!
}
}
else // new client
{
for (i=0; i < cit_count; i++) // check for empty array slots
{
if (cit_array[i].ip == 0) // empty slot found.
{
match = &cit_array[i];
break;
}
}
if (match == NULL) // array full -> resizing array.
{
cit_count++;
cit_array = realloc(cit_array, cit_count*sizeof(cl_ip_tout));
match = &cit_array[cit_count-1];
}
match->count = 1; // set data.
match->ip = from_ip_as_int;
match->time = svs.time + 2000;
}
strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );
// echo back the parameter to status. so master servers can use it as a challenge
// to prevent timed spoofed reply packets that add ghost servers
Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
status[0] = 0;
statusLength = 0;
for (i=0 ; i < sv_maxclients->integer ; i++) {
cl = &svs.clients[i];
if ( cl->state >= CS_CONNECTED ) {
ps = SV_GameClientNum( i );
Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n",
ps->persistant[PERS_SCORE], cl->ping, cl->name);
playerLength = strlen(player);
if (statusLength + playerLength >= sizeof(status) ) {
break; // can't hold any more
}
strcpy (status + statusLength, player);
statusLength += playerLength;
}
}
NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status );
}
/*
================
SVC_Info
Responds with a short info message that should be enough to determine
if a user is interested in a server to do a full status
================
*/
void SVC_Info( netadr_t from ) {
int i, count;
char *gamedir;
char infostring[MAX_INFO_STRING];
// ignore if we are in single player
if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) {
return;
}
uint32_t from_ip_as_int = *((uint32_t*)from.ip);
cl_ip_tout *match = NULL;
// check for timed out clients.
// also check if the ip is already known (multiple queries or DRDOS)
for (i=0; i < cit_count; i++)
{
if (cit_array[i].time < svs.time) // timeout?
{
cit_array[i].ip = 0; // -> drop.
}
else if (cit_array[i].ip == from_ip_as_int) // match found.
{
match = &cit_array[i];
break;
}
}
if (cit_count >= 32) // Prevent mass flooding. Max allowed packets: 32*5 per 2 seconds.
return;
// is the last entry of our array empty? If yes, drop.
if (cit_count > 0 && cit_array[cit_count-1].ip == 0) // resize our array.
{
cit_count--;
cit_array = realloc(cit_array, cit_count*sizeof(cl_ip_tout));
}
if (match != NULL) // now that we've found our match.
{
match->count++;
if (match->count > 5)
{
return; // ignore spammers!
}
}
else // new client
{
for (i=0; i < cit_count; i++) // check for empty array slots
{
if (cit_array[i].ip == 0) // empty slot found.
{
match = &cit_array[i];
break;
}
}
if (match == NULL) // array full -> resizing array.
{
cit_count++;
cit_array = realloc(cit_array, cit_count*sizeof(cl_ip_tout));
match = &cit_array[cit_count-1];
}
match->count = 1; // set data.
match->ip = from_ip_as_int;
match->time = svs.time + 2000;
}
And, before i forget it: Yay! first post after months of membership.
Wizard.
This post has been edited by WizardOfGore: 10 December 2011 - 02:27 PM