[twilight-commits] r10433 - in trunk/dpmaster: . doc src testsuite

molivier at icculus.org molivier at icculus.org
Wed Sep 1 18:58:12 EDT 2010


Author: molivier
Date: 2010-09-01 18:58:12 -0400 (Wed, 01 Sep 2010)
New Revision: 10433

Added:
   trunk/dpmaster/src/clients.c
   trunk/dpmaster/src/clients.h
Modified:
   trunk/dpmaster/doc/techinfo.txt
   trunk/dpmaster/readme.txt
   trunk/dpmaster/src/Makefile
   trunk/dpmaster/src/common.c
   trunk/dpmaster/src/common.h
   trunk/dpmaster/src/dpmaster.c
   trunk/dpmaster/src/dpmaster.vcproj
   trunk/dpmaster/src/messages.c
   trunk/dpmaster/src/servers.c
   trunk/dpmaster/src/servers.h
   trunk/dpmaster/src/system.h
   trunk/dpmaster/testsuite/test-multiple_game_families.pl
Log:
Flood protection against abusive client requests, by Timothee Besset. Updated documentation. Only print the first non-matching parameter when building a getserversResponse in verbose mode. Code cleaning and factorization.

Modified: trunk/dpmaster/doc/techinfo.txt
===================================================================
--- trunk/dpmaster/doc/techinfo.txt	2010-08-31 10:53:54 UTC (rev 10432)
+++ trunk/dpmaster/doc/techinfo.txt	2010-09-01 22:58:12 UTC (rev 10433)
@@ -336,7 +336,8 @@
 
             As you can see in this sample, since the game name is mandatory in
             an extended query, you'll have to use the game name "Quake3Arena"
-            explicitly if you want to ask for Q3A servers.
+            explicitly if you want to ask for Q3A servers. Likewise, you'll have
+            to specify "wolfmp" for RtCW servers, and "et" for WoET servers.
 
     7) getserversExtResponse:
 

Modified: trunk/dpmaster/readme.txt
===================================================================
--- trunk/dpmaster/readme.txt	2010-08-31 10:53:54 UTC (rev 10432)
+++ trunk/dpmaster/readme.txt	2010-09-01 22:58:12 UTC (rev 10433)
@@ -14,10 +14,11 @@
  6) LOGGING
  7) GAME POLICY
  8) GAME PROPERTIES
- 9) ADDRESS MAPPING
-10) LISTENING INTERFACES
-11) VERSION HISTORY
-12) CONTACTS & LINKS
+ 9) FLOOD PROTECTION
+10) ADDRESS MAPPING
+11) LISTENING INTERFACES
+12) VERSION HISTORY
+13) CONTACTS & LINKS
 
 
 1) INTRODUCTION:
@@ -164,9 +165,9 @@
 change the path and name of this file using the "--log-file" option.
 
 The obvious way to use the log is to enable it by default. But if you want to do
-that, you may consider using a lesser verbose level ("-v" or "--verbose", with a
-value of 1 - only errors, or 2 - only errors and warnings), as dpmaster tends
-to be very verbose at its default level (3) or higher.
+that, you may want to consider using a lesser verbose level ("-v" or
+"--verbose", with a value of 1 - only errors, or 2 - only errors and warnings),
+as dpmaster tends to be very verbose at its default level (3) or higher.
 
 Another way to use the log is to set the verbose level to its maximum value, but
 to enable the log only when needed, and then to disable it afterwards. This is
@@ -331,8 +332,39 @@
 make any mistake before actually running your master server.
 
 
-9) ADDRESS MAPPING:
+9) FLOOD PROTECTION:
 
+If the master server you run has to handle a lot of clients, you will probably
+be interested in the flood protection mechanism Timothee Besset contributed to
+dpmaster version 2.2.
+
+Its purpose is to protect the master server bandwidth, by temporary ignoring the
+requests of clients which have already made several ones in the few seconds
+before. More precisely, a client can only make a limited number of requests (up
+to a "throttle limit") before it is only allowed one request every X seconds (X
+is called the "decay time"). A simple way to view it is to imagine that each
+client has initially - and at most - a number of tokens equal to the throttle
+limit minus 1. It must use/give one token for each request he does, but it
+regains tokens over time, 1 token every 3 seconds for instance if the decay time
+is set to 3. So for example, with a throttle limit of 5 and a decay time of 3,
+a client could do 5 - 1 = 4 requests in a row before having to wait 3 seconds
+between its requests, or they will not be answered.
+
+This protection is disabled by default because, by definition, it can disturb
+the service provided to the master's users, and given most master servers don't
+have to deal with this type of flood problem, it would be for no real benefits.
+You can enable the protection by passing the option "-f" or "--flood-protection"
+in the command line. The throttle limit and decay time can be modified with
+"--fp-throttle" and "--fp-decay-time" respectively.
+
+You also have the possibility to tune the maximum number of client records and
+the client hash size with "--max-clients" and "--cl-hash-size". But since client
+records are reused extremely rapidly in this mechanism, chances are the default
+values will be way bigger than your actual needs anyway.
+
+
+10) ADDRESS MAPPING:
+
 Address mapping allows you to tell dpmaster to transmit an IPv4 address instead
 of another one to the clients, in the "getserversResponse" messages. It can be
 useful in several cases. Imagine for instance that you have a dpmaster and a
@@ -391,7 +423,7 @@
 test purposes - do NOT run your master with this option!).
 
 
-10) LISTENING INTERFACES:
+11) LISTENING INTERFACES:
 
 By default, dpmaster creates one IPv4 socket and one IPv6 socket (if IPv6
 support is available of course). It will listen on every network interface, on
@@ -401,7 +433,7 @@
 
 Running dpmaster with no "-l" option is (almost) like running it with:
 
-    dpmaster --listen 0.0.0.0 --listen ::
+        dpmaster --listen 0.0.0.0 --listen ::
 
 The first option is for listening on all IPv4 interfaces, the second for
 listening on all IPv6 interfaces, both on the default port. The only
@@ -416,7 +448,7 @@
 between brackets first, so that dpmaster won't get confused when interpreting
 the various colons. For example:
 
-    dpmaster -l an.address.net:546 -l [2000::1234:5678]:890
+        dpmaster -l an.address.net:546 -l [2000::1234:5678]:890
 
 will make dpmaster listen on the IPv6 interface 2000::1234:5678 on port 890,
 and on the IPv4 or IPv6 interface "an.address.net" (depending on what protocol
@@ -424,18 +456,22 @@
 
 IPv6 addressing has a few tricky aspects, and zone indices are one of them. If
 you encounter problems when configuring dpmaster for listening on a link-local
-IPv6 address, I recommend that you take a look at the paragraph regarding zone
-indices in the Wikipedia article about IPv6 <http://en.wikipedia.org/wiki/IPv6>.
+IPv6 address, I recommend that you read the paragraph called "Link-local
+addresses and zone indices" on this Wikipedia page:
 
+        http://en.wikipedia.org/wiki/IPv6_address
 
-11) VERSION HISTORY:
 
+12) VERSION HISTORY:
+
     - version 2.2-dev:
+        Flood protection against abusive client requests, by Timothee Besset
         New system for managing game properties (see GAME PROPERTIES above)
         Support for RtCW and WoET, using the game properties
         Shutdown heartbeats and unknown heartbeats are now ignored
         The chroot jail was preventing daemonization (fixed thanks to LordHavoc)
         The game type was incorrect when printing the server list in the log
+        Less debug output when building a getserversResponse in verbose mode
 
     - version 2.1:
         A gametype value can now be any string, not just a number
@@ -536,7 +572,7 @@
         First publicly available version
 
 
-12) CONTACTS & LINKS:
+13) CONTACTS & LINKS:
 
 You can get the latest versions of DarkPlaces and dpmaster on the DarkPlaces
 home page <http://icculus.org/twilight/darkplaces/>.

Modified: trunk/dpmaster/src/Makefile
===================================================================
--- trunk/dpmaster/src/Makefile	2010-08-31 10:53:54 UTC (rev 10432)
+++ trunk/dpmaster/src/Makefile	2010-09-01 22:58:12 UTC (rev 10433)
@@ -17,7 +17,7 @@
 CFLAGS_COMMON=-Wall
 CFLAGS_DEBUG=$(CFLAGS_COMMON) -g
 CFLAGS_RELEASE=$(CFLAGS_COMMON) -O2 -DNDEBUG
-OBJECTS=common.o dpmaster.o games.o messages.o servers.o system.o
+OBJECTS=clients.o common.o dpmaster.o games.o messages.o servers.o system.o
 
 ##### Commands #####
 

Added: trunk/dpmaster/src/clients.c
===================================================================
--- trunk/dpmaster/src/clients.c	                        (rev 0)
+++ trunk/dpmaster/src/clients.c	2010-09-01 22:58:12 UTC (rev 10433)
@@ -0,0 +1,338 @@
+/*
+	clients.c
+
+	Client list and flood protection for dpmaster
+
+	Copyright (C) 2010  Timothee Besset
+	Copyright (C) 2010  Mathieu Olivier
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+
+#include "common.h"
+#include "system.h"
+#include "clients.h"
+
+
+// ---------- Private types ---------- //
+
+typedef struct client_s
+{
+	user_t user;		// WARNING: MUST be the 1st member, for compatibility with the user hash tables
+	int count;
+	time_t last_time;
+} client_t;
+
+
+// ---------- Private variables ---------- //
+
+static client_t* clients = NULL;
+
+static unsigned int max_nb_clients = DEFAULT_MAX_NB_CLIENTS;
+static user_hash_table_t hash_clients_ipv4;
+static user_hash_table_t hash_clients_ipv6;
+static size_t cl_hash_size = DEFAULT_CL_HASH_SIZE;
+
+// rolling window for allocation
+static int last_used_slot = -1;
+
+// Allow "throttle - 1" queries in a row, then force a throttle to one every "decay time" seconds
+static time_t fp_decay_time = DEFAULT_FP_DECAY_TIME;
+static int fp_throttle = DEFAULT_FP_THROTTLE;
+
+
+// ---------- Public variables ---------- //
+
+// Enable/disabled the flood protection mechanism against abusive client requests
+qboolean flood_protection = false;
+
+
+// ---------- Private functions ---------- //
+
+/*
+====================
+Cl_QueryThrottleDecay
+
+Compute the current throttle value of a client
+====================
+*/
+static int Cl_QueryThrottleDecay( client_t *client )
+{
+	int count = client->count - (int)((crt_time - client->last_time) / fp_decay_time);
+	if ( count < 0 )
+		count = 0;
+
+	return count;
+}
+
+
+/*
+====================
+Cl_AddClient
+
+Add a client to an hash table
+====================
+*/
+static qboolean Cl_AddClient( user_hash_table_t *hash_clients, const struct sockaddr_storage *address, socklen_t addrlen )
+{
+	int first_slot = ( last_used_slot + 1 ) % max_nb_clients;
+	int free_slot = first_slot;
+	client_t* free_client = NULL;
+
+	// look for the next free slot
+	do
+	{
+		int count;
+		client_t* client = &clients[ free_slot ];
+
+		if ( client->user.prev_ptr == NULL )
+		{
+			// this slot is not in use
+			free_client = client;
+			break;
+		}
+
+		// the rolling window lets us retire inactive queries
+		assert( client->count != 0 );
+		count = Cl_QueryThrottleDecay( client );
+		if ( count == 0 )
+		{
+			// this entry is expired, remove from the hash
+			Com_UserHashTable_Remove( &client->user );
+			free_client = client;
+			Com_Printf( MSG_DEBUG, "> Reusing expired client entry %d\n", (int)(client - clients) );
+			break;
+		}
+
+		free_slot = ( free_slot + 1 ) % max_nb_clients;
+	}
+	while ( free_slot != first_slot );
+
+	if ( free_client != NULL )
+	{
+		int hash;
+
+		last_used_slot = free_slot;
+
+		memcpy( &free_client->user.address, address, sizeof( free_client->user.address ) );
+		free_client->user.addrlen = addrlen;
+		free_client->count = 1;
+		free_client->last_time = crt_time;
+
+		hash = Com_AddressHash( address, cl_hash_size );
+		Com_UserHashTable_Add( hash_clients, &free_client->user, hash );
+
+		Com_Printf( MSG_DEBUG,
+					"> New client added: %s\n"
+					"  - index: %u\n"
+					"  - hash: 0x%04X\n",
+					peer_address, free_slot, hash );
+		return true;
+	}
+	else
+	{
+		Com_Printf (MSG_WARNING,
+					"> WARNING: can't add client %s (client list is full)\n",
+					peer_address);
+		return false;
+	}
+}
+
+
+// ---------- Public functions ---------- //
+
+/*
+====================
+Cl_SetHashSize
+
+Set a new hash size value
+====================
+*/
+qboolean Cl_SetHashSize (unsigned int size)
+{
+	// Too late? Or too big?
+	if (clients != NULL || size > MAX_HASH_SIZE)
+		return false;
+
+	cl_hash_size = size;
+	return true;
+}
+
+
+/*
+====================
+Cl_SetMaxNbClients
+
+Set a new maximum number of clients
+====================
+*/
+qboolean Cl_SetMaxNbClients (unsigned int nb)
+{
+	// Too late? Or too small?
+	if (clients != NULL || nb <= 0)
+		return false;
+
+	max_nb_clients = nb;
+	return true;
+}
+
+
+/*
+====================
+Cl_SetFPDecayTime
+
+Set a new decay time for the flood protection
+====================
+*/
+qboolean Cl_SetFPDecayTime (time_t decay)
+{
+	// Too late? Or too small?
+	if (clients != NULL || decay <= 0)
+		return false;
+
+	fp_decay_time = decay;
+	return true;
+}
+
+
+/*
+====================
+Cl_SetFPThrottle
+
+Set a new throttle limit for the flood protection
+====================
+*/
+qboolean Cl_SetFPThrottle (unsigned int throttle)
+{
+	// Too late? Or too small?
+	if (clients != NULL || throttle <= 1)
+		return false;
+
+	fp_throttle = throttle;
+	return true;
+}
+
+
+/*
+====================
+Cl_Init
+
+Initialize the client list and hash tables
+====================
+*/
+qboolean Cl_Init( void )
+{
+	// If the flood protection is enabled
+	if ( flood_protection )
+	{
+		size_t array_size;
+
+		last_used_slot = -1;
+
+		// data
+		array_size = max_nb_clients * sizeof( clients[0] );
+		clients = malloc( array_size );
+		if (!clients)
+		{
+			Com_Printf (MSG_ERROR,
+						"> ERROR: can't allocate the clients array (%s)\n",
+						  strerror (errno));
+			return false;
+		}
+		memset( clients, 0, array_size );
+
+		Com_Printf( MSG_NORMAL, "> %u client records allocated\n", max_nb_clients );
+
+		if (! Com_UserHashTable_InitTables (&hash_clients_ipv4, &hash_clients_ipv6, cl_hash_size, "Client"))
+			return false;
+	}
+	
+	return true;
+}
+
+
+/*
+====================
+Cl_BlockedByThrottle
+
+Return "true" if a client should be temporary ignored because he has sent too many requests recently
+====================
+*/
+qboolean Cl_BlockedByThrottle( const struct sockaddr_storage* addr, socklen_t addrlen )
+{
+	user_hash_table_t* hash_clients;
+	unsigned int hash;
+	client_t *client;
+	qboolean (*IsSameAddress) (const struct sockaddr_storage* addr1, const struct sockaddr_storage* addr2, qboolean* same_public_address);
+
+	// If the flood protection is disabled
+	if ( !flood_protection )
+		return false;
+
+	if ( addr->ss_family == AF_INET6 )
+	{
+		hash_clients = &hash_clients_ipv6;
+		IsSameAddress = &Com_SameIPv6Addr;
+	}
+	else
+	{
+		assert (addr->ss_family == AF_INET);
+		hash_clients = &hash_clients_ipv4;
+		IsSameAddress = &Com_SameIPv4Addr;
+	}
+
+	// look for activity information about this client
+	hash = Com_AddressHash( addr, cl_hash_size );
+	client = (client_t*)hash_clients->entries[ hash ];
+	while ( client != NULL )
+	{
+		if ( addr->ss_family == client->user.address.ss_family )
+		{
+			qboolean same_public_address = false;
+
+			IsSameAddress( addr, &client->user.address, &same_public_address );
+
+			// found entry
+			if ( same_public_address )
+			{
+				int count = Cl_QueryThrottleDecay( client );
+				if ( count >= fp_throttle )
+				{
+					Com_Printf( MSG_NORMAL, "> Client %s: throttled (count == %d)\n", peer_address, count );
+					return true;
+				}
+
+				count++;
+				client->count = count;
+				client->last_time = crt_time;
+
+				if ( count >= fp_throttle )
+				{
+					Com_Printf( MSG_NORMAL, "> Client %s: start throttling (count == %d)\n", peer_address, count );
+					return true;
+				}
+
+				Com_Printf( MSG_DEBUG, "> Client %s: not throttled (count == %d)\n", peer_address, count );
+				return false;
+			}
+		}
+
+		client = (client_t*)client->user.next;
+	}
+
+	assert( client == NULL );
+	return ( ! Cl_AddClient( hash_clients, addr, addrlen ) );
+}

Added: trunk/dpmaster/src/clients.h
===================================================================
--- trunk/dpmaster/src/clients.h	                        (rev 0)
+++ trunk/dpmaster/src/clients.h	2010-09-01 22:58:12 UTC (rev 10433)
@@ -0,0 +1,63 @@
+/*
+	clients.h
+
+	Client list and flood protection for dpmaster
+
+	Copyright (C) 2010  Timothee Besset
+	Copyright (C) 2010  Mathieu Olivier
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+
+#ifndef _CLIENTS_H_
+#define _CLIENTS_H_
+
+
+// ---------- Constants ---------- //
+
+// Maximum number of clients in all lists by default
+#define DEFAULT_MAX_NB_CLIENTS 512
+
+// Address hash size in bits for clients (between 0 and MAX_HASH_SIZE)
+#define DEFAULT_CL_HASH_SIZE 7
+
+// Allow "throttle - 1" queries in a row, then force a throttle to one every "decay time" seconds
+#define DEFAULT_FP_DECAY_TIME	3
+#define DEFAULT_FP_THROTTLE		5
+
+
+// ---------- Public variables ---------- //
+
+// Enable/disabled the flood protection mechanism against abusive client requests
+extern qboolean flood_protection;
+
+
+// ---------- Public functions ---------- //
+
+// Will simply return "false" if called after Sv_Init
+qboolean Cl_SetHashSize (unsigned int size);
+qboolean Cl_SetMaxNbClients (unsigned int nb);
+qboolean Cl_SetFPDecayTime (time_t decay);
+qboolean Cl_SetFPThrottle (unsigned int throttle);
+
+// Initialize the client list and hash tables
+qboolean Cl_Init( void );
+
+// Return "true" if a client should be temporary ignored because he has sent too many requests recently
+qboolean Cl_BlockedByThrottle( const struct sockaddr_storage* addr, socklen_t addrlen );
+
+
+#endif  // #ifndef _CLIENTS_H_

Modified: trunk/dpmaster/src/common.c
===================================================================
--- trunk/dpmaster/src/common.c	2010-08-31 10:53:54 UTC (rev 10432)
+++ trunk/dpmaster/src/common.c	2010-09-01 22:58:12 UTC (rev 10433)
@@ -56,7 +56,10 @@
 // Should we print the date before any new console message?
 qboolean print_date = false;
 
+// Are port numbers used when computing address hashes?
+qboolean hash_ports = false;
 
+
 // ---------- Private functions ---------- //
 
 /*
@@ -212,6 +215,101 @@
 }
 
 
+// ---------- Private functions (user hash table) ---------- //
+
+/*
+====================
+Com_UserHashTable_Init
+
+Initialize a user hash table
+====================
+*/
+static qboolean Com_UserHashTable_Init (user_hash_table_t* table,
+										size_t hash_size,
+										const char* table_name,
+										sa_family_t addr_family,
+										const char* proto_name)
+{
+	if (Sys_IsListeningOn (addr_family))
+	{
+		size_t array_size = (1 << hash_size) * sizeof (user_t*);
+		user_t** result;
+
+		result = malloc (array_size);
+		if (result == NULL)
+		{
+			Com_Printf (MSG_ERROR,
+						"> ERROR: can't allocate the %s %s hash table (%s)\n",
+						table_name, proto_name, strerror (errno));
+			return false;
+		}
+
+		memset (result, 0, array_size);
+		table->entries = result;
+
+		Com_Printf (MSG_DEBUG,
+					"> %s hash table allocated for %s (%u entries)\n",
+					table_name, proto_name, 1 << hash_size);
+	}
+
+	return true;
+}
+
+
+// ---------- Public functions (user hash table) ---------- //
+
+/*
+====================
+Com_UserHashTable_InitTables
+
+Initialize user hash tables
+====================
+*/
+qboolean Com_UserHashTable_InitTables (user_hash_table_t* ipv4_table,
+									   user_hash_table_t* ipv6_table,
+									   size_t hash_size,
+									   const char* tables_name)
+{
+	return (Com_UserHashTable_Init (ipv4_table, hash_size, tables_name, AF_INET, "IPv4") &&
+			Com_UserHashTable_Init (ipv6_table, hash_size, tables_name, AF_INET6, "IPv6"));
+}
+
+
+/*
+====================
+Com_UserHashTable_Add
+
+Add a user to the hash table
+====================
+*/
+void Com_UserHashTable_Add (user_hash_table_t* table, user_t* user, unsigned int hash)
+{
+	user_t** hash_entry_ptr;
+
+	hash_entry_ptr = &table->entries[hash];
+	user->next = *hash_entry_ptr;
+	user->prev_ptr = hash_entry_ptr;
+	*hash_entry_ptr = user;
+	if (user->next != NULL)
+		user->next->prev_ptr = &user->next;
+}
+
+
+/*
+====================
+Com_UserHashTable_Remove
+
+Remove a user from its hash table
+====================
+*/
+void Com_UserHashTable_Remove (user_t* user)
+{
+	*user->prev_ptr = user->next;
+	if (user->next != NULL)
+		user->next->prev_ptr = user->prev_ptr;	
+}
+
+
 // ---------- Public functions (misc) ---------- //
 
 /*
@@ -288,3 +386,130 @@
 			break;
 	}
 }
+
+
+/*
+====================
+Com_AddressHash
+
+Compute the hash of a server address
+====================
+*/
+unsigned int Com_AddressHash (const struct sockaddr_storage* address, size_t hash_size)
+{
+	unsigned int hash;
+
+	if (address->ss_family == AF_INET6)
+	{
+		const struct sockaddr_in6* addr6;
+		const unsigned int* ipv6_ptr;
+
+		addr6 = (const struct sockaddr_in6*)address;
+		ipv6_ptr = (const unsigned int*)&addr6->sin6_addr.s6_addr;
+		
+		// Since an IPv6 device can have multiple addresses, we only hash
+		// the non-configurable part of its public address (meaning the first
+		// 64 bits, or subnet part)
+		hash = ipv6_ptr[0] ^ ipv6_ptr[1];
+		
+		if (hash_ports)
+			hash ^= addr6->sin6_port;
+	}
+	else
+	{
+		const struct sockaddr_in* addr4;
+
+		assert(address->ss_family == AF_INET);
+
+		addr4 = (const struct sockaddr_in*)address;
+		hash = addr4->sin_addr.s_addr;
+		
+		if (hash_ports)
+			hash ^= addr4->sin_port;
+	}
+
+	// Merge all the bits in the first 16 bits
+	hash = (hash & 0xFFFF) ^ (hash >> 16);
+	
+	// Merge the bits we won't use in the upper part into the lower part.
+	// If hash_size < 8, some bits will be lost, but it's not a real problem
+	hash = (hash ^ (hash >> hash_size)) & ((1 << hash_size) - 1);
+
+	return hash;
+}
+
+
+/*
+====================
+Com_SameIPv4Addr
+
+Compare 2 IPv4 addresses and return "true" if they're equal
+====================
+*/
+qboolean Com_SameIPv4Addr (const struct sockaddr_storage* addr1,
+						   const struct sockaddr_storage* addr2,
+						   qboolean* same_public_address)
+{
+	const struct sockaddr_in *addr1_in, *addr2_in;
+
+	assert (addr1->ss_family == AF_INET);
+	assert (addr2->ss_family == AF_INET);
+
+	addr1_in = (const struct sockaddr_in*)addr1;
+	addr2_in = (const struct sockaddr_in*)addr2;
+
+	// Same address?
+	if (addr1_in->sin_addr.s_addr == addr2_in->sin_addr.s_addr)
+	{
+		*same_public_address = true;
+
+		// Same port?
+		if (addr1_in->sin_port == addr2_in->sin_port)
+			return true;
+	}
+	else
+		*same_public_address = false;
+
+	return false;
+}
+
+
+/*
+====================
+Com_SameIPv6Addr
+
+Compare 2 IPv6 addresses and return "true" if they're equal
+====================
+*/
+qboolean Com_SameIPv6Addr (const struct sockaddr_storage* addr1,
+						   const struct sockaddr_storage* addr2,
+						   qboolean* same_public_address)
+{
+	const struct sockaddr_in6 *addr1_in6, *addr2_in6;
+	const unsigned char *addr1_buff, *addr2_buff;
+
+	assert (addr1->ss_family == AF_INET6);
+	assert (addr2->ss_family == AF_INET6);
+
+	addr1_in6 = (const struct sockaddr_in6*)addr1;
+	addr1_buff = (const unsigned char*)&addr1_in6->sin6_addr.s6_addr;
+
+	addr2_in6 = (const struct sockaddr_in6*)addr2;
+	addr2_buff = (const unsigned char*)&addr2_in6->sin6_addr.s6_addr;
+
+	// Same subnet address (first 64 bits)?
+	if (memcmp (addr1_buff, addr2_buff, 8) == 0)
+	{
+		*same_public_address = true;
+
+		// Same scope ID, port, and host address (last 64 bits)?
+		if (addr1_in6->sin6_scope_id == addr2_in6->sin6_scope_id &&
+			addr1_in6->sin6_port == addr2_in6->sin6_port &&
+			memcmp (addr1_buff + 8, addr2_buff + 8, 8) == 0)
+			return true;
+	}
+	else
+		*same_public_address = false;
+
+	return false;
+}

Modified: trunk/dpmaster/src/common.h
===================================================================
--- trunk/dpmaster/src/common.h	2010-08-31 10:53:54 UTC (rev 10432)
+++ trunk/dpmaster/src/common.h	2010-09-01 22:58:12 UTC (rev 10433)
@@ -38,13 +38,29 @@
 #include <time.h>
 
 
+#ifdef WIN32
+#	include <winsock2.h>
+#	include <ws2tcpip.h>
+#else
+#	include <pwd.h>
+#	include <unistd.h>
+#	include <netinet/in.h>
+#	include <arpa/inet.h>
+#	include <netdb.h>
+#	include <sys/socket.h>
+#endif
+
+
 // ---------- Constants ---------- //
 
 // Maximum and minimum sizes for a valid incoming packet
 #define MAX_PACKET_SIZE_IN 2048
 #define MIN_PACKET_SIZE_IN 5
 
+// Maximum address hash size in bits
+#define MAX_HASH_SIZE 16
 
+
 // ---------- Types ---------- //
 
 // A few basic types
@@ -88,7 +104,22 @@
 	CMDLINE_STATUS_NOT_ENOUGH_MEMORY,
 } cmdline_status_t;
 
+// User (client or server)
+typedef struct user_s
+{
+	struct sockaddr_storage address;
+	socklen_t addrlen;
+	struct user_s* next;
+	struct user_s** prev_ptr;
+} user_t;
 
+// Hash table for users
+typedef struct user_hash_table_s
+{
+	user_t** entries;
+} user_hash_table_t;
+
+
 // ---------- Public variables ---------- //
 
 // The current time (updated every time we receive a packet)
@@ -103,7 +134,25 @@
 // Should we print the date before any new console message?
 extern qboolean print_date;
 
+// Are port numbers used when computing address hashes?
+extern qboolean hash_ports;
 
+
+// ---------- Public functions (user hash table) ---------- //
+
+// Initialize user hash tables
+qboolean Com_UserHashTable_InitTables (user_hash_table_t* ipv4_table,
+									   user_hash_table_t* ipv6_table,
+									   size_t hash_size,
+									   const char* tables_name);
+
+// Add a user to the hash table
+void Com_UserHashTable_Add (user_hash_table_t* table, user_t* user, unsigned int hash);
+
+// Remove a user from its hash table
+void Com_UserHashTable_Remove (user_t* user);
+
+
 // ---------- Public functions (logging) ---------- //
 
 // Enable the logging
@@ -130,5 +179,14 @@
 // Handling of the signals sent to this process
 void Com_SignalHandler (int Signal);
 
+// Compute the hash of a server address
+unsigned int Com_AddressHash (const struct sockaddr_storage* address, size_t hash_size);
 
+// Compare 2 IPv4 addresses and return "true" if they're equal
+qboolean Com_SameIPv4Addr (const struct sockaddr_storage* addr1, const struct sockaddr_storage* addr2, qboolean* same_public_address);
+
+// Compare 2 IPv6 addresses and return "true" if they're equal
+qboolean Com_SameIPv6Addr (const struct sockaddr_storage* addr1, const struct sockaddr_storage* addr2, qboolean* same_public_address);
+
+
 #endif  // #ifndef _COMMON_H_

Modified: trunk/dpmaster/src/dpmaster.c
===================================================================
--- trunk/dpmaster/src/dpmaster.c	2010-08-31 10:53:54 UTC (rev 10432)
+++ trunk/dpmaster/src/dpmaster.c	2010-09-01 22:58:12 UTC (rev 10433)
@@ -24,6 +24,8 @@
 
 #include "common.h"
 #include "system.h"
+
+#include "clients.h"
 #include "games.h"
 #include "messages.h"
 #include "servers.h"
@@ -51,6 +53,42 @@
 		0
 	},
 	{
+		"cl-hash-size",
+		"<hash_size>",
+		"Hash size used for clients, in bits, up to %d (default: %d)",
+		{ MAX_HASH_SIZE, DEFAULT_CL_HASH_SIZE },
+		'\0',
+		1,
+		1
+	},
+	{
+		"flood-protection",
+		NULL,
+		"Enable the flood protection against abusive client requests",
+		{ 0, 0 },
+		'f',
+		0,
+		0
+	},
+	{
+		"fp-decay-time",
+		"<decay_time>",
+		"Set the decay time of the flood protection, in seconds (default: %d)",
+		{ DEFAULT_FP_DECAY_TIME, 0 },
+		'\0',
+		0,
+		0
+	},
+	{
+		"fp-throttle",
+		"<throttle_limit>",
+		"Set the throttle limit of the flood protection (default: %d)",
+		{ DEFAULT_FP_THROTTLE, 0 },
+		'\0',
+		0,
+		0
+	},
+	{
 		"game-properties",
 		"[game_name <property> ...]",
 		"Without parameter, print the list of all known games and their properties.\n"
@@ -83,7 +121,7 @@
 	{
 		"hash-ports",
 		NULL,
-		"Use both a server's address and port number when computing its hash value.\n"
+		"Use both an host's address and port number when computing its hash value.\n"
 		"   The check for a maximum number of servers per address won't work correctly.\n"
 		"   FOR DEBUGGING PURPOSES ONLY!",
 		{ 0, 0 },
@@ -94,8 +132,8 @@
 	{
 		"hash-size",
 		"<hash_size>",
-		"Hash size in bits, up to %d (default: %d)",
-		{ MAX_HASH_SIZE, DEFAULT_HASH_SIZE },
+		"Hash size used for servers in bits, up to %d (default: %d)",
+		{ MAX_HASH_SIZE, DEFAULT_SV_HASH_SIZE },
 		'H',
 		1,
 		1
@@ -139,6 +177,15 @@
 		1
 	},
 	{
+		"max-clients",
+		"<max_clients>",
+		"Maximum number of clients recorded (default: %d)",
+		{ DEFAULT_MAX_NB_CLIENTS, 0 },
+		'\0',
+		1,
+		1
+	},
+	{
 		"max-servers",
 		"<max_servers>",
 		"Maximum number of servers recorded (default: %d)",
@@ -285,6 +332,58 @@
 	if (strcmp (opt_name, "allow-loopback") == 0)
 		allow_loopback = true;
 
+	// Flood protection
+	else if (strcmp (opt_name, "flood-protection") == 0)
+		flood_protection = true;
+
+	// Flood protection decay time
+	else if (strcmp (opt_name, "fp-decay-time") == 0)
+	{
+		const char* start_ptr;
+		char* end_ptr;
+		unsigned int decay_time;
+
+		start_ptr = params[0];
+		decay_time = (unsigned int)strtol (start_ptr, &end_ptr, 0);
+		if (end_ptr == start_ptr || *end_ptr != '\0')
+			return CMDLINE_STATUS_INVALID_OPT_PARAMS;
+
+		if (! Cl_SetFPDecayTime (decay_time))
+			return CMDLINE_STATUS_INVALID_OPT_PARAMS;
+	}
+
+	// Flood protection throttle limit
+	else if (strcmp (opt_name, "fp-throttle") == 0)
+	{
+		const char* start_ptr;
+		char* end_ptr;
+		unsigned int throttle;
+
+		start_ptr = params[0];
+		throttle = (unsigned int)strtol (start_ptr, &end_ptr, 0);
+		if (end_ptr == start_ptr || *end_ptr != '\0')
+			return CMDLINE_STATUS_INVALID_OPT_PARAMS;
+
+		if (! Cl_SetFPThrottle (throttle))
+			return CMDLINE_STATUS_INVALID_OPT_PARAMS;
+	}
+
+	// Client hash size
+	else if (strcmp (opt_name, "cl-hash-size") == 0)
+	{
+		const char* start_ptr;
+		char* end_ptr;
+		unsigned int hash_size;
+
+		start_ptr = params[0];
+		hash_size = (unsigned int)strtol (start_ptr, &end_ptr, 0);
+		if (end_ptr == start_ptr || *end_ptr != '\0')
+			return CMDLINE_STATUS_INVALID_OPT_PARAMS;
+
+		if (! Cl_SetHashSize (hash_size))
+			return CMDLINE_STATUS_INVALID_OPT_PARAMS;
+	}
+
 	// Game properties
 	else if (strcmp (opt_name, "game-properties") == 0)
 	{
@@ -308,7 +407,7 @@
 	else if (strcmp (opt_name, "hash-ports") == 0)
 		hash_ports = true;
 
-	// Hash size
+	// Server hash size
 	else if (strcmp (opt_name, "hash-size") == 0)
 	{
 		const char* start_ptr;
@@ -355,6 +454,22 @@
 			return CMDLINE_STATUS_INVALID_OPT_PARAMS;
 	}
 
+	// Maximum number of clients
+	else if (strcmp (opt_name, "max-clients") == 0)
+	{
+		const char* start_ptr;
+		char* end_ptr;
+		unsigned int max_nb_clients;
+
+		start_ptr = params[0];
+		max_nb_clients = (unsigned int)strtol (start_ptr, &end_ptr, 0);
+		if (end_ptr == start_ptr || *end_ptr != '\0')
+			return CMDLINE_STATUS_INVALID_OPT_PARAMS;
+		
+		if (! Cl_SetMaxNbClients (max_nb_clients))
+			return CMDLINE_STATUS_INVALID_OPT_PARAMS;
+	}
+
 	// Maximum number of servers
 	else if (strcmp (opt_name, "max-servers") == 0)
 	{
@@ -811,6 +926,10 @@
 	if (! Sv_Init ())
 		return false;
 
+	// Initialize the client list and hash table (query rate throttling)
+	if (! Cl_Init ())
+		return false;
+
 	return true;
 }
 

Modified: trunk/dpmaster/src/dpmaster.vcproj
===================================================================
--- trunk/dpmaster/src/dpmaster.vcproj	2010-08-31 10:53:54 UTC (rev 10432)
+++ trunk/dpmaster/src/dpmaster.vcproj	2010-09-01 22:58:12 UTC (rev 10433)
@@ -177,6 +177,10 @@
 			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
 			>
 			<File
+				RelativePath=".\clients.c"
+				>
+			</File>
+			<File
 				RelativePath=".\common.c"
 				>
 			</File>
@@ -207,6 +211,10 @@
 			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
 			>
 			<File
+				RelativePath=".\clients.h"
+				>
+			</File>
+			<File
 				RelativePath=".\common.h"
 				>
 			</File>

Modified: trunk/dpmaster/src/messages.c
===================================================================
--- trunk/dpmaster/src/messages.c	2010-08-31 10:53:54 UTC (rev 10432)
+++ trunk/dpmaster/src/messages.c	2010-09-01 22:58:12 UTC (rev 10433)
@@ -23,6 +23,8 @@
 
 #include "common.h"
 #include "system.h"
+
+#include "clients.h"
 #include "games.h"
 #include "messages.h"
 #include "servers.h"
@@ -71,7 +73,6 @@
 #define M2C_GETSERVERSEXTREPONSE "getserversExtResponse"
 
 
-
 // ---------- Private functions ---------- //
 
 /*
@@ -130,7 +131,7 @@
 			str_buffer[buffer_ind++] = c;
 		}
 
-		// If it's the key we are looking for, save it in "value"
+		// If it's the key we are looking for, save its value in "str_buffer"
 		if (!strcmp (str_buffer, key))
 		{
 			buffer_ind = 0;
@@ -224,8 +225,8 @@
 	strncpy (msg + msglen, server->challenge, sizeof (msg) - msglen - 1);
 	msg[sizeof (msg) - 1] = '\0';
 	if (sendto (recv_socket, msg, strlen (msg), 0,
-				(const struct sockaddr*)&server->address,
-				server->addrlen) < 0)
+				(const struct sockaddr*)&server->user.address,
+				server->user.addrlen) < 0)
 		Com_Printf (MSG_WARNING, "> WARNING: can't send getinfo (%s)\n",
 					Sys_GetLastNetErrorString ());
 	else
@@ -334,6 +335,9 @@
 	unsigned int nb_servers;
 	const char* request_name;
 
+	if (Cl_BlockedByThrottle (addr, addrlen))
+		return;
+
 	if (extended_request)
 	{
 		request_name = "getserversExt";
@@ -491,7 +495,7 @@
 
 	// Add every relevant server
 	nb_servers = 0;
-	for (sv = Sv_GetFirst (); sv != NULL;  sv = Sv_GetNext ())
+	for (sv = Sv_GetFirst (); sv != NULL; sv = Sv_GetNext ())
 	{
 		size_t next_sv_size;
 
@@ -500,7 +504,7 @@
 		// Extra debugging info
 		if (max_msg_level >= MSG_DEBUG)
 		{
-			const char * addrstr = Sys_SockaddrToString (&sv->address, sv->addrlen);
+			const char * addrstr = Sys_SockaddrToString (&sv->user.address, sv->user.addrlen);
 			Com_Printf (MSG_DEBUG,
 						"  - Comparing server: IP:\"%s\", p:%d, g:\"%s\"\n",
 						addrstr, sv->protocol, sv->gamename);
@@ -508,26 +512,39 @@
 			if (sv->state <= sv_state_uninitialized)
 				Com_Printf (MSG_DEBUG,
 							"    Reject: server is not initialized\n");
-			if (sv->protocol != protocol)
+			else if (sv->protocol != protocol)
 				Com_Printf (MSG_DEBUG,
 							"    Reject: protocol %d != requested %d\n",
 							sv->protocol, protocol);
-			if (! opt_empty && sv->state == sv_state_empty)
+			else if (! opt_empty && sv->state == sv_state_empty)
 				Com_Printf (MSG_DEBUG, "    Reject: no empty server allowed\n");
-			if (! opt_full && sv->state == sv_state_full)
+			else if (! opt_full && sv->state == sv_state_full)
 				Com_Printf (MSG_DEBUG, "    Reject: no full server allowed\n");
-			if (! opt_ipv4 && sv->address.ss_family == AF_INET)
+			else if (! opt_ipv4 && sv->user.address.ss_family == AF_INET)
 				Com_Printf (MSG_DEBUG, "    Reject: no IPv4 servers allowed\n");
-			if (! opt_ipv6 && sv->address.ss_family == AF_INET6)
+			else if (! opt_ipv6 && sv->user.address.ss_family == AF_INET6)
 				Com_Printf (MSG_DEBUG, "    Reject: no IPv6 servers allowed\n");
-			if (opt_gametype && strcmp (gametype, sv->gametype) != 0)
+			else if (opt_gametype && strcmp (gametype, sv->gametype) != 0)
 				Com_Printf (MSG_DEBUG,
 							"    Reject: gametype \"%s\" != requested \"%s\"\n",
 							sv->gametype, gametype);
-			if (gamename[0] != '\0' && strcmp (gamename, sv->gamename) != 0)
-				Com_Printf (MSG_DEBUG,
-							"    Reject: gamename \"%s\" != requested \"%s\"\n",
-							sv->gamename, gamename);
+			else
+			{
+				if (gamename[0] != '\0')
+				{
+					if (strcmp (gamename, sv->gamename) != 0)
+						Com_Printf (MSG_DEBUG,
+									"    Reject: gamename \"%s\" != requested \"%s\"\n",
+									sv->gamename, gamename);
+				}
+				else
+				{
+					if (sv->anon_properties == NULL)
+						Com_Printf (MSG_DEBUG,
+									"    Reject: can't use \"%s\" as an anonymous game name\n",
+									sv->gamename);
+				}
+			}
 		}
 
 		// Check state and protocol
@@ -561,8 +578,8 @@
 		// Check options, game type and game name
 		if ((! opt_empty && sv->state == sv_state_empty) ||
 			(! opt_full && sv->state == sv_state_full) ||
-			(! opt_ipv4 && sv->address.ss_family == AF_INET) ||
-			(! opt_ipv6 && sv->address.ss_family == AF_INET6) ||
+			(! opt_ipv4 && sv->user.address.ss_family == AF_INET) ||
+			(! opt_ipv6 && sv->user.address.ss_family == AF_INET6) ||
 			(opt_gametype && strcmp (gametype, sv->gametype) != 0) ||
 			strcmp (gamename, sv->gamename) != 0)
 		{
@@ -571,7 +588,7 @@
 		}
 
 		// If the packet doesn't have enough free space for this server
-		next_sv_size = (sv->address.ss_family == AF_INET ? 4 : 16) + 3;
+		next_sv_size = (sv->user.address.ss_family == AF_INET ? 4 : 16) + 3;
 		if (packetind + next_sv_size > sizeof (packet))
 		{
 			// Send the packet to the client
@@ -588,13 +605,13 @@
 			nb_servers = 0;
 		}
 
-		if (sv->address.ss_family == AF_INET)
+		if (sv->user.address.ss_family == AF_INET)
 		{
 			const struct sockaddr_in* sv_sockaddr;
 			unsigned int sv_addr;
 			unsigned short sv_port;
 
-			sv_sockaddr = (const struct sockaddr_in *)&sv->address;
+			sv_sockaddr = (const struct sockaddr_in *)&sv->user.address;
 			sv_addr = ntohl (sv_sockaddr->sin_addr.s_addr);
 			sv_port = ntohs (sv_sockaddr->sin_port);
 
@@ -610,7 +627,7 @@
 				Com_Printf (MSG_DEBUG,
 							"  - Using mapped address %u.%u.%u.%u:%hu\n",
 							sv_addr >> 24, (sv_addr >> 16) & 0xFF,
-							(sv_addr >>  8) & 0xFF, sv_addr & 0xFF,
+							(sv_addr >> 8) & 0xFF, sv_addr & 0xFF,
 							sv_port);
 			}
 
@@ -639,7 +656,7 @@
 			const struct sockaddr_in6* sv_sockaddr6;
 			unsigned short sv_port;
 
-			sv_sockaddr6 = (const struct sockaddr_in6 *)&sv->address;
+			sv_sockaddr6 = (const struct sockaddr_in6 *)&sv->user.address;
 
 			// Heading '/'
 			packet[packetind] = '/';

Modified: trunk/dpmaster/src/servers.c
===================================================================
--- trunk/dpmaster/src/servers.c	2010-08-31 10:53:54 UTC (rev 10432)
+++ trunk/dpmaster/src/servers.c	2010-09-01 22:58:12 UTC (rev 10433)
@@ -40,9 +40,9 @@
 static server_t* servers = NULL;
 static unsigned int max_nb_servers = DEFAULT_MAX_NB_SERVERS;
 static unsigned int nb_servers = 0;
-static server_t** hash_table_ipv4 = NULL;
-static server_t** hash_table_ipv6 = NULL;
-static size_t hash_size = DEFAULT_HASH_SIZE;
+static user_hash_table_t hash_table_ipv4;
+static user_hash_table_t hash_table_ipv6;
+static size_t sv_hash_size = DEFAULT_SV_HASH_SIZE;
 
 static unsigned int max_per_address = DEFAULT_MAX_NB_SERVERS_PER_ADDRESS;
 
@@ -63,102 +63,11 @@
 // Are servers talking from a loopback interface allowed?
 qboolean allow_loopback = false;
 
-// Are port numbers used when computing servers hashes?
-qboolean hash_ports = false;
 
-
 // ---------- Private functions ---------- //
 
 /*
 ====================
-Sv_AddressHash
-
-Compute the hash of a server address
-====================
-*/
-static unsigned int Sv_AddressHash (const struct sockaddr_storage* address)
-{
-	unsigned int hash;
-
-	if (address->ss_family == AF_INET6)
-	{
-		const struct sockaddr_in6* addr6;
-		const unsigned int* ipv6_ptr;
-
-		addr6 = (const struct sockaddr_in6*)address;
-		ipv6_ptr = (const unsigned int*)&addr6->sin6_addr.s6_addr;
-		
-		// Since an IPv6 device can have multiple addresses, we only hash
-		// the non-configurable part of its public address (meaning the first
-		// 64 bits, or subnet part)
-		hash = ipv6_ptr[0] ^ ipv6_ptr[1];
-		
-		if (hash_ports)
-			hash ^= addr6->sin6_port;
-	}
-	else
-	{
-		const struct sockaddr_in* addr4;
-
-		assert(address->ss_family == AF_INET);
-
-		addr4 = (const struct sockaddr_in*)address;
-		hash = addr4->sin_addr.s_addr;
-		
-		if (hash_ports)
-			hash ^= addr4->sin_port;
-	}
-
-	// Merge all the bits in the first 16 bits
-	hash = (hash & 0xFFFF) ^ (hash >> 16);
-	
-	// Merge the bits we won't use in the upper part into the lower part.
-	// If hash_size < 8, some bits will be lost, but it's not a real problem
-	hash = (hash ^ (hash >> hash_size)) & ((1 << hash_size) - 1);
-
-	return hash;
-}
-
-
-/*
-====================
-Sv_AddToHashTable
-
-Add a server to the hash table
-====================
-*/
-static void Sv_AddToHashTable (server_t* sv, unsigned int hash, server_t** hash_table)
-{
-	server_t** hash_entry_ptr;
-
-	assert (hash == Sv_AddressHash (&sv->address));
-
-	hash_entry_ptr = &hash_table[hash];
-	sv->next = *hash_entry_ptr;
-	sv->prev_ptr = hash_entry_ptr;
-	*hash_entry_ptr = sv;
-	if (sv->next != NULL)
-		sv->next->prev_ptr = &sv->next;
-}
-
-
-/*
-====================
-Sv_RemoveFromHashTable
-
-Remove a server from the hash table
-====================
-*/
-static void Sv_RemoveFromHashTable (server_t* sv)
-{
-	*sv->prev_ptr = sv->next;
-	if (sv->next != NULL)
-		sv->next->prev_ptr = sv->prev_ptr;	
-}
-
-
-/*
-====================
 Sv_Remove
 
 Remove a server from the lists
@@ -168,7 +77,7 @@
 {
 	int sv_ind;
 
-	Sv_RemoveFromHashTable (sv);
+	Com_UserHashTable_Remove (&sv->user);
 
 	// Mark this structure as "free"
 	sv->state = sv_state_unused_slot;
@@ -198,7 +107,7 @@
 	nb_servers--;
 	Com_Printf (MSG_NORMAL,
 				"> %s timed out; %u server(s) currently registered\n",
-				Sys_SockaddrToString(&sv->address, sv->addrlen), nb_servers);
+				Sys_SockaddrToString(&sv->user.address, sv->user.addrlen), nb_servers);
 
 	assert (last_used_slot >= (int)nb_servers - 1);
 }
@@ -206,35 +115,6 @@
 
 /*
 ====================
-Sv_AllocateHashTable
-
-Allocate a hash table
-====================
-*/
-static server_t** Sv_AllocateHashTable (size_t table_size, const char* proto_name)
-{
-	server_t** result;
-	size_t array_size = table_size * sizeof (server_t*);
-
-	result = malloc (array_size);
-	if (result != NULL)
-	{
-		memset (result, 0, array_size);
-		Com_Printf (MSG_DEBUG,
-					"> %s hash table allocated (%u entries)\n",
-					proto_name, table_size);
-	}
-	else
-		Com_Printf (MSG_ERROR,
-					"> ERROR: can't allocate the %s hash table (%s)\n",
-					proto_name, strerror (errno));
-	
-	return result;
-}
-
-
-/*
-====================
 Sv_IsActive
 
 Return true if a server is active.
@@ -266,76 +146,6 @@
 
 /*
 ====================
-Sv_CompareIPv4Addr
-
-Compare 2 IPv4 addresses and return "true" if they're equal
-====================
-*/
-static qboolean Sv_SameIPv4Addr (const struct sockaddr_storage* addr1,
-								 const struct sockaddr_storage* addr2,
-								 qboolean* same_public_address)
-{
-	const struct sockaddr_in *addr1_in, *addr2_in;
-
-	addr1_in = (const struct sockaddr_in*)addr1;
-	addr2_in = (const struct sockaddr_in*)addr2;
-
-	// Same address?
-	if (addr1_in->sin_addr.s_addr == addr2_in->sin_addr.s_addr)
-	{
-		*same_public_address = true;
-
-		// Same port?
-		if (addr1_in->sin_port == addr2_in->sin_port)
-			return true;
-	}
-	else
-		*same_public_address = false;
-
-	return false;
-}
-
-
-/*
-====================
-Sv_CompareIPv6Addr
-
-Compare 2 IPv6 addresses and return "true" if they're equal
-====================
-*/
-static qboolean Sv_SameIPv6Addr (const struct sockaddr_storage* addr1,
-								 const struct sockaddr_storage* addr2,
-								 qboolean* same_public_address)
-{
-	const struct sockaddr_in6 *addr1_in6, *addr2_in6;
-	const unsigned char *addr1_buff, *addr2_buff;
-
-	addr1_in6 = (const struct sockaddr_in6*)addr1;
-	addr1_buff = (const unsigned char*)&addr1_in6->sin6_addr.s6_addr;
-
-	addr2_in6 = (const struct sockaddr_in6*)addr2;
-	addr2_buff = (const unsigned char*)&addr2_in6->sin6_addr.s6_addr;
-
-	// Same subnet address (first 64 bits)?
-	if (memcmp (addr1_buff, addr2_buff, 8) == 0)
-	{
-		*same_public_address = true;
-
-		// Same scope ID, port, and host address (last 64 bits)?
-		if (addr1_in6->sin6_scope_id == addr2_in6->sin6_scope_id &&
-			addr1_in6->sin6_port == addr2_in6->sin6_port &&
-			memcmp (addr1_buff + 8, addr2_buff + 8, 8) == 0)
-			return true;
-	}
-	else
-		*same_public_address = false;
-
-	return false;
-}
-
-
-/*
-====================
 Sv_GetByAddr_Internal
 
 Search for a particular server in the list
@@ -343,28 +153,28 @@
 */
 static server_t* Sv_GetByAddr_Internal (const struct sockaddr_storage* address, unsigned int* same_address_found)
 {
-	unsigned int hash = Sv_AddressHash (address);
-	server_t** hash_table;
+	unsigned int hash = Com_AddressHash (address, sv_hash_size);
+	user_hash_table_t* hash_table;
 	server_t* sv;
 	qboolean (*IsSameAddress) (const struct sockaddr_storage* addr1, const struct sockaddr_storage* addr2, qboolean* same_public_address);
 	
 	if (address->ss_family == AF_INET6)
 	{
-		hash_table = hash_table_ipv6;
-		IsSameAddress = Sv_SameIPv6Addr;
+		hash_table = &hash_table_ipv6;
+		IsSameAddress = &Com_SameIPv6Addr;
 	}
 	else
 	{
 		assert (address->ss_family == AF_INET);
-		hash_table = hash_table_ipv4;
-		IsSameAddress = Sv_SameIPv4Addr;
+		hash_table = &hash_table_ipv4;
+		IsSameAddress = &Com_SameIPv4Addr;
 	}
-	sv = hash_table[hash];
+	sv = (server_t*)hash_table->entries[hash];
 
 	*same_address_found = 0;
 	while (sv != NULL)
 	{
-		server_t* next_sv = sv->next;
+		server_t* next_sv = (server_t*)sv->user.next;
 		unsigned int sv_ind = (unsigned int)(sv - servers);
 
 		if (Sv_IsActive (sv_ind))
@@ -374,15 +184,15 @@
 			qboolean same_address;
 
 			same_public_address = false;
-			same_address = IsSameAddress (&sv->address, address, &same_public_address);
+			same_address = IsSameAddress (&sv->user.address, address, &same_public_address);
 			if (same_public_address)
 				*same_address_found += 1;
 			if (same_address)
 			{
 				// Move it on top of the list (it's useful because heartbeats
 				// are almost always followed by infoResponses)
-				Sv_RemoveFromHashTable (sv);
-				Sv_AddToHashTable (sv, hash, hash_table);
+				Com_UserHashTable_Remove (&sv->user);
+				Com_UserHashTable_Add (hash_table, &sv->user, hash);
 
 				return sv;
 			}
@@ -623,12 +433,11 @@
 */
 qboolean Sv_SetHashSize (unsigned int size)
 {
-	// Too late? Too small or too big?
-	if (hash_table_ipv4 != NULL || hash_table_ipv6 != NULL ||
-		size > MAX_HASH_SIZE)
+	// Too late? Or too big?
+	if (servers != NULL || size > MAX_HASH_SIZE)
 		return false;
 
-	hash_size = size;
+	sv_hash_size = size;
 	return true;
 }
 
@@ -673,12 +482,11 @@
 ====================
 Sv_Init
 
-Initialize the server list and hash table
+Initialize the server list and hash tables
 ====================
 */
 qboolean Sv_Init (void)
 {
-	unsigned int hash_table_size;
 	size_t array_size;
 
 	// Allocate "servers" and clean it
@@ -700,20 +508,8 @@
 	else
 		Com_Printf (MSG_NORMAL, "%u)\n", max_per_address);
 
-	// Allocate the hash tables and clean them
-	hash_table_size = (1 << hash_size);
-	if (Sys_IsListeningOn (AF_INET))
-	{
-		hash_table_ipv4 = Sv_AllocateHashTable (hash_table_size, "IPv4");
-		if (hash_table_ipv4 == NULL)
-			return false;
-	}
-	if (Sys_IsListeningOn (AF_INET6))
-	{
-		hash_table_ipv6 = Sv_AllocateHashTable (hash_table_size, "IPv6");
-		if (hash_table_ipv6 == NULL)
-			return false;
-	}
+	if (! Com_UserHashTable_InitTables (&hash_table_ipv4, &hash_table_ipv6, sv_hash_size, "Server"))
+		return false;
 
 	return true;
 }
@@ -733,12 +529,12 @@
 	const addrmap_t* addrmap = NULL;
 	unsigned int hash;
 	unsigned int ind;
-	server_t** hash_table;
+	user_hash_table_t* hash_table;
 
 	sv = Sv_GetByAddr_Internal (address, &nb_same_address);
 	if (sv != NULL)
 	{
-		assert (addrlen == sv->addrlen);
+		assert (addrlen == sv->user.addrlen);
 		return sv;
 	}
 
@@ -829,17 +625,17 @@
 
 	// Initialize the structure
 	memset (sv, 0, sizeof (*sv));
-	memcpy (&sv->address, address, sizeof (sv->address));
-	sv->addrlen = addrlen;
+	memcpy (&sv->user.address, address, sizeof (sv->user.address));
+	sv->user.addrlen = addrlen;
 	sv->addrmap = addrmap;
 
 	// Add it to the list it belongs to
-	hash = Sv_AddressHash (address);
+	hash = Com_AddressHash (address, sv_hash_size);
 	if (address->ss_family == AF_INET6)
-		hash_table = hash_table_ipv6;
+		hash_table = &hash_table_ipv6;
 	else
-		hash_table = hash_table_ipv4;
-	Sv_AddToHashTable (sv, hash, hash_table);
+		hash_table = &hash_table_ipv4;
+	Com_UserHashTable_Add (hash_table, &sv->user, hash);
 
 	sv->state = sv_state_uninitialized;
 	sv->timeout = crt_time + TIMEOUT_HEARTBEAT;
@@ -932,7 +728,7 @@
 			const char* state_string;
 
 			Com_Printf (msg_level, " * %s",
-						Sys_SockaddrToString (&sv->address, sv->addrlen));
+						Sys_SockaddrToString (&sv->user.address, sv->user.addrlen));
 			if (sv->addrmap != NULL)
 				Com_Printf (msg_level, ", mapped to %s",
 							sv->addrmap->to_string);

Modified: trunk/dpmaster/src/servers.h
===================================================================
--- trunk/dpmaster/src/servers.h	2010-08-31 10:53:54 UTC (rev 10432)
+++ trunk/dpmaster/src/servers.h	2010-09-01 22:58:12 UTC (rev 10433)
@@ -33,9 +33,8 @@
 // Maximum number of servers for one given IP address by default
 #define DEFAULT_MAX_NB_SERVERS_PER_ADDRESS 32
 
-// Address hash size in bits (between 0 and MAX_HASH_SIZE)
-#define DEFAULT_HASH_SIZE 10
-#define MAX_HASH_SIZE 16
+// Address hash size in bits for servers (between 0 and MAX_HASH_SIZE)
+#define DEFAULT_SV_HASH_SIZE 10
 
 // Number of characters in a challenge, including the '\0'
 #define CHALLENGE_MIN_LENGTH 9
@@ -74,15 +73,12 @@
 struct game_properties_s;		// Defined in games.h
 typedef struct server_s
 {
-	struct sockaddr_storage address;
-	struct server_s* next;
-	struct server_s** prev_ptr;
+	user_t user;										// WARNING: MUST be the 1st member, for compatibility with the user hash tables
 	const struct addrmap_s* addrmap;
 	const struct game_properties_s* anon_properties;	// game properties, for an anonymous game
 	const struct game_properties_s* hb_properties;		// future "anon_properties", not yet validated by an infoResponse
 	time_t timeout;
 	time_t challenge_timeout;
-	socklen_t addrlen;
 	int protocol;
 	server_state_t state;
 	char challenge [CHALLENGE_MAX_LENGTH];
@@ -96,10 +92,7 @@
 // Are servers talking from a loopback interface allowed?
 extern qboolean allow_loopback;
 
-// Are port numbers used when computing servers hashes?
-extern qboolean hash_ports;
 
-
 // ---------- Public functions (servers) ---------- //
 
 // Will simply return "false" if called after Sv_Init
@@ -107,7 +100,7 @@
 qboolean Sv_SetMaxNbServers (unsigned int nb);
 qboolean Sv_SetMaxNbServersPerAddress (unsigned int nb);
 
-// Initialize the server list and hash table
+// Initialize the server list and hash tables
 qboolean Sv_Init (void);
 
 // Search for a particular server in the list; add it if necessary

Modified: trunk/dpmaster/src/system.h
===================================================================
--- trunk/dpmaster/src/system.h	2010-08-31 10:53:54 UTC (rev 10432)
+++ trunk/dpmaster/src/system.h	2010-09-01 22:58:12 UTC (rev 10433)
@@ -25,19 +25,6 @@
 #define _SYSTEM_H_
 
 
-#ifdef WIN32
-#	include <winsock2.h>
-#	include <ws2tcpip.h>
-#else
-#	include <pwd.h>
-#	include <unistd.h>
-#	include <netinet/in.h>
-#	include <arpa/inet.h>
-#	include <netdb.h>
-#	include <sys/socket.h>
-#endif
-
-
 // ---------- Contants ---------- //
 
 // The default name of the log file

Modified: trunk/dpmaster/testsuite/test-multiple_game_families.pl
===================================================================
--- trunk/dpmaster/testsuite/test-multiple_game_families.pl	2010-08-31 10:53:54 UTC (rev 10432)
+++ trunk/dpmaster/testsuite/test-multiple_game_families.pl	2010-09-01 22:58:12 UTC (rev 10433)
@@ -45,6 +45,7 @@
 	{
 		family => GAME_FAMILY_QUAKE3ARENA,
 		id => "Q3Server3",
+		protonum => 2,
 	},
 
 	# RTCW servers
@@ -71,9 +72,7 @@
 	# Create the server
 	my $serverRef = Server_New ($serverFamily);
 	Server_SetProperty ($serverRef, "id", $serverId);
-	if (defined $serverProtocol) {
-		Server_SetGameProperty ($serverRef, "protocol", $serverProtocol);
-	}
+	Server_SetGameProperty ($serverRef, "protocol", $serverProtocol);
 	if (defined $serverGame) {
 		Server_SetGameProperty ($serverRef, "gamename", $serverGame);
 	}



More information about the twilight-commits mailing list