From 11f5870a8b519305aa660da1f453478f9899ebd0 Mon Sep 17 00:00:00 2001 From: smallmodel <15067410+smallmodel@users.noreply.github.com> Date: Sat, 31 Aug 2024 20:45:05 +0200 Subject: [PATCH] Add ioq3 server fixes and improvement - Add a rate limit to protect against DoS attacks - Better IPv6 support --- code/qcommon/msg.cpp | 12 + code/qcommon/q_shared.h | 2 + code/qcommon/qcommon.h | 9 +- code/server/server.h | 223 ++++++++------- code/server/sv_client.c | 566 ++++++++++++++++++++++++++------------ code/server/sv_init.c | 55 ++-- code/server/sv_main.c | 424 +++++++++++++++++++++------- code/server/sv_net_chan.c | 16 +- code/server/sv_snapshot.c | 74 ++++- code/server/sv_world.c | 12 +- 10 files changed, 966 insertions(+), 427 deletions(-) diff --git a/code/qcommon/msg.cpp b/code/qcommon/msg.cpp index a5f46991..bb04cba3 100644 --- a/code/qcommon/msg.cpp +++ b/code/qcommon/msg.cpp @@ -875,6 +875,18 @@ void MSG_ReadData( msg_t *msg, void *data, int len ) { } } +// a string hasher which gives the same hash value even if the +// string is later modified via the legacy MSG read/write code +int MSG_HashKey(const char *string, int maxlen) { + int register hash, i; + + hash = 0; + for (i = 0; i < maxlen && string[i] != '\0'; i++) { + hash += string[i] * (119 + i); + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + return hash; +} /* ============================================================================= diff --git a/code/qcommon/q_shared.h b/code/qcommon/q_shared.h index 63f6dd31..cb601125 100644 --- a/code/qcommon/q_shared.h +++ b/code/qcommon/q_shared.h @@ -79,6 +79,8 @@ extern "C" { #define CLIENT_WINDOW_TITLE PRODUCT_NAME #define CLIENT_WINDOW_MIN_TITLE PRODUCT_NAME + +#define MAX_MASTER_SERVERS 5 // number of supported master servers #define DEMOEXT "dm_" // standard demo extension diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index 6baab452..fb7db583 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -87,6 +87,7 @@ void MSG_WriteBigString (msg_t *sb, const char *s); void MSG_WriteScrambledString(msg_t* sb, const char* s); void MSG_WriteScrambledBigString(msg_t* sb, const char* s); void MSG_WriteAngle16 (msg_t *sb, float f); +int MSG_HashKey(const char *string, int maxlen); void MSG_WriteEntityNum(msg_t* sb, short number); void MSG_BeginReading (msg_t *sb); @@ -202,8 +203,8 @@ NET #define MAX_RELIABLE_COMMANDS 512 // max string commands buffered for restransmit typedef enum { - NA_BOT, NA_BAD, // an address lookup failed + NA_BOT, NA_LOOPBACK, NA_BROADCAST, NA_IP, @@ -373,7 +374,11 @@ enum clc_ops_e { clc_move, // [[usercmd_t] clc_moveNoDelta, // [[usercmd_t] clc_clientCommand, // [string] message - clc_EOF + clc_EOF, + +// new commands, supported only by ioquake3 protocol but not legacy + clc_voipSpeex, // not wrapped in USE_VOIP, so this value is reserved. + clc_voipOpus, // }; /* diff --git a/code/server/server.h b/code/server/server.h index 464ecb04..498c15b8 100644 --- a/code/server/server.h +++ b/code/server/server.h @@ -21,8 +21,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ // server.h -#ifndef __SERVER_H__ -#define __SERVER_H__ +#pragma once #include "../qcommon/q_shared.h" #include "../qcommon/qcommon.h" @@ -43,6 +42,21 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA extern "C" { #endif +#ifdef USE_VOIP +#define VOIP_QUEUE_LENGTH 64 + +typedef struct voipServerPacket_s +{ + int generation; + int sequence; + int frames; + int len; + int sender; + int flags; + byte data[4000]; +} voipServerPacket_t; +#endif + typedef struct svEntity_s { struct worldSector_s *worldSector; struct svEntity_s *nextEntityInWorldSector; @@ -93,6 +107,10 @@ typedef struct { int gameClientSize; // will be > sizeof(playerState_t) due to game private data } server_t; + + + + typedef struct { int areabytes; byte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits @@ -103,7 +121,7 @@ typedef struct { // order, otherwise the delta compression will fail int messageSent; // time the message was transmitted int messageAcked; // time the message was acked - size_t messageSize; // used to rate drop packets + int messageSize; // used to rate drop packets } clientSnapshot_t; typedef enum { @@ -129,13 +147,13 @@ typedef struct client_s { char userinfo[MAX_INFO_STRING]; // name, etc char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; - int reliableSequence; // last added reliable message, not necesarily sent or acknowledged yet + int reliableSequence; // last added reliable message, not necessarily sent or acknowledged yet int reliableAcknowledge; // last acknowledged reliable message - int reliableSent; // last sent reliable message, not necesarily acknowledged yet + int reliableSent; // last sent reliable message, not necessarily acknowledged yet int messageAcknowledge; int gamestateMessageNum; // netchan->outgoingSequence of gamestate - int challenge; + int challenge; int serverIdAcknowledge; usercmd_t lastUsercmd; @@ -151,21 +169,21 @@ typedef struct client_s { // downloading char downloadName[MAX_QPATH]; // if not empty string, we are downloading fileHandle_t download; // file being downloaded - size_t downloadSize; // total bytes (can't use EOF because of paks) - size_t downloadCount; // bytes sent + int downloadSize; // total bytes (can't use EOF because of paks) + int downloadCount; // bytes sent int downloadClientBlock; // last block we sent to the client, awaiting ack int downloadCurrentBlock; // current block number int downloadXmitBlock; // last block we xmited unsigned char *downloadBlocks[MAX_DOWNLOAD_WINDOW]; // the buffers for the download blocks - size_t downloadBlockSize[MAX_DOWNLOAD_WINDOW]; + int downloadBlockSize[MAX_DOWNLOAD_WINDOW]; qboolean downloadEOF; // We have sent the EOF block int downloadSendTime; // time we last got an ack from the client int deltaMessage; // frame last client usercmd message int nextReliableTime; // svs.time when another reliable command will be allowed int lastPacketTime; // svs.time when packet was last received - int lastConnectTime; // svs.time when connection started - int lastSnapshotTime; // svs.time of last sent snapshot + int lastConnectTime; // svs.time when connection started + int lastSnapshotTime; // svs.time of last sent snapshot qboolean rateDelayed; // true if nextSnapshotTime was set based on rate instead of snapshotMsec int timeoutCount; // must timeout a few frames in a row so debugging doesn't break clientSnapshot_t frames[PACKET_BACKUP]; // updates can be delta'd from here @@ -182,8 +200,17 @@ typedef struct client_s { netchan_buffer_t *netchan_start_queue; netchan_buffer_t **netchan_end_queue; +#ifdef USE_VOIP + qboolean hasVoip; + qboolean muteAllVoip; + qboolean ignoreVoipFromClient[MAX_CLIENTS]; + voipServerPacket_t *voipPacket[VOIP_QUEUE_LENGTH]; + int queuedVoipPackets; + int queuedVoipIndex; +#endif + int oldServerTime; - qboolean csUpdated[MAX_CONFIGSTRINGS+1]; + qboolean csUpdated[MAX_CONFIGSTRINGS]; server_sound_t server_sounds[ MAX_SERVER_SOUNDS ]; int number_of_server_sounds; @@ -198,7 +225,7 @@ typedef struct client_s { int lastVisCheckTime[MAX_CLIENTS]; #ifdef LEGACY_PROTOCOL - qboolean compat; + qboolean compat; #endif } client_t; @@ -208,7 +235,11 @@ typedef struct client_s { // MAX_CHALLENGES is made large to prevent a denial // of service attack that could cycle all of them // out before legitimate users connected -#define MAX_CHALLENGES 1024 +#define MAX_CHALLENGES 2048 +// Allow a certain amount of challenges to have the same IP address +// to make it a bit harder to DOS one single IP address from connecting +// while not allowing a single ip to grab all challenge resources +#define MAX_CHALLENGES_MULTI (MAX_CHALLENGES / 2) #define AUTHORIZE_TIMEOUT 5000 @@ -227,47 +258,52 @@ typedef enum { typedef struct { netadr_t adr; - int challenge; - int clientChallenge; // challenge number coming from the client - int time; // time the last packet was sent to the autherize server - int pingTime; // time the challenge response was sent to client - int firstTime; // time the adr was first used, for authorize timeout checks - qboolean wasrefused; - qboolean connected; + int challenge; + int clientChallenge; // challenge number coming from the client + int time; // time the last packet was sent to the autherize server + int pingTime; // time the challenge response was sent to client + int firstTime; // time the adr was first used, for authorize timeout checks + qboolean wasrefused; + qboolean connected; + + // + // gamespy stuff + // unsigned int gamespyId; char gsChallenge[12]; cdKeyState_e cdkeyState; } challenge_t; - -#define MAX_MASTERS 8 // max recipients for heartbeat packets - // this structure will be cleared only when the game dll changes typedef struct { - qboolean initialized; // sv_init has completed + qboolean initialized; // sv_init has completed - int snapFlagServerBit; // ^= SNAPFLAG_SERVERCOUNT every SV_SpawnServer() + int time; // will be strictly increasing across level changes - int time; // will be strictly increasing across level changes - int startTime; - int lastTime; - int serverLagTime; - qboolean autosave; - int mapTime; + int snapFlagServerBit; // ^= SNAPFLAG_SERVERCOUNT every SV_SpawnServer() - client_t *clients; // [sv_maxclients->integer]; - int iNumClients; - int numSnapshotEntities; // sv_maxclients->integer*PACKET_BACKUP*MAX_PACKET_ENTITIES - int nextSnapshotEntities; // next snapshotEntities to use + int startTime; + int lastTime; + int serverLagTime; + qboolean autosave; + int mapTime; + + client_t *clients; // [sv_maxclients->integer]; + int iNumClients; + int numSnapshotEntities; // sv_maxclients->integer*PACKET_BACKUP*MAX_SNAPSHOT_ENTITIES + int nextSnapshotEntities; // next snapshotEntities to use entityState_t *snapshotEntities; // [numSnapshotEntities] - int nextHeartbeatTime; - challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting - netadr_t redirectAddress; // for rcon return messages - netadr_t authorizeAddress; // for rcon return messages + int nextHeartbeatTime; + challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting + netadr_t redirectAddress; // for rcon return messages +#ifndef STANDALONE + netadr_t authorizeAddress; // authorize server address +#endif char gameName[ MAX_QPATH ]; char mapName[ MAX_QPATH ]; char rawServerName[ MAX_QPATH ]; int areabits_warning_time; + qboolean soundsNeedLoad; char tm_filename[ MAX_QPATH ]; int tm_loopcount; @@ -291,53 +327,51 @@ typedef struct //============================================================================= -extern cvar_t *sv_mapname; -extern serverStatic_t svs; // persistant server info across maps -extern server_t sv; // cleared each map -extern game_export_t *ge; // game exports +extern serverStatic_t svs; // persistant server info across maps +extern server_t sv; // cleared each map +extern game_export_t *ge; // game exports -#define MAX_MASTER_SERVERS 5 +extern cvar_t *sv_fps; +extern cvar_t *sv_timeout; +extern cvar_t *sv_zombietime; +extern cvar_t *sv_rconPassword; +extern cvar_t *sv_privatePassword; +extern cvar_t *sv_allowDownload; +extern cvar_t *sv_maxclients; -extern cvar_t *sv_fps; -extern cvar_t *sv_timeout; -extern cvar_t *sv_zombietime; -extern cvar_t *sv_rconPassword; -extern cvar_t *sv_privatePassword; -extern cvar_t *sv_allowDownload; -extern cvar_t *sv_maxclients; - -extern cvar_t *sv_privateClients; -extern cvar_t *sv_hostname; -extern cvar_t *sv_master[ MAX_MASTER_SERVERS ]; -extern cvar_t *sv_reconnectlimit; -extern cvar_t *sv_showloss; -extern cvar_t *sv_padPackets; -extern cvar_t *sv_killserver; -extern cvar_t *sv_mapChecksum; -extern cvar_t *sv_serverid; -extern cvar_t *sv_minRate; -extern cvar_t *sv_maxRate; -extern cvar_t *sv_dlRate; -extern cvar_t *sv_minPing; -extern cvar_t *sv_maxPing; -extern cvar_t *sv_pure; -extern cvar_t *sv_floodProtect; -extern cvar_t *sv_maplist; -extern cvar_t *sv_drawentities; -extern cvar_t *sv_deeptracedebug; -extern cvar_t *sv_netprofile; -extern cvar_t *sv_netprofileoverlay; -extern cvar_t *sv_netoptimize; -extern cvar_t *sv_netoptimize_vistime; -extern cvar_t *g_netoptimize; -extern cvar_t *g_gametype; -extern cvar_t *g_gametypestring; -extern cvar_t *sv_chatter; -extern cvar_t *sv_gamename; -extern cvar_t *sv_location; -extern cvar_t *sv_debug_gamespy; -extern cvar_t *sv_gamespy; -extern cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491) +extern cvar_t *sv_privateClients; +extern cvar_t *sv_hostname; +extern cvar_t *sv_master[MAX_MASTER_SERVERS]; +extern cvar_t *sv_reconnectlimit; +extern cvar_t *sv_showloss; +extern cvar_t *sv_padPackets; +extern cvar_t *sv_killserver; +extern cvar_t *sv_mapname; +extern cvar_t *sv_mapChecksum; +extern cvar_t *sv_serverid; +extern cvar_t *sv_minRate; +extern cvar_t *sv_maxRate; +extern cvar_t *sv_dlRate; +extern cvar_t *sv_minPing; +extern cvar_t *sv_maxPing; +extern cvar_t *g_gametype; +extern cvar_t *g_gametypestring; +extern cvar_t *sv_pure; +extern cvar_t *sv_floodProtect; +extern cvar_t *sv_lanForceRate; +extern cvar_t *sv_maplist; +extern cvar_t *sv_drawentities; +extern cvar_t *sv_deeptracedebug; +extern cvar_t *sv_netprofile; +extern cvar_t *sv_netprofileoverlay; +extern cvar_t *sv_netoptimize; +extern cvar_t *sv_netoptimize_vistime; +extern cvar_t *g_netoptimize; +extern cvar_t *sv_chatter; +extern cvar_t *sv_gamename; +extern cvar_t *sv_location; +extern cvar_t *sv_debug_gamespy; +extern cvar_t *sv_gamespy; #ifndef STANDALONE extern cvar_t *sv_strictAuth; #endif @@ -351,6 +385,7 @@ extern cvar_t *sv_voip; extern cvar_t *sv_voipProtocol; #endif + //=========================================================== // @@ -378,8 +413,8 @@ extern leakyBucket_t outboundLeakyBucket; qboolean SVC_RateLimit( leakyBucket_t *bucket, int burst, int period ); qboolean SVC_RateLimitAddress( netadr_t from, int burst, int period ); -void SV_FinalMessage( const char *message ); -void QDECL SV_SendServerCommand( client_t *cl, const char *fmt, ...); +void SV_FinalMessage (const char *message); +void QDECL SV_SendServerCommand( client_t *cl, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); void SV_AddOperatorCommands (void); @@ -444,8 +479,8 @@ void SV_SpawnServer( const char *server, qboolean loadgame, qboolean restart, qb // // sv_client.c // -challenge_t* FindChallenge(netadr_t from, qboolean connecting); -void SV_GetChallenge( netadr_t from ); +challenge_t* FindChallenge(netadr_t from, qboolean connecting); +void SV_GetChallenge(netadr_t from); void SV_DirectConnect( netadr_t from ); @@ -455,15 +490,17 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ); void SV_UserinfoChanged( client_t *cl ); void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ); +void SV_FreeClient(client_t *client); void SV_DropClient( client_t *drop, const char *reason ); void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ); -void SV_ClientThink( client_t *cl, usercmd_t *cmd ); +void SV_ClientThink (client_t *cl, usercmd_t *cmd); -int SV_WriteDownloadToClient( client_t *cl , msg_t *msg ); +int SV_WriteDownloadToClient(client_t *cl , msg_t *msg); int SV_SendDownloadMessages(void); int SV_SendQueuedMessages(void); + // // sv_ccmds.c // @@ -524,7 +561,7 @@ void SV_UnlinkEntity( gentity_t *ent ); void SV_LinkEntity( gentity_t *ent ); // Needs to be called any time an entity changes origin, mins, maxs, // or solid. Automatically unlinks if needed. -// sets ent->v.absmin and ent->v.absmax +// sets ent->r.absmin and ent->r.absmax // sets ent->leafnums[] for pvs determination even if the entity // is not solid @@ -585,5 +622,3 @@ void SV_ShutdownGamespy(); #ifdef __cplusplus } #endif - -#endif // __SERVER_H__ diff --git a/code/server/sv_client.c b/code/server/sv_client.c index a2868930..1e4b4646 100644 --- a/code/server/sv_client.c +++ b/code/server/sv_client.c @@ -46,10 +46,19 @@ to be sent to the authorize server. When an authorizeip is returned, a challenge response will be sent to that ip. + +ioquake3: we added a possibility for clients to add a challenge +to their packets, to make it more difficult for malicious servers +to hi-jack client connections. +Also, the auth stuff is completely disabled for com_standalone games +as well as IPv6 connections, since there is no way to use the +v4-only auth server for these new types of connections. ================= */ -void SV_GetChallenge( netadr_t from ) { +void SV_GetChallenge(netadr_t from) +{ challenge_t *challenge; + qboolean wasfound = qfalse; // ignore if we are in single player // Removed in OPM @@ -58,9 +67,23 @@ void SV_GetChallenge( netadr_t from ) { // return; //} - challenge = FindChallenge(from, qtrue); + // Prevent using getchallenge as an amplifier + if ( SVC_RateLimitAddress( from, 10, 1000 ) ) { + Com_DPrintf( "SV_GetChallenge: rate limit from %s exceeded, dropping request\n", + NET_AdrToString( from ) ); + return; + } - // if they are on a lan address, send the challengeResponse immediately + // Allow getchallenge to be DoSed relatively easily, but prevent + // excess outbound bandwidth usage when being flooded inbound + if ( SVC_RateLimit( &outboundLeakyBucket, 10, 100 ) ) { + Com_DPrintf( "SV_GetChallenge: rate limit exceeded, dropping request\n" ); + return; + } + + challenge = FindChallenge(from, qtrue); + + // if they are on a lan address, send the challengeResponse immediately // we send the challengeResponse immediately as there is no AUTH server for us // it's also way more cool this way :) @@ -89,8 +112,10 @@ void SV_GetChallenge( netadr_t from ) { // check client's cd key NET_OutOfBandPrint(NS_SERVER, from, "getKey %s", challenge->gsChallenge); + challenge->pingTime = svs.time; } +#ifndef STANDALONE /* ==================== SV_AuthorizeIpPacket @@ -98,8 +123,6 @@ SV_AuthorizeIpPacket A packet has been returned from the authorize server. If we have a challenge adr for that ip, send the challengeResponse to it - -NOTE: This function is deprecated, SV_GamespyAuthorize must be used instead. ==================== */ void SV_AuthorizeIpPacket( netadr_t from ) { @@ -107,6 +130,7 @@ void SV_AuthorizeIpPacket( netadr_t from ) { int i; char *s; char *r; + challenge_t *challengeptr; if ( !NET_CompareBaseAdr( from, svs.authorizeAddress ) ) { Com_Printf( "SV_AuthorizeIpPacket: not from authorize server\n" ); @@ -124,45 +148,48 @@ void SV_AuthorizeIpPacket( netadr_t from ) { Com_Printf( "SV_AuthorizeIpPacket: challenge not found\n" ); return; } + + challengeptr = &svs.challenges[i]; // send a packet back to the original client - svs.challenges[i].pingTime = svs.time; + challengeptr->pingTime = svs.time; s = Cmd_Argv( 2 ); r = Cmd_Argv( 3 ); // reason if ( !Q_stricmp( s, "demo" ) ) { // they are a demo client trying to connect to a real server - NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nServer is not a demo server\n" ); + NET_OutOfBandPrint( NS_SERVER, challengeptr->adr, "print\nServer is not a demo server\n" ); // clear the challenge record so it won't timeout and let them through - Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); + Com_Memset( challengeptr, 0, sizeof( *challengeptr ) ); return; } if ( !Q_stricmp( s, "accept" ) ) { - NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, - "challengeResponse %i", svs.challenges[i].challenge ); + NET_OutOfBandPrint(NS_SERVER, challengeptr->adr, + "challengeResponse %d %d %d", challengeptr->challenge, challengeptr->clientChallenge, com_protocol->integer); return; } if ( !Q_stricmp( s, "unknown" ) ) { if (!r) { - NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nAwaiting CD key authorization\n" ); + NET_OutOfBandPrint( NS_SERVER, challengeptr->adr, "print\nAwaiting CD key authorization\n" ); } else { - NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\n%s\n", r); + NET_OutOfBandPrint( NS_SERVER, challengeptr->adr, "print\n%s\n", r); } // clear the challenge record so it won't timeout and let them through - Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); + Com_Memset( challengeptr, 0, sizeof( *challengeptr ) ); return; } // authorization failed if (!r) { - NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nSomeone is using this CD Key\n" ); + NET_OutOfBandPrint( NS_SERVER, challengeptr->adr, "print\nSomeone is using this CD Key\n" ); } else { - NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\n%s\n", r ); + NET_OutOfBandPrint( NS_SERVER, challengeptr->adr, "print\n%s\n", r ); } // clear the challenge record so it won't timeout and let them through - Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); + Com_Memset( challengeptr, 0, sizeof(*challengeptr) ); } +#endif /* ================== @@ -265,16 +292,11 @@ A "connect" OOB command has been received ================== */ -#define PB_MESSAGE "PunkBuster Anti-Cheat software must be installed " \ - "and Enabled in order to join this server. An updated game patch can be downloaded from " \ - "www.idsoftware.com" - void SV_DirectConnect( netadr_t from ) { char userinfo[MAX_INFO_STRING]; int i; client_t *cl, *newcl; - // Make this a temp variable to avoid stack overflow issues - static client_t temp; + static client_t temp; // static variable, otherwise it causes a stack overflow gentity_t *ent; int clientNum; int version; @@ -290,7 +312,7 @@ void SV_DirectConnect( netadr_t from ) { #endif challenge_t* ch; - Com_DPrintf( "SVC_DirectConnect ()\n" ); + Com_DPrintf ("SVC_DirectConnect ()\n"); // Check whether this client is banned. if(SV_IsBanned(&from, qfalse)) @@ -299,9 +321,10 @@ void SV_DirectConnect( netadr_t from ) { return; } - Q_strncpyz( userinfo, Cmd_Argv( 1 ), sizeof( userinfo ) ); + Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); - version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); + version = atoi(Info_ValueForKey(userinfo, "protocol")); + #ifdef LEGACY_PROTOCOL if(version > 0 && com_legacyprotocol->integer == version) compat = qtrue; @@ -351,42 +374,54 @@ void SV_DirectConnect( netadr_t from ) { Info_SetValueForKey( userinfo, "ip", ip ); // see if the challenge is valid (LAN clients don't need to challenge) - if ( !NET_IsLocalAddress (from) ) { - int ping; + if (!NET_IsLocalAddress(from)) + { + int ping; + challenge_t *challengeptr; - for (i=0 ; iwasrefused) + { + // Return silently, so that error messages written by the server keep being displayed. return; } - ping = svs.time - svs.challenges[i].pingTime; - Com_Printf( "Client %i connecting with %i challenge ping\n", i, ping ); - svs.challenges[i].connected = qtrue; + ping = svs.time - challengeptr->pingTime; // never reject a LAN client based on ping if ( !Sys_IsLANAddress( from ) ) { if ( sv_minPing->value && ping < sv_minPing->value ) { - // don't let them keep trying until they get a big delay - NET_OutOfBandPrint( NS_SERVER, from, "droperror\nServer is for high pings only\n" ); + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for high pings only\n" ); Com_DPrintf ("Client %i rejected on a too low ping\n", i); - // reset the address otherwise their ping will keep increasing - // with each connect message and they'd eventually be able to connect - svs.challenges[i].adr.port = 0; + challengeptr->wasrefused = qtrue; return; } if ( sv_maxPing->value && ping > sv_maxPing->value ) { - NET_OutOfBandPrint( NS_SERVER, from, "droperror\nServer is for low pings only\n" ); + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for low pings only\n" ); Com_DPrintf ("Client %i rejected on a too high ping\n", i); + challengeptr->wasrefused = qtrue; return; } } + + Com_Printf("Client %i connecting with %i challenge ping\n", i, ping); + challengeptr->connected = qtrue; } newcl = &temp; @@ -425,7 +460,7 @@ void SV_DirectConnect( netadr_t from ) { // check for privateClient password password = Info_ValueForKey( userinfo, "password" ); - if ( !strcmp( password, sv_privatePassword->string ) ) { + if ( *password && !strcmp( password, sv_privatePassword->string ) ) { startIndex = 0; } else { // skip past the reserved slots @@ -499,10 +534,9 @@ gotnewcl: // get the game a chance to reject this connection or modify the userinfo denied = ge->ClientConnect( clientNum, qtrue, qfalse ); - if ( denied ) { NET_OutOfBandPrint( NS_SERVER, from, "droperror\n%s\n", denied ); - Com_DPrintf( "Game rejected a connection: %s.\n", denied ); + Com_DPrintf ( "Game rejected a connection: %s.\n", denied ); return; } @@ -514,7 +548,7 @@ gotnewcl: SV_UserinfoChanged( newcl ); // send the connect packet to the client - NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); + NET_OutOfBandPrint(NS_SERVER, from, "connectResponse %d", challenge); Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); @@ -554,23 +588,23 @@ SV_FreeClient Destructor for data allocated in a client structure ===================== */ -void SV_FreeClient(client_t* client) +void SV_FreeClient(client_t *client) { #ifdef USE_VOIP - int index; - - for (index = client->queuedVoipIndex; index < client->queuedVoipPackets; index++) - { - index %= ARRAY_LEN(client->voipPacket); - - Z_Free(client->voipPacket[index]); - } - - client->queuedVoipPackets = 0; + int index; + + for(index = client->queuedVoipIndex; index < client->queuedVoipPackets; index++) + { + index %= ARRAY_LEN(client->voipPacket); + + Z_Free(client->voipPacket[index]); + } + + client->queuedVoipPackets = 0; #endif - SV_Netchan_FreeQueue(client); - SV_CloseDownload(client); + SV_Netchan_FreeQueue(client); + SV_CloseDownload(client); } /* @@ -585,35 +619,32 @@ or crashing -- SV_FinalMessage() will handle that void SV_DropClient( client_t *drop, const char *reason ) { int i; challenge_t *challenge; + const qboolean isBot = drop->netchan.remoteAddress.type == NA_BOT; if ( drop->state == CS_ZOMBIE ) { return; // already dropped } - if (drop->netchan.remoteAddress.type != NA_BOT) { + if ( !isBot ) { // see if we already have a challenge for this ip challenge = &svs.challenges[0]; - for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) { - if ( NET_CompareAdr( drop->netchan.remoteAddress, challenge->adr ) ) { - challenge->connected = qfalse; + for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) + { + if(NET_CompareAdr(drop->netchan.remoteAddress, challenge->adr)) + { + Com_Memset(challenge, 0, sizeof(*challenge)); break; } } - } + } - // Free all allocated data on the client structure - SV_FreeClient(drop); + // Free all allocated data on the client structure + SV_FreeClient(drop); // tell everyone why they got dropped SV_SendServerCommand( NULL, "print \"%s %s\n\"", drop->name, reason ); - - if (drop->download) { - FS_FCloseFile( drop->download ); - drop->download = 0; - } - // call the prog function for removing a client // this will remove the body, among other things ge->ClientDisconnect( ( gentity_t * )SV_GentityNum( drop - svs.clients ) ); @@ -621,11 +652,18 @@ void SV_DropClient( client_t *drop, const char *reason ) { // add the disconnect command SV_SendServerCommand( drop, "disconnect \"%s\"", reason); + if ( isBot ) { + //SV_BotFreeClient( drop - svs.clients ); + + // bots shouldn't go zombie, as there's no real net connection. + drop->state = CS_FREE; + } else { + Com_DPrintf( "Going to CS_ZOMBIE for %s\n", drop->name ); + drop->state = CS_ZOMBIE; // become free in a few seconds + } + // nuke user info SV_SetUserinfo( drop - svs.clients, "" ); - - Com_DPrintf( "Going to CS_ZOMBIE for %s\n", drop->name ); - drop->state = CS_ZOMBIE; // become free in a few seconds // if this was the last client on the server, send a heartbeat // to the master so it is known the server is empty @@ -652,7 +690,7 @@ It will be resent if the client acknowledges a later message but has the wrong gamestate. ================ */ -void SV_SendClientGameState( client_t *client ) { +static void SV_SendClientGameState( client_t *client ) { int start; entityState_t *base, nullstate; msg_t msg; @@ -690,7 +728,7 @@ void SV_SendClientGameState( client_t *client ) { SV_UpdateServerCommandsToClient( client, &msg ); // send the gamestate - MSG_WriteSVC( &msg, svc_gamestate ); + MSG_WriteSVC( &msg, svc_gamestate ); MSG_WriteLong( &msg, client->reliableSequence ); // write the configstrings @@ -719,7 +757,7 @@ void SV_SendClientGameState( client_t *client ) { MSG_WriteLong( &msg, client - svs.clients); // write the checksum feed - MSG_WriteLong( &msg, 0); + MSG_WriteLong( &msg, sv.checksumFeed); // write the server frametime to the client (only on TA/TT) MSG_WriteServerFrameTime(&msg, sv.frameTime); @@ -754,7 +792,7 @@ void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) { client->deltaMessage = -1; client->lastSnapshotTime = 0; // generate a snapshot immediately - if (cmd) + if(cmd) memcpy(&client->lastUsercmd, cmd, sizeof(client->lastUsercmd)); else memset(&client->lastUsercmd, '\0', sizeof(client->lastUsercmd)); @@ -798,7 +836,7 @@ static void SV_CloseDownload( client_t *cl ) { // Free the temporary buffer space for (i = 0; i < MAX_DOWNLOAD_WINDOW; i++) { if (cl->downloadBlocks[i]) { - Z_Free( cl->downloadBlocks[i] ); + Z_Free(cl->downloadBlocks[i]); cl->downloadBlocks[i] = NULL; } } @@ -812,7 +850,7 @@ SV_StopDownload_f Abort a download if in progress ================== */ -void SV_StopDownload_f( client_t *cl ) { +static void SV_StopDownload_f( client_t *cl ) { if (*cl->downloadName) Com_DPrintf( "clientDownload: %d : file \"%s\" aborted\n", (int) (cl - svs.clients), cl->downloadName ); @@ -826,7 +864,10 @@ SV_DoneDownload_f Downloads are finished ================== */ -void SV_DoneDownload_f( client_t *cl ) { +static void SV_DoneDownload_f( client_t *cl ) { + if ( cl->state == CS_ACTIVE ) + return; + Com_DPrintf( "clientDownload: %s Done\n", cl->name); // resend the game state to update any clients that entered during the download SV_SendClientGameState(cl); @@ -840,7 +881,7 @@ The argument will be the last acknowledged block from the client, it should be the same as cl->downloadClientBlock ================== */ -void SV_NextDownload_f( client_t *cl ) +static void SV_NextDownload_f( client_t *cl ) { int block = atoi( Cmd_Argv(1) ); @@ -869,7 +910,7 @@ void SV_NextDownload_f( client_t *cl ) SV_BeginDownload_f ================== */ -void SV_BeginDownload_f( client_t *cl ) { +static void SV_BeginDownload_f( client_t *cl ) { // Kill any existing download SV_CloseDownload( cl ); @@ -884,15 +925,13 @@ void SV_BeginDownload_f( client_t *cl ) { SV_WriteDownloadToClient Check to see if the client wants a file, open it if needed and start pumping the client -Fill up msg with data +Fill up msg with data, return number of download blocks added ================== */ -int SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) +int SV_WriteDownloadToClient(client_t *cl, msg_t *msg) { int curindex; - int rate; - int blockspersnap; - int idPack = 0, missionPack = 0, unreferenced = 1; + int unreferenced = 1; char errorMessage[1024]; char pakbuf[MAX_QPATH], *pakptr; int numRefPaks; @@ -900,10 +939,16 @@ int SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) if (!*cl->downloadName) return 0; // Nothing being downloaded - if (!cl->download) { + if(!cl->download) + { + qboolean idPack = qfalse; + #ifndef STANDALONE + qboolean missionPack = qfalse; + #endif + // Chop off filename extension. Com_sprintf(pakbuf, sizeof(pakbuf), "%s", cl->downloadName); - pakptr = Q_strrchr(pakbuf, '.'); + pakptr = strrchr(pakbuf, '.'); if(pakptr) { @@ -927,8 +972,11 @@ int SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) // now that we know the file is referenced, // check whether it's legal to download it. - missionPack = FS_idPak(pakbuf, "missionpack"); - idPack = missionPack || FS_idPak(pakbuf, BASEGAME); +#ifndef STANDALONE + //missionPack = FS_idPak(pakbuf, BASETA, NUM_TA_PAKS); + //idPack = missionPack; +#endif + //idPack = idPack || FS_idPak(pakbuf, BASEGAME, NUM_ID_PAKS); break; } @@ -936,11 +984,13 @@ int SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) } } + cl->download = 0; + // We open the file here if ( !(sv_allowDownload->integer & DLF_ENABLE) || (sv_allowDownload->integer & DLF_NO_UDP) || idPack || unreferenced || - ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) <= 0 ) { + ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) < 0 ) { // cannot auto-download file if(unreferenced) { @@ -949,18 +999,22 @@ int SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) } else if (idPack) { Com_Printf("clientDownload: %d : \"%s\" cannot download id pk3 files\n", (int) (cl - svs.clients), cl->downloadName); - if (missionPack) { +#ifndef STANDALONE + if(missionPack) + { Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload Team Arena file \"%s\"\n" "The Team Arena mission pack can be found in your local game store.", cl->downloadName); } - else { + else +#endif + { Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload id pk3 file \"%s\"", cl->downloadName); } } else if ( !(sv_allowDownload->integer & DLF_ENABLE) || (sv_allowDownload->integer & DLF_NO_UDP) ) { - Com_Printf("clientDownload: %d : \"%s\" download disabled", (int) (cl - svs.clients), cl->downloadName); + Com_Printf("clientDownload: %d : \"%s\" download disabled\n", (int) (cl - svs.clients), cl->downloadName); if (sv_pure->integer) { Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" "You will need to get this file elsewhere before you " @@ -983,6 +1037,10 @@ int SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) MSG_WriteString( msg, errorMessage ); *cl->downloadName = 0; + + if(cl->download) + FS_FCloseFile(cl->download); + return 1; } @@ -1001,7 +1059,7 @@ int SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW); if (!cl->downloadBlocks[curindex]) - cl->downloadBlocks[curindex] = Z_Malloc( MAX_DOWNLOAD_BLKSIZE ); + cl->downloadBlocks[curindex] = Z_Malloc(MAX_DOWNLOAD_BLKSIZE); cl->downloadBlockSize[curindex] = FS_Read( cl->downloadBlocks[curindex], MAX_DOWNLOAD_BLKSIZE, cl->download ); @@ -1028,75 +1086,42 @@ int SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) cl->downloadEOF = qtrue; // We have added the EOF block } - // Loop up to window size times based on how many blocks we can fit in the - // client snapMsec and rate + if (cl->downloadClientBlock == cl->downloadCurrentBlock) + return 0; // Nothing to transmit - // based on the rate, how many bytes can we fit in the snapMsec time of the client - // normal rate / snapshotMsec calculation - rate = cl->rate; - if ( sv_maxRate->integer ) { - if ( sv_maxRate->integer < 1000 ) { - Cvar_Set( "sv_MaxRate", "1000" ); - } - if ( sv_maxRate->integer < rate ) { - rate = sv_maxRate->integer; - } + // Write out the next section of the file, if we have already reached our window, + // automatically start retransmitting + if (cl->downloadXmitBlock == cl->downloadCurrentBlock) + { + // We have transmitted the complete window, should we start resending? + if (svs.time - cl->downloadSendTime > 1000) + cl->downloadXmitBlock = cl->downloadClientBlock; + else + return 0; } - if (!rate) { - blockspersnap = 1; - } else { - blockspersnap = ( (rate * cl->snapshotMsec) / 1000 + MAX_DOWNLOAD_BLKSIZE ) / - MAX_DOWNLOAD_BLKSIZE; - } + // Send current block + curindex = (cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW); - if (blockspersnap < 0) - blockspersnap = 1; + MSG_WriteByte( msg, svc_download ); + MSG_WriteShort( msg, cl->downloadXmitBlock ); - while (blockspersnap--) { + // block zero is special, contains file size + if ( cl->downloadXmitBlock == 0 ) + MSG_WriteLong( msg, cl->downloadSize ); - // Write out the next section of the file, if we have already reached our window, - // automatically start retransmitting + MSG_WriteShort( msg, cl->downloadBlockSize[curindex] ); - if (cl->downloadClientBlock == cl->downloadCurrentBlock) - return 0; // Nothing to transmit + // Write the block + if(cl->downloadBlockSize[curindex]) + MSG_WriteData(msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex]); - if (cl->downloadXmitBlock == cl->downloadCurrentBlock) { - // We have transmitted the complete window, should we start resending? + Com_DPrintf( "clientDownload: %d : writing block %d\n", (int) (cl - svs.clients), cl->downloadXmitBlock ); - //FIXME: This uses a hardcoded one second timeout for lost blocks - //the timeout should be based on client rate somehow - if (svs.time - cl->downloadSendTime > 1000) - cl->downloadXmitBlock = cl->downloadClientBlock; - else - return 0; - } - - // Send current block - curindex = (cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW); - - MSG_WriteByte( msg, svc_download ); - MSG_WriteShort( msg, cl->downloadXmitBlock ); - - // block zero is special, contains file size - if ( cl->downloadXmitBlock == 0 ) - MSG_WriteLong( msg, (int)cl->downloadSize ); - - MSG_WriteShort( msg, ( short )cl->downloadBlockSize[curindex] ); - - // Write the block - if ( cl->downloadBlockSize[curindex] ) { - MSG_WriteData( msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex] ); - } - - Com_DPrintf( "clientDownload: %d : writing block %d\n", (int) (cl - svs.clients), cl->downloadXmitBlock ); - - // Move on to the next block - // It will get sent with next snap shot. The rate will keep us in line. - cl->downloadXmitBlock++; - - cl->downloadSendTime = svs.time; - } + // Move on to the next block + // It will get sent with next snap shot. The rate will keep us in line. + cl->downloadXmitBlock++; + cl->downloadSendTime = svs.time; return 1; } @@ -1375,7 +1400,7 @@ void SV_UserinfoChanged( client_t *cl ) { char *val; char *ip; int i; - size_t len; + int len; // name for C code Q_strncpyz( cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name) ); @@ -1472,17 +1497,49 @@ SV_UpdateUserinfo_f ================== */ static void SV_UpdateUserinfo_f( client_t *cl ) { - Q_strncpyz( cl->userinfo, Cmd_Argv( 1 ), sizeof( cl->userinfo ) ); + Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) ); SV_UserinfoChanged( cl ); - // call prog code to allow overrides ge->ClientUserinfoChanged( ( gentity_t * )SV_GentityNum( cl - svs.clients ), cl->userinfo ); } + +#ifdef USE_VOIP +static +void SV_UpdateVoipIgnore(client_t *cl, const char *idstr, qboolean ignore) +{ + if ((*idstr >= '0') && (*idstr <= '9')) { + const int id = atoi(idstr); + if ((id >= 0) && (id < MAX_CLIENTS)) { + cl->ignoreVoipFromClient[id] = ignore; + } + } +} + +/* +================== +SV_Voip_f +================== +*/ +static void SV_Voip_f( client_t *cl ) { + const char *cmd = Cmd_Argv(1); + if (strcmp(cmd, "ignore") == 0) { + SV_UpdateVoipIgnore(cl, Cmd_Argv(2), qtrue); + } else if (strcmp(cmd, "unignore") == 0) { + SV_UpdateVoipIgnore(cl, Cmd_Argv(2), qfalse); + } else if (strcmp(cmd, "muteall") == 0) { + cl->muteAllVoip = qtrue; + } else if (strcmp(cmd, "unmuteall") == 0) { + cl->muteAllVoip = qfalse; + } +} +#endif + + typedef struct { char *name; - void ( *func )( client_t *cl ); + void (*func)( client_t *cl ); } ucmd_t; static ucmd_t ucmds[] = { @@ -1495,6 +1552,10 @@ static ucmd_t ucmds[] = { {"stopdl", SV_StopDownload_f}, {"donedl", SV_DoneDownload_f}, +#ifdef USE_VOIP + {"voip", SV_Voip_f}, +#endif + {NULL, NULL} }; @@ -1533,7 +1594,8 @@ void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ) { if (clientOK) { // pass unknown strings to the game - if (!u->name && sv.state == SS_GAME) { + if (!u->name && sv.state == SS_GAME && (cl->state == CS_ACTIVE || cl->state == CS_PRIMED)) { + Cmd_Args_Sanitize(); ge->ClientCommand( ( gentity_t * )SV_GentityNum( cl - svs.clients ) ); if (ge->errorMessage) @@ -1566,6 +1628,7 @@ static qboolean SV_ClientCommand( client_t *cl, msg_t *msg ) { } if( developer->integer == 2 ) { + // don't spam the console with client commands in production Com_DPrintf( "clientCommand: %s : %i : %s\n", cl->name, seq, s ); } @@ -1582,9 +1645,10 @@ static qboolean SV_ClientCommand( client_t *cl, msg_t *msg ) { // the command, we will stop processing the rest of the packet, // including the usercmd. This causes flooders to lag themselves // but not other people - // We don't do this when the client hasn't been active yet since its + // We don't do this when the client hasn't been active yet since it's // normal to spam a lot of commands when downloading - if ( cl->state >= CS_ACTIVE && + if ( !com_cl_running->integer && + cl->state >= CS_ACTIVE && sv_floodProtect->integer && svs.time < cl->nextReliableTime ) { // ignore any other text messages from this client but let them keep playing @@ -1609,8 +1673,7 @@ static qboolean SV_ClientCommand( client_t *cl, msg_t *msg ) { // Actual execution of the command SV_ExecuteClientCommand( cl, s, clientOk ); - // continue procesing - return qtrue; + return qtrue; // continue procesing } @@ -1624,8 +1687,7 @@ SV_ClientThink Also called by bot code ================== */ -void SV_ClientThink( client_t *cl, usercmd_t *cmd ) -{ +void SV_ClientThink (client_t *cl, usercmd_t *cmd) { const char *err; cl->lastUsercmd = *cmd; @@ -1686,7 +1748,7 @@ static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) { // also use the message acknowledge key ^= cl->messageAcknowledge; // also use the last acknowledged server command in the key - key ^= Com_HashKey(cl->reliableCommands[ cl->reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ], 32); + key ^= MSG_HashKey(cl->reliableCommands[ cl->reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ], 32); MSG_ReadDeltaEyeInfo( msg, &cl->lastEyeinfo, &cl->lastEyeinfo ); @@ -1713,7 +1775,7 @@ static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) { SV_SendClientGameState( cl ); } return; - } + } // if this is the first usercmd we have received // this gamestate, put the client into the world @@ -1721,9 +1783,9 @@ static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) { SV_ClientEnterWorld( cl, &cmds[0] ); // the moves can be processed normaly } - - // a bad cp command was sent, drop the client - if (g_gametype->integer != GT_SINGLE_PLAYER && sv_pure->integer != 0 && !com_sv_running->integer && cl->pureAuthentic == 0) { + + // a bad cp command was sent, drop the client + if (sv_pure->integer != 0 && cl->pureAuthentic == 0) { SV_DropClient( cl, "Cannot validate pure client!"); return; } @@ -1750,12 +1812,122 @@ static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) { if ( cmds[i].serverTime <= cl->lastUsercmd.serverTime ) { continue; } - - SV_ClientThink( cl, &cmds[ i ] ); + SV_ClientThink (cl, &cmds[ i ]); } } +#ifdef USE_VOIP +/* +================== +SV_ShouldIgnoreVoipSender + +Blocking of voip packets based on source client +================== +*/ + +static qboolean SV_ShouldIgnoreVoipSender(const client_t *cl) +{ + if (!sv_voip->integer) + return qtrue; // VoIP disabled on this server. + else if (!cl->hasVoip) // client doesn't have VoIP support?! + return qtrue; + + // !!! FIXME: implement player blacklist. + + return qfalse; // don't ignore. +} + +static +void SV_UserVoip(client_t *cl, msg_t *msg, qboolean ignoreData) +{ + int sender, generation, sequence, frames, packetsize; + uint8_t recips[(MAX_CLIENTS + 7) / 8]; + int flags; + byte encoded[sizeof(cl->voipPacket[0]->data)]; + client_t *client = NULL; + voipServerPacket_t *packet = NULL; + int i; + + sender = cl - svs.clients; + generation = MSG_ReadByte(msg); + sequence = MSG_ReadLong(msg); + frames = MSG_ReadByte(msg); + MSG_ReadData(msg, recips, sizeof(recips)); + flags = MSG_ReadByte(msg); + packetsize = MSG_ReadShort(msg); + + if (msg->readcount > msg->cursize) + return; // short/invalid packet, bail. + + if (packetsize > sizeof (encoded)) { // overlarge packet? + int bytesleft = packetsize; + while (bytesleft) { + int br = bytesleft; + if (br > sizeof (encoded)) + br = sizeof (encoded); + MSG_ReadData(msg, encoded, br); + bytesleft -= br; + } + return; // overlarge packet, bail. + } + + MSG_ReadData(msg, encoded, packetsize); + + if (ignoreData || SV_ShouldIgnoreVoipSender(cl)) + return; // Blacklisted, disabled, etc. + + // !!! FIXME: see if we read past end of msg... + + // !!! FIXME: reject if not opus data. + // !!! FIXME: decide if this is bogus data? + + // decide who needs this VoIP packet sent to them... + for (i = 0, client = svs.clients; i < sv_maxclients->integer ; i++, client++) { + if (client->state != CS_ACTIVE) + continue; // not in the game yet, don't send to this guy. + else if (i == sender) + continue; // don't send voice packet back to original author. + else if (!client->hasVoip) + continue; // no VoIP support, or unsupported protocol + else if (client->muteAllVoip) + continue; // client is ignoring everyone. + else if (client->ignoreVoipFromClient[sender]) + continue; // client is ignoring this talker. + else if (*cl->downloadName) // !!! FIXME: possible to DoS? + continue; // no VoIP allowed if downloading, to save bandwidth. + + if(Com_IsVoipTarget(recips, sizeof(recips), i)) + flags |= VOIP_DIRECT; + else + flags &= ~VOIP_DIRECT; + + if (!(flags & (VOIP_SPATIAL | VOIP_DIRECT))) + continue; // not addressed to this player. + + // Transmit this packet to the client. + if (client->queuedVoipPackets >= ARRAY_LEN(client->voipPacket)) { + Com_Printf("Too many VoIP packets queued for client #%d\n", i); + continue; // no room for another packet right now. + } + + packet = Z_Malloc(sizeof(*packet)); + packet->sender = sender; + packet->frames = frames; + packet->len = packetsize; + packet->generation = generation; + packet->sequence = sequence; + packet->flags = flags; + memcpy(packet->data, encoded, packetsize); + + client->voipPacket[(client->queuedVoipIndex + client->queuedVoipPackets) % ARRAY_LEN(client->voipPacket)] = packet; + client->queuedVoipPackets++; + } +} +#endif + + + /* =========================================================================== @@ -1777,7 +1949,7 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { MSG_Bitstream(msg); - serverId = MSG_ReadLong(msg); + serverId = MSG_ReadLong( msg ); cl->serverIdAcknowledge = serverId; cl->messageAcknowledge = MSG_ReadLong( msg ); @@ -1795,7 +1967,7 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { // NOTE: when the client message is fux0red the acknowledgement numbers // can be out of range, this could cause the server to send thousands of server // commands which the server thinks are not yet acknowledged in SV_UpdateServerCommandsToClient - if (cl->reliableAcknowledge < cl->reliableSequence - MAX_RELIABLE_COMMANDS) { + if ((cl->reliableSequence - cl->reliableAcknowledge >= MAX_RELIABLE_COMMANDS) || (cl->reliableSequence - cl->reliableAcknowledge < 0)) { // usually only hackers create messages like this // it is more annoying for them to let them hanging #ifndef NDEBUG @@ -1816,24 +1988,36 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { // don't drop as long as previous command was a nextdl, after a dl is done, downloadName is set back to "" // but we still need to read the next message to move to next download or send gamestate // I don't like this hack though, it must have been working fine at some point, suspecting the fix is somewhere else - if ( serverId != sv.serverId && !*cl->downloadName ) { + if ( serverId != sv.serverId && !*cl->downloadName && !strstr(cl->lastClientCommandString, "nextdl") ) { + if ( serverId >= sv.restartedServerId && serverId < sv.serverId ) { // TTimo - use a comparison here to catch multiple map_restart + // they just haven't caught the map_restart yet + //Com_DPrintf("%s : ignoring pre map_restart / outdated client message\n", cl->name); + return; + } // if we can tell that the client has dropped the last // gamestate we sent them, resend it - // also, don't resend the gamestate if the server just restarted - if ( serverId != sv.restartedServerId && cl->messageAcknowledge > cl->gamestateMessageNum ) { + if ( cl->state != CS_ACTIVE && cl->messageAcknowledge > cl->gamestateMessageNum ) { Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); SV_SendClientGameState( cl ); } return; } + // this client has acknowledged the new gamestate so it's + // safe to start sending it the real time again + if( cl->oldServerTime && serverId == sv.serverId ){ + Com_DPrintf( "%s acknowledged gamestate\n", cl->name ); + cl->oldServerTime = 0; + } + // read optional clientCommand strings do { c = MSG_ReadByte( msg ); - //Com_DPrintf( "SV_ExecuteClientMessage: %i\n", c ); + if ( c == clc_EOF ) { break; } + if ( c != clc_clientCommand ) { break; } @@ -1845,6 +2029,22 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { } } while ( 1 ); + // skip legacy speex voip data + if ( c == clc_voipSpeex ) { +#ifdef USE_VOIP + SV_UserVoip( cl, msg, qtrue ); + c = MSG_ReadByte( msg ); +#endif + } + + // read optional voip data + if ( c == clc_voipOpus ) { +#ifdef USE_VOIP + SV_UserVoip( cl, msg, qfalse ); + c = MSG_ReadByte( msg ); +#endif + } + // read the usercmd_t if ( c == clc_move ) { SV_UserMove( cl, msg, qtrue ); diff --git a/code/server/sv_init.c b/code/server/sv_init.c index 145e3548..62c58f58 100644 --- a/code/server/sv_init.c +++ b/code/server/sv_init.c @@ -73,12 +73,11 @@ SV_SetConfigstring =============== */ void SV_SetConfigstring (int index, const char *val) { - int i; - size_t len; + int i; client_t *client; if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { - Com_Error( ERR_DROP, "SV_SetConfigstring: bad index %i\n", index ); + Com_Error (ERR_DROP, "SV_SetConfigstring: bad index %i\n", index); } if ( !val ) { @@ -98,7 +97,7 @@ void SV_SetConfigstring (int index, const char *val) { // spawning a new server if (sv.state == SS_LOADING2 || sv.state == SS_GAME || sv.restarting ) { - // send the data to all relevent clients + // send the data to all relevant clients for (i = 0, client = svs.clients; i < svs.iNumClients ; i++, client++) { if ( client->state < CS_ACTIVE ) { if ( client->state == CS_PRIMED ) @@ -111,7 +110,6 @@ void SV_SetConfigstring (int index, const char *val) { } - len = strlen( val ); SV_SendConfigstring(client, index); } } @@ -316,7 +314,7 @@ void SV_UpdateConfigstrings(client_t *client) { int index; - for( index = 0; index <= MAX_CONFIGSTRINGS; index++ ) { + for( index = 0; index < MAX_CONFIGSTRINGS; index++ ) { // if the CS hasn't changed since we went to CS_PRIMED, ignore if(!client->csUpdated[index]) continue; @@ -452,6 +450,9 @@ void SV_Startup( void ) { SV_InitGamespy(); Cvar_Set( "sv_running", "1" ); + + // Join the ipv6 multicast group now that a map is running so clients can scan for us on the local network. + NET_JoinMulticast6(); } /* @@ -478,10 +479,10 @@ SV_ChangeMaxClients ================== */ void SV_ChangeMaxClients( void ) { - int oldMaxClients; - int i; + int oldMaxClients; + int i; client_t *oldClients; - int count; + int count; // get the highest client number in use count = 0; @@ -538,6 +539,20 @@ void SV_ClearServer(void) { sv.frameTime = 1.0 / sv_fps->value; } +/* +================ +SV_TouchFile +================ +*/ +static void SV_TouchFile( const char *filename ) { + fileHandle_t f; + + FS_FOpenFileRead( filename, &f, qfalse, qtrue ); + if ( f ) { + FS_FCloseFile( f ); + } +} + /* ================ SV_SpawnServer @@ -980,15 +995,16 @@ SV_Init Only called at main exe startup, not for each game =============== */ -void SV_Init( void ) { +void SV_Init (void) +{ int index; SV_AddOperatorCommands(); // serverinfo vars - Cvar_Get( "dmflags", "0", CVAR_SERVERINFO ); - Cvar_Get( "fraglimit", "20", CVAR_SERVERINFO ); - Cvar_Get( "timelimit", "0", CVAR_SERVERINFO ); + Cvar_Get ("dmflags", "0", CVAR_SERVERINFO); + Cvar_Get ("fraglimit", "20", CVAR_SERVERINFO); + Cvar_Get ("timelimit", "0", CVAR_SERVERINFO); g_gametype = Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_LATCH ); g_gametypestring = Cvar_Get( "g_gametypestring", "0", CVAR_SERVERINFO | CVAR_LATCH ); Cvar_Get( "sv_keywords", "", CVAR_SERVERINFO ); @@ -1013,12 +1029,11 @@ void SV_Init( void ) { // sv_pure is disabled by default in mohaa // the problem is because of difference in pak files between languages sv_pure = Cvar_Get ("sv_pure", "0", CVAR_SYSTEMINFO ); - #ifdef USE_VOIP +#ifdef USE_VOIP sv_voip = Cvar_Get("sv_voip", "1", CVAR_LATCH); Cvar_CheckRange(sv_voip, 0, 1, qtrue); sv_voipProtocol = Cvar_Get("sv_voipProtocol", sv_voip->integer ? "opus" : "", CVAR_SYSTEMINFO | CVAR_ROM ); #endif - Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM ); Cvar_Get ("sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); Cvar_Get ("sv_referencedPaks", "", CVAR_SYSTEMINFO | CVAR_ROM ); @@ -1129,8 +1144,14 @@ void SV_Shutdown( const char *finalmsg ) { SV_ClearServer(); // free server static data - if ( svs.clients ) { - Z_Free( svs.clients ); + if(svs.clients) + { + int index; + + for(index = 0; index < sv_maxclients->integer; index++) + SV_FreeClient(&svs.clients[index]); + + Z_Free(svs.clients); } Com_Memset( &svs, 0, sizeof( svs ) ); diff --git a/code/server/sv_main.c b/code/server/sv_main.c index a97f9f9b..19944dd9 100644 --- a/code/server/sv_main.c +++ b/code/server/sv_main.c @@ -223,7 +223,7 @@ void QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) { Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) ); } - // send the data to all relevent clients + // send the data to all relevant clients for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) { SV_AddServerCommand( client, (char *)message ); } @@ -290,6 +290,167 @@ 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; + 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 +================ +*/ +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 || interval < 0 ) { + 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 +================ +*/ +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 @@ -314,6 +475,24 @@ void SVC_Status( netadr_t from ) { return; } + // 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( &outboundLeakyBucket, 10, 100 ) ) { + Com_DPrintf( "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) + return; + strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) ); // echo back the parameter to status. so master servers can use it as a challenge @@ -361,6 +540,20 @@ void SVC_Info( netadr_t from ) { return; } + // Prevent using getinfo as an amplifier + if ( SVC_RateLimitAddress( from, 10, 1000 ) ) { + Com_DPrintf( "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 ) ) { + Com_DPrintf( "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. @@ -394,6 +587,12 @@ void SVC_Info( netadr_t from ) { Info_SetValueForKey( infostring, "gametypestring", g_gametypestring->string ); 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) ); } @@ -417,7 +616,7 @@ SVC_FlushRedirect ================ */ -void SV_FlushRedirect( char *outputbuf ) { +static void SV_FlushRedirect( char *outputbuf ) { NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf ); } @@ -430,26 +629,32 @@ Shift down the remaining args Redirect all printfs =============== */ -void SVC_RemoteCommand( netadr_t from, msg_t *msg ) { +static void SVC_RemoteCommand( netadr_t from, msg_t *msg ) { qboolean valid; - unsigned int time; char remaining[1024]; // TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc. // (OOB messages are the bottleneck here) #define SV_OUTPUTBUF_LENGTH (1024 - 16) char sv_outputbuf[SV_OUTPUTBUF_LENGTH]; - static unsigned int lasttime = 0; char *cmd_aux; - // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534 - time = Com_Milliseconds(); - if ( (unsigned)( time - lasttime ) < 500u ) { + // Prevent using rcon as an amplifier and make dictionary attacks impractical + if ( SVC_RateLimitAddress( from, 10, 1000 ) ) { + Com_DPrintf( "SVC_RemoteCommand: rate limit from %s exceeded, dropping request\n", + NET_AdrToString( from ) ); return; } - lasttime = time; if ( !strlen( sv_rconPassword->string ) || strcmp (Cmd_Argv(1), sv_rconPassword->string) ) { + static leakyBucket_t bucket; + + // Make DoS via rcon impractical + if ( SVC_RateLimit( &bucket, 10, 1000 ) ) { + Com_DPrintf( "SVC_RemoteCommand: rate limit exceeded, dropping request\n" ); + return; + } + valid = qfalse; Com_Printf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) ); } else { @@ -520,11 +725,11 @@ void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) { Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c); if (!Q_stricmp(c, "getstatus")) { - SVC_Status( from ); - } else if (!Q_stricmp(c, "getinfo")) { + SVC_Status( from ); + } else if (!Q_stricmp(c, "getinfo")) { SVC_Info( from ); } else if (!Q_stricmp(c, "getchallenge")) { - SV_GetChallenge( from ); + SV_GetChallenge(from); } else if (!Q_stricmp(c, "connect")) { SV_DirectConnect( from ); } else if (!Q_stricmp(c, "authorizeThis")) { @@ -536,8 +741,8 @@ void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) { // server disconnect messages when their new server sees our final // sequenced messages to the old client } else { - Com_DPrintf ("bad connectionless packet from %s:\n%s\n" - , NET_AdrToString (from), s); + Com_DPrintf ("bad connectionless packet from %s:\n%s\n", + NET_AdrToString (from), s); } } @@ -562,7 +767,7 @@ void SV_PacketEvent( netadr_t from, msg_t *msg ) { // read the qport out of the message so we can fix up // stupid address translating routers MSG_BeginReadingOOB( msg ); - i=MSG_ReadLong( msg ); // sequence number + MSG_ReadLong( msg ); // sequence number qport = MSG_ReadShort( msg ) & 0xffff; // find which client the message is from @@ -593,7 +798,7 @@ void SV_PacketEvent( netadr_t from, msg_t *msg ) { // 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.lastTime; // don't timeout + cl->lastPacketTime = svs.time; // don't timeout SV_ExecuteClientMessage( cl, msg ); } } @@ -613,7 +818,7 @@ SV_CalcPings Updates the cl->ping variables =================== */ -void SV_CalcPings( void ) { +static void SV_CalcPings( void ) { int i, j; client_t *cl; int total, count; @@ -673,7 +878,7 @@ for a few seconds to make sure any final reliable message gets resent if necessary ================== */ -void SV_CheckTimeouts( void ) { +static void SV_CheckTimeouts( void ) { int i; client_t *cl; int droppoint; @@ -799,6 +1004,11 @@ void SV_Frame( int msec ) { 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; } @@ -1010,42 +1220,42 @@ a client based on its rate settings #define UDPIP_HEADER_SIZE 28 #define UDPIP6_HEADER_SIZE 48 -int SV_RateMsec(client_t* client) +int SV_RateMsec(client_t *client) { - int rate, rateMsec; - int messageSize; + int rate, rateMsec; + int messageSize; + + messageSize = client->netchan.lastSentSize; + rate = client->rate; - 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_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 (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; - - rateMsec = messageSize * 1000 / ((int)(rate * com_timescale->value)); - rate = Sys_Milliseconds() - client->netchan.lastSentTime; - - if (rate > rateMsec) - return 0; - else - return rateMsec - rate; + if(client->netchan.remoteAddress.type == NA_IP6) + messageSize += UDPIP6_HEADER_SIZE; + else + messageSize += UDPIP_HEADER_SIZE; + + rateMsec = messageSize * 1000 / ((int) (rate * com_timescale->value)); + rate = Sys_Milliseconds() - client->netchan.lastSentTime; + + if(rate > rateMsec) + return 0; + else + return rateMsec - rate; } /* @@ -1060,70 +1270,70 @@ 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; + 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; + // 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(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(deltaT > 0) + { + if(deltaT < timeVal) + timeVal = deltaT + 1; + } + else + { + numBlocks = SV_SendDownloadMessages(); - if (numBlocks) - { - // There are active downloads - deltaT = Sys_Milliseconds() - dlStart; + if(numBlocks) + { + // There are active downloads + deltaT = Sys_Milliseconds() - dlStart; - delayT = 1000 * numBlocks * MAX_DOWNLOAD_BLKSIZE; - delayT /= sv_dlRate->integer * 1024; + 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; + 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; + dlNextRound = dlStart + deltaT + 1; + } + else + { + dlNextRound = dlStart + delayT; + delayT -= deltaT; - if (delayT < timeVal) - timeVal = delayT; - } - } - } - } - else - { - if (SV_SendDownloadMessages()) - timeVal = 0; - } + if(delayT < timeVal) + timeVal = delayT; + } + } + } + } + else + { + if(SV_SendDownloadMessages()) + timeVal = 0; + } - return timeVal; + return timeVal; } diff --git a/code/server/sv_net_chan.c b/code/server/sv_net_chan.c index 1aa5a266..66832630 100644 --- a/code/server/sv_net_chan.c +++ b/code/server/sv_net_chan.c @@ -259,17 +259,17 @@ void SV_Netchan_Transmit( client_t *client, msg_t *msg) Netchan_SV_Process ================= */ -qboolean SV_Netchan_Process(client_t* client, msg_t* msg) { - int ret; - ret = Netchan_Process(&client->netchan, msg); - if (!ret) - return qfalse; +qboolean SV_Netchan_Process( client_t *client, msg_t *msg ) { + int ret; + ret = Netchan_Process( &client->netchan, msg ); + if (!ret) + return qfalse; #ifdef LEGACY_PROTOCOL - if (client->compat) - SV_Netchan_Decode(client, msg); + if(client->compat) + SV_Netchan_Decode(client, msg); #endif - return qtrue; + return qtrue; } diff --git a/code/server/sv_snapshot.c b/code/server/sv_snapshot.c index 7ac89133..2009947f 100644 --- a/code/server/sv_snapshot.c +++ b/code/server/sv_snapshot.c @@ -91,7 +91,7 @@ static void SV_EmitPacketEntities( clientSnapshot_t *from, clientSnapshot_t *to, if ( newnum == oldnum ) { // delta update from old position // because the force parm is qfalse, this will not result - // in any bytes being emited if the entity has not changed at all + // in any bytes being emitted if the entity has not changed at all MSG_WriteDeltaEntity (msg, oldent, newent, qfalse, sv.frameTime); oldindex++; newindex++; @@ -1049,6 +1049,53 @@ static void SV_BuildClientSnapshot( client_t *client ) { frame->ps.radarInfo = client->radarInfo; } +#ifdef USE_VOIP +/* +================== +SV_WriteVoipToClient + +Check to see if there is any VoIP queued for a client, and send if there is. +================== +*/ +static void SV_WriteVoipToClient(client_t *cl, msg_t *msg) +{ + int totalbytes = 0; + int i; + voipServerPacket_t *packet; + + if(cl->queuedVoipPackets) + { + // Write as many VoIP packets as we reasonably can... + for(i = 0; i < cl->queuedVoipPackets; i++) + { + packet = cl->voipPacket[(i + cl->queuedVoipIndex) % ARRAY_LEN(cl->voipPacket)]; + + if(!*cl->downloadName) + { + totalbytes += packet->len; + if (totalbytes > (msg->maxsize - msg->cursize) / 2) + break; + + MSG_WriteByte(msg, svc_voipOpus); + MSG_WriteShort(msg, packet->sender); + MSG_WriteByte(msg, (byte) packet->generation); + MSG_WriteLong(msg, packet->sequence); + MSG_WriteByte(msg, packet->frames); + MSG_WriteShort(msg, packet->len); + MSG_WriteBits(msg, packet->flags, VOIP_FLAGCNT); + MSG_WriteData(msg, packet->data, packet->len); + } + + Z_Free(packet); + } + + cl->queuedVoipPackets -= i; + cl->queuedVoipIndex += i; + cl->queuedVoipIndex %= ARRAY_LEN(cl->voipPacket); + } +} +#endif + /* ======================= SV_SendMessageToClient @@ -1056,7 +1103,8 @@ SV_SendMessageToClient Called by SV_SendClientSnapshot and SV_SendClientGameState ======================= */ -void SV_SendMessageToClient( msg_t *msg, client_t *client ) { +void SV_SendMessageToClient(msg_t *msg, client_t *client) +{ // record information about the message client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize; client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time; @@ -1088,7 +1136,6 @@ void SV_SendClientSnapshot( client_t *client ) { return; } - memset(msg_buf, 0, sizeof(msg_buf)); MSG_Init (&msg, msg_buf, sizeof(msg_buf)); msg.allowoverflow = qtrue; @@ -1115,6 +1162,10 @@ void SV_SendClientSnapshot( client_t *client ) { // Add any download data if the client is downloading SV_WriteDownloadToClient( client, &msg ); +#ifdef USE_VOIP + SV_WriteVoipToClient( client, &msg ); +#endif + // check for overflow if ( msg.overflowed ) { Com_Printf ("WARNING: msg overflowed for %s\n", client->name); @@ -1130,16 +1181,19 @@ void SV_SendClientSnapshot( client_t *client ) { SV_SendClientMessages ======================= */ -void SV_SendClientMessages( void ) { - int i; +void SV_SendClientMessages(void) +{ + int i; int rate; client_t *c; // send a message to each connected client - for (i=0, c = svs.clients ; i < sv_maxclients->integer ; i++, c++) { - if (!c->state) { + for(i=0; i < sv_maxclients->integer; i++) + { + c = &svs.clients[i]; + + if(!c->state) continue; // not connected - } if(svs.time - c->lastSnapshotTime < c->snapshotMsec * com_timescale->value) continue; // It's not time yet @@ -1171,8 +1225,8 @@ void SV_SendClientMessages( void ) { } // generate and send a new message - SV_SendClientSnapshot(c); - c->lastSnapshotTime = svs.time; + SV_SendClientSnapshot(c); + c->lastSnapshotTime = svs.time; c->rateDelayed = qfalse; } diff --git a/code/server/sv_world.c b/code/server/sv_world.c index 7f19d1c1..879d9d3d 100644 --- a/code/server/sv_world.c +++ b/code/server/sv_world.c @@ -100,7 +100,7 @@ SV_CreateworldSector Builds a uniformly subdivided tree for the given world size =============== */ -worldSector_t *SV_CreateworldSector( int depth, vec3_t mins, vec3_t maxs ) { +static worldSector_t *SV_CreateworldSector( int depth, vec3_t mins, vec3_t maxs ) { worldSector_t *anode; vec3_t size; vec3_t mins1, maxs1, mins2, maxs2; @@ -388,8 +388,8 @@ SV_AreaEntities_r ==================== */ -void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) { - svEntity_t *check, *next; +static void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) { + svEntity_t *check, *next; gentity_t *gcheck; worldSector_t *nodestack[ 8 ]; int iStackPos; @@ -499,7 +499,7 @@ void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, con touch = SV_GentityNum( entityNum ); - Com_Memset( trace, 0, sizeof( trace_t ) ); + Com_Memset(trace, 0, sizeof(trace_t)); // if it doesn't have any brushes of a type we // are looking for, ignore it @@ -527,7 +527,7 @@ SV_ClipMoveToEntities ==================== */ -void SV_ClipMoveToEntities( moveclip_t *clip ) { +static void SV_ClipMoveToEntities( moveclip_t *clip ) { int i, num; int touchlist[MAX_GENTITIES]; gentity_t *touch; @@ -797,7 +797,7 @@ void SV_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const ve moveclip_t clip; int i; - Com_Memset( &clip, 0, sizeof( moveclip_t ) ); + Com_Memset ( &clip, 0, sizeof ( moveclip_t ) ); // clip to world CM_BoxTrace( &clip.trace, start, end, mins, maxs, 0, contentmask, cylinder );