summaryrefslogtreecommitdiff
path: root/src/server/sv_main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/sv_main.cpp')
-rw-r--r--src/server/sv_main.cpp1551
1 files changed, 1551 insertions, 0 deletions
diff --git a/src/server/sv_main.cpp b/src/server/sv_main.cpp
new file mode 100644
index 0000000..f5c3b98
--- /dev/null
+++ b/src/server/sv_main.cpp
@@ -0,0 +1,1551 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2012-2018 ET:Legacy team <mail@etlegacy.com>
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "server.h"
+
+#include <iostream>
+
+#ifdef USE_VOIP
+cvar_t *sv_voip;
+cvar_t *sv_voipProtocol;
+#endif
+
+serverStatic_t svs; // persistant server info
+server_t sv {}; // local server
+
+cvar_t *sv_fps = NULL; // time rate for running non-clients
+cvar_t *sv_timeout; // seconds without any message
+cvar_t *sv_zombietime; // seconds to sink messages after disconnect
+cvar_t *sv_rconPassword; // password for remote server commands
+cvar_t *sv_privatePassword; // password for the privateClient slots
+cvar_t *sv_allowDownload;
+cvar_t *sv_maxclients;
+
+cvar_t *sv_privateClients; // number of clients reserved for password
+cvar_t *sv_hostname;
+cvar_t *sv_masters[3][MAX_MASTER_SERVERS]; // master server IP addresses
+cvar_t *sv_reconnectlimit; // minimum seconds between connect messages
+cvar_t *sv_showloss; // report when usercmds are lost
+cvar_t *sv_padPackets; // add nop bytes to messages
+cvar_t *sv_killserver; // menu system can set to 1 to shut server down
+cvar_t *sv_mapname;
+cvar_t *sv_mapChecksum;
+cvar_t *sv_serverid;
+cvar_t *sv_minRate;
+cvar_t *sv_maxRate;
+cvar_t *sv_dlRate;
+cvar_t *sv_minPing;
+cvar_t *sv_maxPing;
+cvar_t *sv_pure;
+cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491)
+cvar_t *sv_banFile;
+
+cvar_t *sv_rsaAuth;
+
+cvar_t *sv_schachtmeisterPort;
+
+// server attack protection
+cvar_t *sv_protect; // 0 - unprotected
+ // 1 - ioquake3 method (default)
+ // 2 - OpenWolf method
+ // 4 - prints attack info to console (when ioquake3 or OPenWolf method is set)
+cvar_t *sv_protectLog; // name of log file
+
+/*
+=============================================================================
+
+EVENT MESSAGES
+
+=============================================================================
+*/
+
+/*
+===============
+SV_ExpandNewlines
+
+Converts newlines to "\n" so a line prints nicer
+===============
+*/
+static char *SV_ExpandNewlines( char *in ) {
+ static char string[1024];
+ int l;
+
+ l = 0;
+ while ( *in && l < sizeof(string) - 3 ) {
+ if ( *in == '\n' ) {
+ string[l++] = '\\';
+ string[l++] = 'n';
+ } else {
+ string[l++] = *in;
+ }
+ in++;
+ }
+ string[l] = 0;
+
+ return string;
+}
+
+/*
+======================
+SV_ReplacePendingServerCommands
+
+FIXME: This is ugly
+======================
+*/
+#if 0 // unused
+static bool SV_ReplacePendingServerCommands( client_t *client, const char *cmd )
+{
+ int i, index, csnum1, csnum2;
+
+ for ( i = client->reliableSent+1; i <= client->reliableSequence; i++ ) {
+ index = i & ( MAX_RELIABLE_COMMANDS - 1 );
+ //
+ if ( !Q_strncmp(cmd, client->reliableCommands[ index ], strlen("cs")) )
+ {
+ sscanf(cmd, "cs %i", &csnum1);
+ sscanf(client->reliableCommands[ index ], "cs %i", &csnum2);
+ if ( csnum1 == csnum2 )
+ {
+ Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
+ return true;
+ }
+ }
+ }
+ return false;
+}
+#endif
+
+/*
+======================
+SV_AddServerCommand
+
+The given command will be transmitted to the client, and is guaranteed to
+not have future snapshot_t executed before it is executed
+======================
+*/
+void SV_AddServerCommand( client_t *client, const char *cmd ) {
+ int index, i;
+
+ // this is very ugly but it's also a waste to for instance send multiple config string updates
+ // for the same config string index in one snapshot
+// if ( SV_ReplacePendingServerCommands( client, cmd ) ) {
+// return;
+// }
+
+ // do not send commands until the gamestate has been sent
+ if( client->state < CS_PRIMED )
+ return;
+
+ client->reliableSequence++;
+ // if we would be losing an old command that hasn't been acknowledged,
+ // we must drop the connection
+ // we check == instead of >= so a broadcast print added by SV_DropClient()
+ // doesn't cause a recursive drop client
+ if ( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) {
+ Com_Printf( "===== pending server commands =====\n" );
+ for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) {
+ Com_Printf( "cmd %5d: %s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
+ }
+ Com_Printf( "cmd %5d: %s\n", i, cmd );
+ SV_DropClient( client, "Server command overflow" );
+ return;
+ }
+ index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
+ Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
+}
+
+/*
+=================
+SV_SendServerCommand
+
+Sends a reliable command string to be interpreted by
+the client game module: "cp", "print", "chat", etc
+A NULL client will broadcast to all clients
+=================
+*/
+void QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) {
+ va_list argptr;
+ byte message[MAX_MSGLEN];
+ client_t *client;
+ int j;
+
+ va_start(argptr, fmt);
+ Q_vsnprintf((char*)message, sizeof(message), fmt,argptr);
+ va_end(argptr);
+
+ // Fix to http://aluigi.altervista.org/adv/q3msgboom-adv.txt
+ // The actual cause of the bug is probably further downstream
+ // and should maybe be addressed later, but this certainly
+ // fixes the problem for now.
+ // Summary: The bug is that messages longer than 1022 are not
+ // allowed downstream and there is a buffer overflow issue
+ // affecting network traffic etc. Therefore, one way to stop
+ // this from happening is stopping the packet here. Ideally,
+ // we should increase the size of the downstream message.
+ if ( strlen ((char *)message) > 1022 ) {
+ SV_WriteAttackLog( va( "SV_SendServerCommand( %ld, %.20s... ) length %ld > 1022, "
+ "dropping to avoid server buffer overflow.\n",
+ cl - svs.clients, message, strlen( (char *)message ) ) );
+ SV_WriteAttackLog( va( "Full message: [%s]\n", message ) );
+ return;
+ }
+
+ if ( cl != NULL ) {
+ SV_AddServerCommand( cl, (char *)message );
+ return;
+ }
+
+ // hack to echo broadcast prints to console
+ if ( com_dedicated->integer && !strncmp( (char *)message, "print", 5) ) {
+ Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) );
+ }
+
+ // send the data to all relevent clients
+ for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) {
+ SV_AddServerCommand( client, (char *)message );
+ }
+}
+
+
+/*
+==============================================================================
+
+MASTER SERVER FUNCTIONS
+
+==============================================================================
+*/
+
+/*
+================
+SV_MasterHeartbeat
+
+Send a message to the masters every few minutes to
+let it know we are alive, and log information.
+We will also have a heartbeat sent when a server
+changes from empty to non-empty, and full to non-full,
+but not on every player enter or exit.
+================
+*/
+#define HEARTBEAT_MSEC 300*1000
+void SV_MasterHeartbeat(const char *message)
+{
+ static netadr_t adrs[3][MAX_MASTER_SERVERS][2]; // [2] for v4 and v6 address for the same address string.
+ int a;
+ int i;
+ int res;
+ int netenabled;
+ int netAlternateProtocols;
+
+ netenabled = Cvar_VariableIntegerValue("net_enabled");
+ netAlternateProtocols = Cvar_VariableIntegerValue("net_alternateProtocols");
+
+ // "dedicated 1" is for lan play, "dedicated 2" is for inet public play
+ if (!com_dedicated || com_dedicated->integer != 2 || !(netenabled & (NET_ENABLEV4 | NET_ENABLEV6)))
+ return; // only dedicated servers send heartbeats
+
+ // if not time yet, don't send anything
+ if ( svs.time < svs.nextHeartbeatTime )
+ return;
+
+ svs.nextHeartbeatTime = svs.time + HEARTBEAT_MSEC;
+
+ for (a = 0; a < 3; ++a)
+ {
+ // indent
+ if(a == 0 && (netAlternateProtocols & NET_DISABLEPRIMPROTO))
+ continue;
+ if(a == 1 && !(netAlternateProtocols & NET_ENABLEALT1PROTO))
+ continue;
+ if(a == 2 && !(netAlternateProtocols & NET_ENABLEALT2PROTO))
+ continue;
+
+ // send to group masters
+ for (i = 0; i < MAX_MASTER_SERVERS; i++)
+ {
+ if(!sv_masters[a][i]->string[0])
+ continue;
+
+ // see if we haven't already resolved the name
+ // resolving usually causes hitches on win95, so only
+ // do it when needed
+ if(sv_masters[a][i]->modified || (adrs[a][i][0].type == NA_BAD && adrs[a][i][1].type == NA_BAD))
+ {
+ sv_masters[a][i]->modified = false;
+
+ if(netenabled & NET_ENABLEV4)
+ {
+ Com_Printf("Resolving %s (IPv4)\n", sv_masters[a][i]->string);
+ res = NET_StringToAdr(sv_masters[a][i]->string, &adrs[a][i][0], NA_IP);
+ adrs[a][i][0].alternateProtocol = a;
+
+ if(res == 2)
+ {
+ // if no port was specified, use the default master port
+ adrs[a][i][0].port = BigShort(a == 2 ? ALT2PORT_MASTER : a == 1 ? ALT1PORT_MASTER : PORT_MASTER);
+ }
+
+ if(res)
+ Com_Printf( "%s resolved to %s\n", sv_masters[a][i]->string, NET_AdrToStringwPort(adrs[a][i][0]));
+ else
+ Com_Printf( "%s has no IPv4 address.\n", sv_masters[a][i]->string);
+ }
+
+ if(netenabled & NET_ENABLEV6)
+ {
+ Com_Printf("Resolving %s (IPv6)\n", sv_masters[a][i]->string);
+ res = NET_StringToAdr(sv_masters[a][i]->string, &adrs[a][i][1], NA_IP6);
+ adrs[a][i][1].alternateProtocol = a;
+
+ if(res == 2)
+ {
+ // if no port was specified, use the default master port
+ adrs[a][i][1].port = BigShort(a == 2 ? ALT2PORT_MASTER : a == 1 ? ALT1PORT_MASTER : PORT_MASTER);
+ }
+
+ if(res)
+ Com_Printf( "%s resolved to %s\n", sv_masters[a][i]->string, NET_AdrToStringwPort(adrs[a][i][1]));
+ else
+ Com_Printf( "%s has no IPv6 address.\n", sv_masters[a][i]->string);
+ }
+
+ if(adrs[a][i][0].type == NA_BAD && adrs[a][i][1].type == NA_BAD)
+ {
+ Com_Printf("Couldn't resolve address: %s\n", sv_masters[a][i]->string);
+ Cvar_Set(sv_masters[a][i]->name, "");
+ sv_masters[a][i]->modified = false;
+ continue;
+ }
+ }
+
+
+ Com_Printf ("Sending%s heartbeat to %s\n", (a == 2 ? " alternate-2" : a == 1 ? " alternate-1" : ""), sv_masters[a][i]->string );
+
+ // this command should be changed if the server info / status format
+ // ever incompatably changes
+
+ if(adrs[a][i][0].type != NA_BAD)
+ NET_OutOfBandPrint( NS_SERVER, adrs[a][i][0], "heartbeat %s\n", message);
+ if(adrs[a][i][1].type != NA_BAD)
+ NET_OutOfBandPrint( NS_SERVER, adrs[a][i][1], "heartbeat %s\n", message);
+ }
+ // outdent
+ }
+}
+
+/*
+=================
+SV_MasterShutdown
+
+Informs all masters that this server is going down
+=================
+*/
+void SV_MasterShutdown( void ) {
+ // send a heartbeat right now
+ svs.nextHeartbeatTime = -9999;
+ SV_MasterHeartbeat(HEARTBEAT_FOR_MASTER);
+
+ // send it again to minimize chance of drops
+ svs.nextHeartbeatTime = -9999;
+ SV_MasterHeartbeat(HEARTBEAT_FOR_MASTER);
+
+ // when the master tries to poll the server, it won't respond, so
+ // it will be removed from the list
+}
+
+/*
+==============================================================================
+
+CONNECTIONLESS COMMANDS
+
+==============================================================================
+*/
+
+// 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 ];
+leakyBucket_t outboundLeakyBucket;
+
+/*
+================
+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;
+ 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 ( int 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 ) ||
+ interval < 0 ) ) {
+ if ( bucket->prev != NULL ) {
+ bucket->prev->next = bucket->next;
+ } else {
+ bucketHashes[ bucket->hash ] = bucket->next;
+ }
+
+ if ( bucket->next != NULL ) {
+ bucket->next->prev = bucket->prev;
+ }
+
+ ::memset( bucket, 0, sizeof( leakyBucket_t ) );
+ }
+
+ if ( bucket->type == NA_BAD ) {
+ bucket->type = address.type;
+ switch ( address.type ) {
+ case NA_IP: ::memcpy( bucket->ipv._4, address.ip, 4 ); break;
+ case NA_IP6: ::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
+ // Write the info to the attack log since this is relevant information as the system is malfunctioning
+ SV_WriteAttackLogD(va("SVC_BucketForAddress: Could not allocate a bucket for client from %s\n", NET_AdrToString(address)));
+
+ return NULL;
+}
+
+/*
+================
+SVC_RateLimit
+ *
+ * @param[in,out] bucket
+ * @param[in] burst
+ * @param[in] period
+ * @return
+ *
+ * @note Don't call if sv_protect 1 (SVP_IOQ3) flag is not set!
+================
+*/
+bool 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 || interval < 0 )
+ {
+ bucket->burst = 0;
+ bucket->lastTime = now;
+ }
+ else
+ {
+ bucket->burst -= expired;
+ bucket->lastTime = now - expiredRemainder;
+ }
+
+ if ( bucket->burst < burst )
+ {
+ bucket->burst++;
+ return false;
+ }
+ else
+ {
+ SV_WriteAttackLogD(va("SVC_RateLimit: burst limit exceeded for bucket: %i limit: %i\n", bucket->burst, burst));
+ }
+ }
+
+ return true;
+}
+
+/*
+================
+SVC_RateLimitAddress
+
+Rate limit for a particular address
+================
+*/
+bool 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
+
+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.
+================
+*/
+static 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];
+
+ if (sv_protect->integer & SVP_IOQ3) {
+ // Prevent using getstatus as an amplifier
+ if (SVC_RateLimitAddress(from, 10, 1000)) {
+ SV_WriteAttackLog(va("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(&outboundLeakyBucket, 10, 100)) {
+ SV_WriteAttackLog("SVC_Status: rate limit exceeded, dropping request\n");
+ return;
+ }
+ }
+
+ // A maximum challenge length of 128 should be more than plenty.
+ if (strlen(Cmd_Argv(1)) > 128) {
+ SV_WriteAttackLog(va("SVC_Status: challenge length exceeded from %s, dropping request\n", NET_AdrToString(from)));
+ return;
+ }
+
+ 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) );
+
+ if ( from.alternateProtocol != 0 )
+ Info_SetValueForKey( infostring, "protocol", from.alternateProtocol == 2 ? "69" : "70" );
+
+ 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;
+ const char *gamedir;
+ char infostring[MAX_INFO_STRING];
+
+ if (sv_protect->integer & SVP_IOQ3) {
+ // Prevent using getinfo as an amplifier
+ if (SVC_RateLimitAddress(from, 10, 1000)) {
+ SV_WriteAttackLog(va("SVC_Info: rate limit from %s exceeded, dropping request\n",
+ NET_AdrToString(from)));
+ return;
+ }
+
+ // Allow getinfo to be DoSed relatively easily, but prevent
+ // excess outbound bandwidth usage when being flooded inbound
+ if (SVC_RateLimit(&outboundLeakyBucket, 10, 100)) {
+ SV_WriteAttackLog("SVC_Info: rate limit exceeded, dropping request\n");
+ return;
+ }
+ }
+
+ // Check whether Cmd_Argv(1) has a sane length. This was not done in the original Quake3 version which led
+ // to the Infostring bug discovered by Luigi Auriemma. See http://aluigi.altervista.org/ for the advisory.
+ // A maximum challenge length of 128 should be more than plenty.
+ if (strlen(Cmd_Argv(1)) > 128) {
+ SV_WriteAttackLog(va("SVC_Info: challenge length from %s exceeded, dropping request\n", NET_AdrToString(from)));
+ return;
+ }
+
+ // don't count privateclients
+ count = 0;
+ for ( i = sv_privateClients->integer ; i < sv_maxclients->integer ; i++ ) {
+ if ( svs.clients[i].state >= CS_CONNECTED ) {
+ count++;
+ }
+ }
+
+ infostring[0] = 0;
+
+ // echo back the parameter to status. so servers can use it as a challenge
+ // to prevent timed spoofed reply packets that add ghost servers
+ Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
+
+ Info_SetValueForKey( infostring, "protocol", va("%i", from.alternateProtocol == 2 ? 69 : from.alternateProtocol == 1 ? 70 : PROTOCOL_VERSION) );
+ Info_SetValueForKey( infostring, "gamename", com_gamename->string );
+ Info_SetValueForKey( infostring, "hostname", sv_hostname->string );
+ Info_SetValueForKey( infostring, "mapname", sv_mapname->string );
+ Info_SetValueForKey( infostring, "clients", va("%i", count) );
+ Info_SetValueForKey( infostring, "sv_maxclients",
+ va("%i", sv_maxclients->integer - sv_privateClients->integer ) );
+ Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) );
+
+#ifdef USE_VOIP
+ if (sv_voipProtocol->string && *sv_voipProtocol->string) {
+ Info_SetValueForKey( infostring, "voip", sv_voipProtocol->string );
+ }
+#endif
+
+ if( sv_minPing->integer ) {
+ Info_SetValueForKey( infostring, "minPing", va("%i", sv_minPing->integer) );
+ }
+ if( sv_maxPing->integer ) {
+ Info_SetValueForKey( infostring, "maxPing", va("%i", sv_maxPing->integer) );
+ }
+ gamedir = Cvar_VariableString( "fs_game" );
+ if( *gamedir ) {
+ Info_SetValueForKey( infostring, "game", gamedir );
+ }
+
+ NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring );
+}
+
+/*
+================
+SVC_FlushRedirect
+
+================
+*/
+static void SV_FlushRedirect( char *outputbuf ) {
+ NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf );
+}
+
+/**
+ * @brief DRDoS stands for "Distributed Reflected Denial of Service".
+ * See here: http://www.lemuria.org/security/application-drdos.html
+ *
+ * If the address isn't NA_IP, it's automatically denied.
+ *
+ * @return false if we're good.
+ * otherwise true means we need to block.
+ *
+ * @note Don't call this if sv_protect 2 flag is not set!
+ */
+bool SV_CheckDRDoS(netadr_t from) {
+ int i;
+ int globalCount;
+ int specificCount;
+ int timeNow;
+ receipt_t *receipt;
+ netadr_t exactFrom;
+ int oldest;
+ int oldestTime;
+ static int lastGlobalLogTime = 0;
+ static int lastSpecificLogTime = 0;
+
+ // Usually the network is smart enough to not allow incoming UDP packets
+ // with a source address being a spoofed LAN address. Even if that's not
+ // the case, sending packets to other hosts in the LAN is not a big deal.
+ // NA_LOOPBACK qualifies as a LAN address.
+ if (Sys_IsLANAddress(from)) {
+ return false;
+ }
+
+ timeNow = svs.time;
+ exactFrom = from;
+
+ // Time has wrapped
+ if (lastGlobalLogTime > timeNow || lastSpecificLogTime > timeNow) {
+ lastGlobalLogTime = 0;
+ lastSpecificLogTime = 0;
+
+ // just setting time to 1 (cannot be 0 as then globalCount would not be counted)
+ for (i = 0; i < MAX_INFO_RECEIPTS; i++) {
+ if (svs.infoReceipts[i].time) {
+ svs.infoReceipts[i].time = 1; // hack it so we count globalCount correctly
+ }
+ }
+ }
+
+ if (from.type == NA_IP) {
+ from.ip[3] = 0; // xx.xx.xx.0
+ } else {
+ from.ip6[15] = 0;
+ }
+
+ // Count receipts in last 2 seconds.
+ globalCount = 0;
+ specificCount = 0;
+ receipt = &svs.infoReceipts[0];
+ oldest = 0;
+ oldestTime = 0x7fffffff;
+ for (i = 0; i < MAX_INFO_RECEIPTS; i++, receipt++) {
+ if (receipt->time + 2000 > timeNow) {
+ if (receipt->time) {
+ // When the server starts, all receipt times are at zero. Furthermore,
+ // svs.time is close to zero. We check that the receipt time is already
+ // set so that during the first two seconds after server starts, queries
+ // from the master servers don't get ignored. As a consequence a potentially
+ // unlimited number of getinfo+getstatus responses may be sent during the
+ // first frame of a server's life.
+ globalCount++;
+ }
+ if (NET_CompareBaseAdr(from, receipt->adr)) {
+ specificCount++;
+ }
+ }
+ if (receipt->time < oldestTime) {
+ oldestTime = receipt->time;
+ oldest = i;
+ }
+ }
+
+ if (globalCount == MAX_INFO_RECEIPTS) { // All receipts happened in last 2 seconds.
+ if (lastGlobalLogTime + 1000 <= timeNow) { // Limit one log every second.
+ SV_WriteAttackLog("Detected flood of getinfo/getstatus connectionless packets\n");
+ lastGlobalLogTime = timeNow;
+ }
+
+ return true;
+ }
+ if (specificCount >= 3) { // Already sent 3 to this IP in last 2 seconds.
+ if (lastSpecificLogTime + 1000 <= timeNow) { // Limit one log every second.
+ SV_WriteAttackLog(va("Possible DRDoS attack to address %s, ignoring getinfo/getstatus connectionless packet\n",
+ NET_AdrToString(exactFrom)));
+ lastSpecificLogTime = timeNow;
+ }
+
+ return true;
+ }
+
+ receipt = &svs.infoReceipts[oldest];
+ receipt->adr = from;
+ receipt->time = timeNow;
+ return false;
+}
+
+/*
+===============
+SVC_RemoteCommand
+
+An rcon packet arrived from the network.
+Shift down the remaining args
+Redirect all printfs
+===============
+*/
+static void SVC_RemoteCommand( netadr_t from, msg_t *msg ) {
+ bool valid;
+ 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];
+ char *cmd_aux;
+
+ // Prevent using rcon as an amplifier and make dictionary attacks impractical
+ if ((sv_protect->integer & SVP_IOQ3) && SVC_RateLimitAddress(from, 10, 1000)) {
+ SV_WriteAttackLog(va("Bad rcon - rate limit from %s exceeded, dropping request\n",
+ NET_AdrToString(from)));
+ return;
+ }
+
+ if ( !strlen( sv_rconPassword->string ) ||
+ strcmp (Cmd_Argv(1), sv_rconPassword->string) ) {
+ static leakyBucket_t bucket;
+
+ // Make DoS via rcon impractical
+ if ((sv_protect->integer & SVP_IOQ3) && SVC_RateLimit(&bucket, 10, 1000)) {
+ SV_WriteAttackLog("Bad rcon - rate limit exceeded, dropping request\n");
+ return;
+ }
+
+ valid = false;
+ Com_Printf ("Bad rcon from %s: %s\n", NET_AdrToString (from), Cmd_ArgsFrom(2) );
+ } else {
+ valid = true;
+ Com_Printf ("Rcon from %s: %s\n", NET_AdrToString (from), Cmd_ArgsFrom(2) );
+ SV_WriteAttackLog(va("Rcon from %s: %s\n", NET_AdrToString(from), Cmd_Argv(2)));
+ }
+
+ // start redirecting all print outputs to the packet
+ svs.redirectAddress = from;
+ Com_BeginRedirect (sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);
+
+ if ( !strlen( sv_rconPassword->string ) ) {
+ Com_Printf ("No rconpassword set on the server.\n");
+ } else if ( !valid ) {
+ Com_Printf ("Bad rconpassword.\n");
+ SV_WriteAttackLog(va("Bad rconpassword from %s\n", NET_AdrToString(from)));
+ } else {
+ remaining[0] = 0;
+
+ // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
+ // get the command directly, "rcon <pass> <command>" to avoid quoting issues
+ // extract the command by walking
+ // since the cmd formatting can fuckup (amount of spaces), using a dumb step by step parsing
+ cmd_aux = Cmd_Cmd();
+ cmd_aux+=4;
+ while(cmd_aux[0]==' ')
+ cmd_aux++;
+ while(cmd_aux[0] && cmd_aux[0]!=' ') // password
+ cmd_aux++;
+ while(cmd_aux[0]==' ')
+ cmd_aux++;
+
+ Q_strcat( remaining, sizeof(remaining), cmd_aux);
+
+ Cmd_ExecuteString (remaining);
+
+ }
+
+ Com_EndRedirect ();
+}
+
+static void SVC_SchachtmeisterResponse( netadr_t from ) {
+
+ int tmp[ 4 ];
+
+ if ( !( from.type == NA_IP && from.ip[0] == 127 ) ) {
+ return;
+ }
+
+ if ( Cmd_Argc() >= 2 && sscanf( Cmd_Argv( 1 ), "%i.%i.%i.%i", &tmp[ 0 ], &tmp[ 1 ], &tmp[ 2 ], &tmp[ 3 ] ) == 4 ) { // compatibility with out-of-date crapware conceived in the future
+ char cmdl[ MAX_STRING_CHARS ];
+ Com_sprintf( cmdl, sizeof( cmdl ), "smr ipa %s", Cmd_ArgsFrom( 1 ) );
+ Cmd_TokenizeString( cmdl );
+ } else {
+ strcpy( Cmd_Argv( 0 ), "smr" );
+ }
+
+ SV_GameCommand();
+}
+
+/*
+=================
+SV_ConnectionlessPacket
+
+A connectionless packet has four leading 0xff
+characters to distinguish it from a game channel.
+Clients that are in the game can still send
+connectionless packets.
+=================
+*/
+static void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
+ char *s;
+ const char *c;
+
+ MSG_BeginReadingOOB( msg );
+ MSG_ReadLong( msg ); // skip the -1 marker
+
+ if (!Q_strncmp("connect", (char *) &msg->data[4], 7)) {
+ Huff_Decompress(msg, 12);
+ }
+
+ s = MSG_ReadStringLine( msg );
+ Cmd_TokenizeString( s );
+
+ c = Cmd_Argv(0);
+ Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c);
+
+ if (!Q_stricmp(c, "getstatus")) {
+ if ((sv_protect->integer & SVP_OWOLF) && SV_CheckDRDoS(from)) {
+ return;
+ }
+
+ SVC_Status( from );
+ } else if (!Q_stricmp(c, "getinfo")) {
+ if ((sv_protect->integer & SVP_OWOLF) && SV_CheckDRDoS(from)) {
+ return;
+ }
+
+ SVC_Info( from );
+ } else if (!Q_stricmp(c, "getchallenge")) {
+ if ((sv_protect->integer & SVP_OWOLF) && SV_CheckDRDoS(from)) {
+ return;
+ }
+
+ SV_GetChallenge(from);
+ } else if (!Q_stricmp(c, "connect")) {
+ SV_DirectConnect( from );
+ } else if (!Q_stricmp(c, "rcon")) {
+ SVC_RemoteCommand( from, msg );
+ } else if (!Q_stricmp(c, "disconnect")) {
+ // if a client starts up a local server, we may see some spurious
+ // server disconnect messages when their new server sees our final
+ // sequenced messages to the old client
+ } else if (!Q_stricmp(c, "sm2reply")) {
+ SVC_SchachtmeisterResponse( from );
+ Com_Printf( "^2response [^7%s^2]\n", s );
+ } else {
+ SV_WriteAttackLog(va("bad connectionless packet from %s:\n%s\n" // changed from Com_DPrintf to print in attack log
+ , NET_AdrToString(from), s)); // this was never reported to admins before so they might be confused
+ } // note: if protect log isn't set we do Com_Printf
+}
+
+//============================================================================
+
+/*
+=================
+SV_PacketEvent
+=================
+*/
+void SV_PacketEvent( netadr_t from, msg_t *msg ) {
+ int i;
+ client_t *cl;
+ int qport;
+
+ // check for connectionless packet (0xffffffff) first
+ if ( msg->cursize >= 4 && *(int *)msg->data == -1) {
+ SV_ConnectionlessPacket( from, msg );
+ return;
+ }
+
+ // read the qport out of the message so we can fix up
+ // stupid address translating routers
+ MSG_BeginReadingOOB( msg );
+ MSG_ReadLong( msg ); // sequence number
+ qport = MSG_ReadShort( msg ) & 0xffff;
+
+ // find which client the message is from
+ for (i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
+ if (cl->state == CS_FREE) {
+ continue;
+ }
+ if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) {
+ continue;
+ }
+ // it is possible to have multiple clients from a single IP
+ // address, so they are differentiated by the qport variable
+ if (cl->netchan.qport != qport) {
+ continue;
+ }
+
+ // the IP port can't be used to differentiate them, because
+ // some address translating routers periodically change UDP
+ // port assignments
+ if (cl->netchan.remoteAddress.port != from.port) {
+ Com_Printf( "SV_PacketEvent: fixing up a translated port\n" );
+ cl->netchan.remoteAddress.port = from.port;
+ }
+
+ // make sure it is a valid, in sequence packet
+ if (SV_Netchan_Process(cl, msg)) {
+ // zombie clients still need to do the Netchan_Process
+ // to make sure they don't need to retransmit the final
+ // reliable message, but they don't do any other processing
+ if (cl->state != CS_ZOMBIE) {
+ cl->lastPacketTime = svs.time; // don't timeout
+ SV_ExecuteClientMessage( cl, msg );
+ }
+ }
+ return;
+ }
+}
+
+
+/*
+===================
+SV_CalcPings
+
+Updates the cl->ping variables
+===================
+*/
+static void SV_CalcPings( void ) {
+ int i, j;
+ client_t *cl;
+ int total, count;
+ int delta;
+ playerState_t *ps;
+
+ for (i=0 ; i < sv_maxclients->integer ; i++) {
+ cl = &svs.clients[i];
+ if ( cl->state != CS_ACTIVE ) {
+ cl->ping = 999;
+ continue;
+ }
+ if ( !cl->gentity ) {
+ cl->ping = 999;
+ continue;
+ }
+
+ total = 0;
+ count = 0;
+ for ( j = 0 ; j < PACKET_BACKUP ; j++ ) {
+ if ( cl->frames[j].messageAcked <= 0 ) {
+ continue;
+ }
+ delta = cl->frames[j].messageAcked - cl->frames[j].messageSent;
+ count++;
+ total += delta;
+ }
+ if (!count) {
+ cl->ping = 999;
+ } else {
+ cl->ping = total/count;
+ if ( cl->ping > 999 ) {
+ cl->ping = 999;
+ }
+ }
+
+ // let the game dll know about the ping
+ ps = SV_GameClientNum( i );
+ ps->ping = cl->ping;
+ }
+}
+
+/*
+==================
+SV_CheckTimeouts
+
+If a packet has not been received from a client for timeout->integer
+seconds, drop the conneciton. Server time is used instead of
+realtime to avoid dropping the local client while debugging.
+
+When a client is normally dropped, the client_t goes into a zombie state
+for a few seconds to make sure any final reliable message gets resent
+if necessary
+==================
+*/
+static void SV_CheckTimeouts( void ) {
+ int i;
+ client_t *cl;
+ int droppoint;
+ int zombiepoint;
+
+ droppoint = svs.time - 1000 * sv_timeout->integer;
+ zombiepoint = svs.time - 1000 * sv_zombietime->integer;
+
+ for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
+ // message times may be wrong across a changelevel
+ if (cl->lastPacketTime > svs.time) {
+ cl->lastPacketTime = svs.time;
+ }
+
+ if (cl->state == CS_ZOMBIE
+ && cl->lastPacketTime < zombiepoint) {
+ // using the client id cause the cl->name is empty at this point
+ Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for client %d\n", i );
+ cl->state = CS_FREE; // can now be reused
+ continue;
+ }
+ if ( cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint) {
+ // wait several frames so a debugger session doesn't
+ // cause a timeout
+ if ( ++cl->timeoutCount > 5 ) {
+ SV_DropClient (cl, "timed out");
+ cl->state = CS_FREE; // don't bother with zombie state
+ }
+ } else {
+ cl->timeoutCount = 0;
+ }
+ }
+}
+
+
+/*
+==================
+SV_CheckPaused
+==================
+*/
+static bool SV_CheckPaused( void ) {
+ int count;
+ client_t *cl;
+ int i;
+
+ if ( !cl_paused->integer ) {
+ return false;
+ }
+
+ // only pause if there is just a single client connected
+ count = 0;
+ for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
+ if ( cl->state >= CS_CONNECTED ) {
+ count++;
+ }
+ }
+
+ if ( count > 1 ) {
+ // don't pause
+ if (sv_paused->integer)
+ Cvar_Set("sv_paused", "0");
+ return false;
+ }
+
+ if (!sv_paused->integer)
+ Cvar_Set("sv_paused", "1");
+ return true;
+}
+
+/*
+==================
+SV_FrameMsec
+Return time in millseconds until processing of the next server frame.
+==================
+*/
+int SV_FrameMsec()
+{
+ if(sv_fps)
+ {
+ int frameMsec;
+
+ frameMsec = 1000.0f / sv_fps->value;
+
+ if(frameMsec < sv.timeResidual)
+ return 0;
+ else
+ return frameMsec - sv.timeResidual;
+ }
+ else
+ return 1;
+}
+
+#define CPU_USAGE_WARNING 70
+#define FRAME_TIME_WARNING 30
+
+/*
+==================
+SV_Frame
+
+Player movement occurs as a result of packet events, which
+happen before SV_Frame is called
+==================
+*/
+void SV_Frame( int msec ) {
+ int frameMsec;
+ int startTime;
+ int frameStartTime = 0;
+ static int start, end;
+
+ start = Sys_Milliseconds();
+ svs.stats.idle += ( double )(start - end) / 1000;
+
+ // the menu kills the server with this cvar
+ if ( sv_killserver->integer ) {
+ SV_Shutdown ("Server was killed");
+ Cvar_Set( "sv_killserver", "0" );
+ return;
+ }
+
+ if (!com_sv_running->integer)
+ {
+ // Running as a server, but no map loaded
+#ifdef DEDICATED
+ // Block until something interesting happens
+ Sys_Sleep(-1);
+#endif
+
+ return;
+ }
+
+ // allow pause if only the local client is connected
+ if ( SV_CheckPaused() ) {
+ return;
+ }
+
+ if (com_dedicated->integer)
+ {
+ frameStartTime = Sys_Milliseconds();
+ }
+
+ // if it isn't time for the next frame, do nothing
+ if ( sv_fps->integer < 1 ) {
+ Cvar_Set( "sv_fps", "10" );
+ }
+
+ frameMsec = 1000 / sv_fps->integer * com_timescale->value;
+ // don't let it scale below 1ms
+ if(frameMsec < 1)
+ {
+ Cvar_Set("timescale", va("%f", sv_fps->integer / 1000.0f));
+ frameMsec = 1;
+ }
+
+ sv.timeResidual += msec;
+
+ // if time is about to hit the 32nd bit, kick all clients
+ // and clear sv.time, rather
+ // than checking for negative time wraparound everywhere.
+ // 2giga-milliseconds = 23 days, so it won't be too often
+ if ( svs.time > 0x70000000 ) {
+ SV_Shutdown( "Restarting server due to time wrapping" );
+ Cbuf_AddText( va( "map \"%s\"\n", Cvar_VariableString( "mapname" ) ) );
+ return;
+ }
+ // this can happen considerably earlier when lots of clients play and the map doesn't change
+ if ( svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities ) {
+ SV_Shutdown( "Restarting server due to numSnapshotEntities wrapping" );
+ Cbuf_AddText( va( "map \"%s\"\n", Cvar_VariableString( "mapname" ) ) );
+ return;
+ }
+
+ if( sv.restartTime && sv.time >= sv.restartTime ) {
+ sv.restartTime = 0;
+ Cbuf_AddText( "map_restart 0\n" );
+ return;
+ }
+
+ // update infostrings if anything has been changed
+ if ( cvar_modifiedFlags & CVAR_SERVERINFO ) {
+ SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) );
+ cvar_modifiedFlags &= ~CVAR_SERVERINFO;
+ }
+ if ( cvar_modifiedFlags & CVAR_SYSTEMINFO ) {
+ SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString_Big( CVAR_SYSTEMINFO ) );
+ cvar_modifiedFlags &= ~CVAR_SYSTEMINFO;
+ }
+
+ if ( com_speeds->integer ) {
+ startTime = Sys_Milliseconds ();
+ } else {
+ startTime = 0; // quite a compiler warning
+ }
+
+ // update ping based on the all received frames
+ SV_CalcPings();
+
+ // run the game simulation in chunks
+ while ( sv.timeResidual >= frameMsec ) {
+ sv.timeResidual -= frameMsec;
+ svs.time += frameMsec;
+ sv.time += frameMsec;
+
+ // let everything in the world think and move
+ VM_Call (sv.gvm, GAME_RUN_FRAME, sv.time);
+ }
+
+ if ( com_speeds->integer ) {
+ time_game = Sys_Milliseconds () - startTime;
+ }
+
+ // check timeouts
+ SV_CheckTimeouts();
+
+ // send messages back to the clients
+ SV_SendClientMessages();
+
+ // send a heartbeat to the master if needed
+ SV_MasterHeartbeat(HEARTBEAT_FOR_MASTER);
+
+ if (com_dedicated->integer)
+ {
+ int frameEndTime = Sys_Milliseconds();
+
+ svs.totalFrameTime += (frameEndTime - frameStartTime);
+
+ // we may send warnings (similar to watchdog) to the game in case the frametime is unacceptable
+ //Com_Printf("FRAMETIME frame: %i total %i\n", frameEndTime - frameStartTime, svs.totalFrameTime);
+
+ svs.currentFrameIndex++;
+
+ //if( svs.currentFrameIndex % 50 == 0 )
+ // Com_Printf( "currentFrameIndex: %i\n", svs.currentFrameIndex );
+
+ if (svs.currentFrameIndex == SERVER_PERFORMANCECOUNTER_FRAMES)
+ {
+ int averageFrameTime = svs.totalFrameTime / SERVER_PERFORMANCECOUNTER_FRAMES;
+
+ svs.sampleTimes[svs.currentSampleIndex % SERVER_PERFORMANCECOUNTER_SAMPLES] = averageFrameTime;
+ svs.currentSampleIndex++;
+
+ if (svs.currentSampleIndex > SERVER_PERFORMANCECOUNTER_SAMPLES)
+ {
+ int totalTime = 0, i;
+
+ for (i = 0; i < SERVER_PERFORMANCECOUNTER_SAMPLES; i++)
+ {
+ totalTime += svs.sampleTimes[i];
+ }
+
+ if (!totalTime)
+ {
+ totalTime = 1;
+ }
+
+ averageFrameTime = totalTime / SERVER_PERFORMANCECOUNTER_SAMPLES;
+
+ svs.serverLoad = (int)((averageFrameTime / (float)frameMsec) * 100);
+ }
+
+ //Com_Printf( "serverload: %i (%i/%i)\n", svs.serverLoad, averageFrameTime, frameMsec );
+
+ svs.totalFrameTime = 0;
+ svs.currentFrameIndex = 0;
+ }
+ }
+ else
+ {
+ svs.serverLoad = -1;
+ }
+
+ // collect timing statistics
+ // - the above 2.60 performance thingy is just inaccurate (30 seconds 'stats')
+ // to give good warning messages and is only done for dedicated
+ end = Sys_Milliseconds();
+ svs.stats.active += (( double )(end - start)) / 1000;
+
+ if (++svs.stats.count == STATFRAMES) // 5 seconds
+ {
+ svs.stats.latched_active = svs.stats.active;
+ svs.stats.latched_idle = svs.stats.idle;
+ svs.stats.active = 0;
+ svs.stats.idle = 0;
+ svs.stats.count = 0;
+
+ svs.stats.cpu = svs.stats.latched_active + svs.stats.latched_idle;
+
+ if (svs.stats.cpu != 0.f)
+ {
+ svs.stats.cpu = 100 * svs.stats.latched_active / svs.stats.cpu;
+ }
+
+ svs.stats.avg = 1000 * svs.stats.latched_active / STATFRAMES;
+
+ // FIXME: add mail, IRC, player info etc for both warnings
+ // TODO: inspect/adjust these values and/or add cvars
+ if (svs.stats.cpu > CPU_USAGE_WARNING)
+ {
+ Com_Printf("^3WARNING: Server CPU has reached a critical usage of %i%%\n", (int) svs.stats.cpu);
+ }
+
+ if (svs.stats.avg > FRAME_TIME_WARNING)
+ {
+ Com_Printf("^3WARNING: Average frame time has reached a critical value of %ims\n", (int) svs.stats.avg);
+ }
+ }
+}
+
+/*
+====================
+SV_RateMsec
+
+Return the number of msec until another message can be sent to
+a client based on its rate settings
+====================
+*/
+
+#define UDPIP_HEADER_SIZE 28
+#define UDPIP6_HEADER_SIZE 48
+
+int SV_RateMsec(client_t *client)
+{
+ int rate, rateMsec;
+ int messageSize;
+
+ messageSize = client->netchan.lastSentSize;
+ rate = client->rate;
+
+ if(sv_maxRate->integer)
+ {
+ if(sv_maxRate->integer < 1000)
+ Cvar_Set( "sv_MaxRate", "1000" );
+ if(sv_maxRate->integer < rate)
+ rate = sv_maxRate->integer;
+ }
+
+ if(sv_minRate->integer)
+ {
+ if(sv_minRate->integer < 1000)
+ Cvar_Set("sv_minRate", "1000");
+ if(sv_minRate->integer > rate)
+ rate = sv_minRate->integer;
+ }
+
+ if(client->netchan.remoteAddress.type == NA_IP6)
+ messageSize += UDPIP6_HEADER_SIZE;
+ else
+ messageSize += UDPIP_HEADER_SIZE;
+
+ rate = (int)(rate * com_timescale->value);
+ if(rate < 1)
+ rate = 1;
+ rateMsec = messageSize * 1000 / rate;
+ rate = Sys_Milliseconds() - client->netchan.lastSentTime;
+
+ if(rate > rateMsec)
+ return 0;
+ else
+ return rateMsec - rate;
+}
+
+/*
+====================
+SV_SendQueuedPackets
+
+Send download messages and queued packets in the time that we're idle, i.e.
+not computing a server frame or sending client snapshots.
+Return the time in msec until we expect to be called next
+====================
+*/
+
+int SV_SendQueuedPackets()
+{
+ int numBlocks;
+ int dlStart, deltaT, delayT;
+ static int dlNextRound = 0;
+ int timeVal = INT_MAX;
+
+ // Send out fragmented packets now that we're idle
+ delayT = SV_SendQueuedMessages();
+ if(delayT >= 0)
+ timeVal = delayT;
+
+ if(sv_dlRate->integer)
+ {
+ // Rate limiting. This is very imprecise for high
+ // download rates due to millisecond timedelta resolution
+ dlStart = Sys_Milliseconds();
+ deltaT = dlNextRound - dlStart;
+
+ if(deltaT > 0)
+ {
+ if(deltaT < timeVal)
+ timeVal = deltaT + 1;
+ }
+ else
+ {
+ numBlocks = SV_SendDownloadMessages();
+
+ if(numBlocks)
+ {
+ // There are active downloads
+ deltaT = Sys_Milliseconds() - dlStart;
+
+ delayT = 1000 * numBlocks * MAX_DOWNLOAD_BLKSIZE;
+ delayT /= sv_dlRate->integer * 1024;
+
+ if(delayT <= deltaT + 1)
+ {
+ // Sending the last round of download messages
+ // took too long for given rate, don't wait for
+ // next round, but always enforce a 1ms delay
+ // between DL message rounds so we don't hog
+ // all of the bandwidth. This will result in an
+ // effective maximum rate of 1MB/s per user, but the
+ // low download window size limits this anyways.
+ if(timeVal > 2)
+ timeVal = 2;
+
+ dlNextRound = dlStart + deltaT + 1;
+ }
+ else
+ {
+ dlNextRound = dlStart + delayT;
+ delayT -= deltaT;
+
+ if(delayT < timeVal)
+ timeVal = delayT;
+ }
+ }
+ }
+ }
+ else
+ {
+ if(SV_SendDownloadMessages())
+ timeVal = 0;
+ }
+
+ return timeVal;
+}