diff options
Diffstat (limited to 'src/server/sv_main.c')
-rw-r--r-- | src/server/sv_main.c | 1147 |
1 files changed, 1147 insertions, 0 deletions
diff --git a/src/server/sv_main.c b/src/server/sv_main.c new file mode 100644 index 0000000..014c3a9 --- /dev/null +++ b/src/server/sv_main.c @@ -0,0 +1,1147 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +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 2 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, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "server.h" + +#ifdef USE_VOIP +cvar_t *sv_voip; +#endif + +serverStatic_t svs; // persistant server info +server_t sv; // local server +vm_t *gvm = NULL; // game virtual machine + +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_master[MAX_MASTER_SERVERS]; // master server ip address +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_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_dequeuePeriod; +cvar_t *sv_heartbeat; // Heartbeat string that is sent to the master +cvar_t *sv_flatline; // If the master server supports it we can send a flatline + // when server is killed + +/* +============================================================================= + +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 int 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 qtrue; + } + } + } + return qfalse; +} +#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 + if ( strlen ((char *)message) > 1022 ) { + 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 adr[MAX_MASTER_SERVERS][2]; // [2] for v4 and v6 address for the same address string. + int i; + int res; + int netenabled; + + netenabled = Cvar_VariableIntegerValue("net_enabled"); + + // "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; + + // send to group masters + for (i = 0; i < MAX_MASTER_SERVERS; i++) + { + if(!sv_master[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_master[i]->modified || (adr[i][0].type == NA_BAD && adr[i][1].type == NA_BAD)) + { + sv_master[i]->modified = qfalse; + + if(netenabled & NET_ENABLEV4) + { + Com_Printf("Resolving %s (IPv4)\n", sv_master[i]->string); + res = NET_StringToAdr(sv_master[i]->string, &adr[i][0], NA_IP); + + if(res == 2) + { + // if no port was specified, use the default master port + adr[i][0].port = BigShort(PORT_MASTER); + } + + if(res) + Com_Printf( "%s resolved to %s\n", sv_master[i]->string, NET_AdrToStringwPort(adr[i][0])); + else + Com_Printf( "%s has no IPv4 address.\n", sv_master[i]->string); + } + + if(netenabled & NET_ENABLEV6) + { + Com_Printf("Resolving %s (IPv6)\n", sv_master[i]->string); + res = NET_StringToAdr(sv_master[i]->string, &adr[i][1], NA_IP6); + + if(res == 2) + { + // if no port was specified, use the default master port + adr[i][1].port = BigShort(PORT_MASTER); + } + + if(res) + Com_Printf( "%s resolved to %s\n", sv_master[i]->string, NET_AdrToStringwPort(adr[i][1])); + else + Com_Printf( "%s has no IPv6 address.\n", sv_master[i]->string); + } + + if(adr[i][0].type == NA_BAD && adr[i][1].type == NA_BAD) + { + Com_Printf("Couldn't resolve address: %s\n", sv_master[i]->string); + Cvar_Set(sv_master[i]->name, ""); + sv_master[i]->modified = qfalse; + continue; + } + } + + + Com_Printf ("Sending heartbeat to %s\n", sv_master[i]->string ); + + // this command should be changed if the server info / status format + // ever incompatably changes + + if(adr[i][0].type != NA_BAD) + NET_OutOfBandPrint( NS_SERVER, adr[i][0], "heartbeat %s\n", message); + if(adr[i][1].type != NA_BAD) + NET_OutOfBandPrint( NS_SERVER, adr[i][1], "heartbeat %s\n", message); + } +} + +/* +================= +SV_MasterShutdown + +Informs all masters that this server is going down +================= +*/ +void SV_MasterShutdown( void ) { + // send a hearbeat right now + svs.nextHeartbeatTime = -9999; + SV_MasterHeartbeat(sv_flatline->string); + + // send it again to minimize chance of drops + svs.nextHeartbeatTime = -9999; + SV_MasterHeartbeat(sv_flatline->string); + + // when the master tries to poll the server, it won't respond, so + // it will be removed from the list +} + +/* +================= +SV_MasterGameStat +================= +*/ +void SV_MasterGameStat( const char *data ) +{ + netadr_t adr; + + if( !com_dedicated || com_dedicated->integer != 2 ) + return; // only dedicated servers send stats + + Com_Printf( "Resolving %s\n", MASTER_SERVER_NAME ); + + switch( NET_StringToAdr( MASTER_SERVER_NAME, &adr, NA_UNSPEC ) ) + { + case 0: + Com_Printf( "Couldn't resolve master address: %s\n", MASTER_SERVER_NAME ); + return; + + case 2: + adr.port = BigShort( PORT_MASTER ); + default: + break; + } + + Com_Printf( "%s resolved to %s\n", MASTER_SERVER_NAME, + NET_AdrToStringwPort( adr ) ); + + Com_Printf( "Sending gamestat to %s\n", MASTER_SERVER_NAME ); + NET_OutOfBandPrint( NS_SERVER, adr, "gamestat %s", data ); +} + +/* +============================================================================== + +CONNECTIONLESS COMMANDS + +============================================================================== +*/ + +typedef struct leakyBucket_s leakyBucket_t; +struct leakyBucket_s { + netadrtype_t type; + + union { + byte _4[4]; + byte _6[16]; + } ipv; + + int lastTime; + signed char burst; + + long hash; + + leakyBucket_t *prev, *next; +}; + +// This is deliberately quite large to make it more of an effort to DoS +#define MAX_BUCKETS 16384 +#define MAX_HASHES 1024 + +static leakyBucket_t buckets[ MAX_BUCKETS ]; +static leakyBucket_t *bucketHashes[ MAX_HASHES ]; + +/* +================ +SVC_HashForAddress +================ +*/ +static long SVC_HashForAddress( netadr_t address ) { + byte *ip = NULL; + size_t size = 0; + int i; + long hash = 0; + + switch ( address.type ) { + case NA_IP: ip = address.ip; size = 4; break; + case NA_IP6: ip = address.ip6; size = 16; break; + default: break; + } + + for ( i = 0; i < size; i++ ) { + hash += (long)( ip[ i ] ) * ( i + 119 ); + } + + hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) ); + hash &= ( MAX_HASHES - 1 ); + + return hash; +} + +/* +================ +SVC_BucketForAddress + +Find or allocate a bucket for an address +================ +*/ +static leakyBucket_t *SVC_BucketForAddress( netadr_t address, int burst, int period ) { + leakyBucket_t *bucket = NULL; + int i; + long hash = SVC_HashForAddress( address ); + int now = Sys_Milliseconds(); + + for ( bucket = bucketHashes[ hash ]; bucket; bucket = bucket->next ) { + switch ( bucket->type ) { + case NA_IP: + if ( memcmp( bucket->ipv._4, address.ip, 4 ) == 0 ) { + return bucket; + } + break; + + case NA_IP6: + if ( memcmp( bucket->ipv._6, address.ip6, 16 ) == 0 ) { + return bucket; + } + break; + + default: + break; + } + } + + for ( i = 0; i < MAX_BUCKETS; i++ ) { + int interval; + + bucket = &buckets[ i ]; + interval = now - bucket->lastTime; + + // Reclaim expired buckets + if ( bucket->lastTime > 0 && ( interval > ( burst * period ) || + 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; + } + + Com_Memset( bucket, 0, sizeof( leakyBucket_t ) ); + } + + if ( bucket->type == NA_BAD ) { + bucket->type = address.type; + switch ( address.type ) { + case NA_IP: Com_Memcpy( bucket->ipv._4, address.ip, 4 ); break; + case NA_IP6: Com_Memcpy( bucket->ipv._6, address.ip6, 16 ); break; + default: break; + } + + bucket->lastTime = now; + bucket->burst = 0; + bucket->hash = hash; + + // Add to the head of the relevant hash chain + bucket->next = bucketHashes[ hash ]; + if ( bucketHashes[ hash ] != NULL ) { + bucketHashes[ hash ]->prev = bucket; + } + + bucket->prev = NULL; + bucketHashes[ hash ] = bucket; + + return bucket; + } + } + + // Couldn't allocate a bucket for this address + return NULL; +} + +/* +================ +SVC_RateLimit +================ +*/ +static qboolean SVC_RateLimit( leakyBucket_t *bucket, int burst, int period ) { + if ( bucket != NULL ) { + int now = Sys_Milliseconds(); + int interval = now - bucket->lastTime; + int expired = interval / period; + int expiredRemainder = interval % period; + + if ( expired > bucket->burst ) { + bucket->burst = 0; + bucket->lastTime = now; + } else { + bucket->burst -= expired; + bucket->lastTime = now - expiredRemainder; + } + + if ( bucket->burst < burst ) { + bucket->burst++; + + return qfalse; + } + } + + return qtrue; +} + +/* +================ +SVC_RateLimitAddress + +Rate limit for a particular address +================ +*/ +static qboolean SVC_RateLimitAddress( netadr_t from, int burst, int period ) { + leakyBucket_t *bucket = SVC_BucketForAddress( from, burst, period ); + + return SVC_RateLimit( bucket, burst, period ); +} + +/* +================ +SVC_Status + +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]; + static leakyBucket_t bucket; + + // Prevent using getstatus as an amplifier + if ( SVC_RateLimitAddress( from, 10, 1000 ) ) { + Com_DPrintf( "SVC_Status: rate limit from %s exceeded, dropping request\n", + NET_AdrToString( from ) ); + return; + } + + // Allow getstatus to be DoSed relatively easily, but prevent + // excess outbound bandwidth usage when being flooded inbound + if ( SVC_RateLimit( &bucket, 10, 100 ) ) { + Com_DPrintf( "SVC_Status: rate limit exceeded, dropping request\n" ); + return; + } + + strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) ); + + // echo back the parameter to status. so master servers can use it as a challenge + // to prevent timed spoofed reply packets that add ghost servers + Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) ); + + status[0] = 0; + statusLength = 0; + + for (i=0 ; i < sv_maxclients->integer ; i++) { + cl = &svs.clients[i]; + if ( cl->state >= CS_CONNECTED ) { + ps = SV_GameClientNum( i ); + Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n", + ps->persistant[PERS_SCORE], cl->ping, cl->name); + playerLength = strlen(player); + if (statusLength + playerLength >= sizeof(status) ) { + break; // can't hold any more + } + strcpy (status + statusLength, player); + statusLength += playerLength; + } + } + + NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status ); +} + +/* +================ +SVC_Info + +Responds with a short info message that should be enough to determine +if a user is interested in a server to do a full status +================ +*/ +void SVC_Info( netadr_t from ) { + int i, count; + char *gamedir; + char infostring[MAX_INFO_STRING]; + + /* + * 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) + 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", PROTOCOL_VERSION) ); + 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_voip->integer) { + Info_SetValueForKey( infostring, "voip", va("%i", sv_voip->integer ) ); + } +#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 ); +} + +/* +=============== +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 ) { + qboolean 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 ( SVC_RateLimitAddress( from, 10, 1000 ) ) { + Com_DPrintf( "SVC_RemoteCommand: 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 ( SVC_RateLimit( &bucket, 10, 1000 ) ) { + Com_DPrintf( "SVC_RemoteCommand: rate limit exceeded, dropping request\n" ); + return; + } + + valid = qfalse; + Com_Printf ("Bad rcon from %s: %s\n", NET_AdrToString (from), Cmd_ArgsFrom(2) ); + } else { + valid = qtrue; + Com_Printf ("Rcon from %s: %s\n", NET_AdrToString (from), Cmd_ArgsFrom(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"); + } 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 (); +} + +/* +================= +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; + 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")) { + SVC_Status( from ); + } else if (!Q_stricmp(c, "getinfo")) { + SVC_Info( from ); + } else if (!Q_stricmp(c, "getchallenge")) { + 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 { + Com_DPrintf ("bad connectionless packet from %s:\n%s\n", + NET_AdrToString (from), s); + } +} + +//============================================================================ + +/* +================= +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; + } + + // if we received a sequenced packet from an address we don't recognize, + // send an out of band disconnect packet to it + NET_OutOfBandPrint( NS_SERVER, from, "disconnect" ); +} + + +/* +=================== +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 qboolean SV_CheckPaused( void ) { + int count; + client_t *cl; + int i; + + if ( !cl_paused->integer ) { + return qfalse; + } + + // 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 qfalse; + } + + if (!sv_paused->integer) + Cvar_Set("sv_paused", "1"); + return qtrue; +} + +/* +================== +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; +} + +/* +================== +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; + + // 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 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 (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(sv_heartbeat->string); +} + +//============================================================================ + |