Add ioq3 server fixes and improvement

- Add a rate limit to protect against DoS attacks
- Better IPv6 support
This commit is contained in:
smallmodel 2024-08-31 20:45:05 +02:00
parent 9de71e3529
commit 11f5870a8b
No known key found for this signature in database
GPG key ID: 9F2D623CEDF08512
10 changed files with 966 additions and 427 deletions

View file

@ -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;
}
/* /*
============================================================================= =============================================================================

View file

@ -79,6 +79,8 @@ extern "C" {
#define CLIENT_WINDOW_TITLE PRODUCT_NAME #define CLIENT_WINDOW_TITLE PRODUCT_NAME
#define CLIENT_WINDOW_MIN_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 #define DEMOEXT "dm_" // standard demo extension

View file

@ -87,6 +87,7 @@ void MSG_WriteBigString (msg_t *sb, const char *s);
void MSG_WriteScrambledString(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_WriteScrambledBigString(msg_t* sb, const char* s);
void MSG_WriteAngle16 (msg_t *sb, float f); 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_WriteEntityNum(msg_t* sb, short number);
void MSG_BeginReading (msg_t *sb); void MSG_BeginReading (msg_t *sb);
@ -202,8 +203,8 @@ NET
#define MAX_RELIABLE_COMMANDS 512 // max string commands buffered for restransmit #define MAX_RELIABLE_COMMANDS 512 // max string commands buffered for restransmit
typedef enum { typedef enum {
NA_BOT,
NA_BAD, // an address lookup failed NA_BAD, // an address lookup failed
NA_BOT,
NA_LOOPBACK, NA_LOOPBACK,
NA_BROADCAST, NA_BROADCAST,
NA_IP, NA_IP,
@ -373,7 +374,11 @@ enum clc_ops_e {
clc_move, // [[usercmd_t] clc_move, // [[usercmd_t]
clc_moveNoDelta, // [[usercmd_t] clc_moveNoDelta, // [[usercmd_t]
clc_clientCommand, // [string] message 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, //
}; };
/* /*

View file

@ -21,8 +21,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
// server.h // server.h
#ifndef __SERVER_H__ #pragma once
#define __SERVER_H__
#include "../qcommon/q_shared.h" #include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h" #include "../qcommon/qcommon.h"
@ -43,6 +42,21 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
extern "C" { extern "C" {
#endif #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 { typedef struct svEntity_s {
struct worldSector_s *worldSector; struct worldSector_s *worldSector;
struct svEntity_s *nextEntityInWorldSector; struct svEntity_s *nextEntityInWorldSector;
@ -93,6 +107,10 @@ typedef struct {
int gameClientSize; // will be > sizeof(playerState_t) due to game private data int gameClientSize; // will be > sizeof(playerState_t) due to game private data
} server_t; } server_t;
typedef struct { typedef struct {
int areabytes; int areabytes;
byte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits byte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits
@ -103,7 +121,7 @@ typedef struct {
// order, otherwise the delta compression will fail // order, otherwise the delta compression will fail
int messageSent; // time the message was transmitted int messageSent; // time the message was transmitted
int messageAcked; // time the message was acked int messageAcked; // time the message was acked
size_t messageSize; // used to rate drop packets int messageSize; // used to rate drop packets
} clientSnapshot_t; } clientSnapshot_t;
typedef enum { typedef enum {
@ -129,13 +147,13 @@ typedef struct client_s {
char userinfo[MAX_INFO_STRING]; // name, etc char userinfo[MAX_INFO_STRING]; // name, etc
char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; 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 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 messageAcknowledge;
int gamestateMessageNum; // netchan->outgoingSequence of gamestate int gamestateMessageNum; // netchan->outgoingSequence of gamestate
int challenge; int challenge;
int serverIdAcknowledge; int serverIdAcknowledge;
usercmd_t lastUsercmd; usercmd_t lastUsercmd;
@ -151,21 +169,21 @@ typedef struct client_s {
// downloading // downloading
char downloadName[MAX_QPATH]; // if not empty string, we are downloading char downloadName[MAX_QPATH]; // if not empty string, we are downloading
fileHandle_t download; // file being downloaded fileHandle_t download; // file being downloaded
size_t downloadSize; // total bytes (can't use EOF because of paks) int downloadSize; // total bytes (can't use EOF because of paks)
size_t downloadCount; // bytes sent int downloadCount; // bytes sent
int downloadClientBlock; // last block we sent to the client, awaiting ack int downloadClientBlock; // last block we sent to the client, awaiting ack
int downloadCurrentBlock; // current block number int downloadCurrentBlock; // current block number
int downloadXmitBlock; // last block we xmited int downloadXmitBlock; // last block we xmited
unsigned char *downloadBlocks[MAX_DOWNLOAD_WINDOW]; // the buffers for the download blocks 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 qboolean downloadEOF; // We have sent the EOF block
int downloadSendTime; // time we last got an ack from the client int downloadSendTime; // time we last got an ack from the client
int deltaMessage; // frame last client usercmd message int deltaMessage; // frame last client usercmd message
int nextReliableTime; // svs.time when another reliable command will be allowed int nextReliableTime; // svs.time when another reliable command will be allowed
int lastPacketTime; // svs.time when packet was last received int lastPacketTime; // svs.time when packet was last received
int lastConnectTime; // svs.time when connection started int lastConnectTime; // svs.time when connection started
int lastSnapshotTime; // svs.time of last sent snapshot int lastSnapshotTime; // svs.time of last sent snapshot
qboolean rateDelayed; // true if nextSnapshotTime was set based on rate instead of snapshotMsec 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 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 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_start_queue;
netchan_buffer_t **netchan_end_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; int oldServerTime;
qboolean csUpdated[MAX_CONFIGSTRINGS+1]; qboolean csUpdated[MAX_CONFIGSTRINGS];
server_sound_t server_sounds[ MAX_SERVER_SOUNDS ]; server_sound_t server_sounds[ MAX_SERVER_SOUNDS ];
int number_of_server_sounds; int number_of_server_sounds;
@ -198,7 +225,7 @@ typedef struct client_s {
int lastVisCheckTime[MAX_CLIENTS]; int lastVisCheckTime[MAX_CLIENTS];
#ifdef LEGACY_PROTOCOL #ifdef LEGACY_PROTOCOL
qboolean compat; qboolean compat;
#endif #endif
} client_t; } client_t;
@ -208,7 +235,11 @@ typedef struct client_s {
// MAX_CHALLENGES is made large to prevent a denial // MAX_CHALLENGES is made large to prevent a denial
// of service attack that could cycle all of them // of service attack that could cycle all of them
// out before legitimate users connected // 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 #define AUTHORIZE_TIMEOUT 5000
@ -227,47 +258,52 @@ typedef enum {
typedef struct { typedef struct {
netadr_t adr; netadr_t adr;
int challenge; int challenge;
int clientChallenge; // challenge number coming from the client int clientChallenge; // challenge number coming from the client
int time; // time the last packet was sent to the autherize server int time; // time the last packet was sent to the autherize server
int pingTime; // time the challenge response was sent to client int pingTime; // time the challenge response was sent to client
int firstTime; // time the adr was first used, for authorize timeout checks int firstTime; // time the adr was first used, for authorize timeout checks
qboolean wasrefused; qboolean wasrefused;
qboolean connected; qboolean connected;
//
// gamespy stuff
//
unsigned int gamespyId; unsigned int gamespyId;
char gsChallenge[12]; char gsChallenge[12];
cdKeyState_e cdkeyState; cdKeyState_e cdkeyState;
} challenge_t; } challenge_t;
#define MAX_MASTERS 8 // max recipients for heartbeat packets
// this structure will be cleared only when the game dll changes // this structure will be cleared only when the game dll changes
typedef struct { 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 snapFlagServerBit; // ^= SNAPFLAG_SERVERCOUNT every SV_SpawnServer()
int startTime;
int lastTime;
int serverLagTime;
qboolean autosave;
int mapTime;
client_t *clients; // [sv_maxclients->integer]; int startTime;
int iNumClients; int lastTime;
int numSnapshotEntities; // sv_maxclients->integer*PACKET_BACKUP*MAX_PACKET_ENTITIES int serverLagTime;
int nextSnapshotEntities; // next snapshotEntities to use 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] entityState_t *snapshotEntities; // [numSnapshotEntities]
int nextHeartbeatTime; int nextHeartbeatTime;
challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting
netadr_t redirectAddress; // for rcon return messages netadr_t redirectAddress; // for rcon return messages
netadr_t authorizeAddress; // for rcon return messages #ifndef STANDALONE
netadr_t authorizeAddress; // authorize server address
#endif
char gameName[ MAX_QPATH ]; char gameName[ MAX_QPATH ];
char mapName[ MAX_QPATH ]; char mapName[ MAX_QPATH ];
char rawServerName[ MAX_QPATH ]; char rawServerName[ MAX_QPATH ];
int areabits_warning_time; int areabits_warning_time;
qboolean soundsNeedLoad; qboolean soundsNeedLoad;
char tm_filename[ MAX_QPATH ]; char tm_filename[ MAX_QPATH ];
int tm_loopcount; int tm_loopcount;
@ -291,53 +327,51 @@ typedef struct
//============================================================================= //=============================================================================
extern cvar_t *sv_mapname; extern serverStatic_t svs; // persistant server info across maps
extern serverStatic_t svs; // persistant server info across maps extern server_t sv; // cleared each map
extern server_t sv; // cleared each map extern game_export_t *ge; // game exports
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_privateClients;
extern cvar_t *sv_timeout; extern cvar_t *sv_hostname;
extern cvar_t *sv_zombietime; extern cvar_t *sv_master[MAX_MASTER_SERVERS];
extern cvar_t *sv_rconPassword; extern cvar_t *sv_reconnectlimit;
extern cvar_t *sv_privatePassword; extern cvar_t *sv_showloss;
extern cvar_t *sv_allowDownload; extern cvar_t *sv_padPackets;
extern cvar_t *sv_maxclients; extern cvar_t *sv_killserver;
extern cvar_t *sv_mapname;
extern cvar_t *sv_privateClients; extern cvar_t *sv_mapChecksum;
extern cvar_t *sv_hostname; extern cvar_t *sv_serverid;
extern cvar_t *sv_master[ MAX_MASTER_SERVERS ]; extern cvar_t *sv_minRate;
extern cvar_t *sv_reconnectlimit; extern cvar_t *sv_maxRate;
extern cvar_t *sv_showloss; extern cvar_t *sv_dlRate;
extern cvar_t *sv_padPackets; extern cvar_t *sv_minPing;
extern cvar_t *sv_killserver; extern cvar_t *sv_maxPing;
extern cvar_t *sv_mapChecksum; extern cvar_t *g_gametype;
extern cvar_t *sv_serverid; extern cvar_t *g_gametypestring;
extern cvar_t *sv_minRate; extern cvar_t *sv_pure;
extern cvar_t *sv_maxRate; extern cvar_t *sv_floodProtect;
extern cvar_t *sv_dlRate; extern cvar_t *sv_lanForceRate;
extern cvar_t *sv_minPing; extern cvar_t *sv_maplist;
extern cvar_t *sv_maxPing; extern cvar_t *sv_drawentities;
extern cvar_t *sv_pure; extern cvar_t *sv_deeptracedebug;
extern cvar_t *sv_floodProtect; extern cvar_t *sv_netprofile;
extern cvar_t *sv_maplist; extern cvar_t *sv_netprofileoverlay;
extern cvar_t *sv_drawentities; extern cvar_t *sv_netoptimize;
extern cvar_t *sv_deeptracedebug; extern cvar_t *sv_netoptimize_vistime;
extern cvar_t *sv_netprofile; extern cvar_t *g_netoptimize;
extern cvar_t *sv_netprofileoverlay; extern cvar_t *sv_chatter;
extern cvar_t *sv_netoptimize; extern cvar_t *sv_gamename;
extern cvar_t *sv_netoptimize_vistime; extern cvar_t *sv_location;
extern cvar_t *g_netoptimize; extern cvar_t *sv_debug_gamespy;
extern cvar_t *g_gametype; extern cvar_t *sv_gamespy;
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)
#ifndef STANDALONE #ifndef STANDALONE
extern cvar_t *sv_strictAuth; extern cvar_t *sv_strictAuth;
#endif #endif
@ -351,6 +385,7 @@ extern cvar_t *sv_voip;
extern cvar_t *sv_voipProtocol; extern cvar_t *sv_voipProtocol;
#endif #endif
//=========================================================== //===========================================================
// //
@ -378,8 +413,8 @@ extern leakyBucket_t outboundLeakyBucket;
qboolean SVC_RateLimit( leakyBucket_t *bucket, int burst, int period ); qboolean SVC_RateLimit( leakyBucket_t *bucket, int burst, int period );
qboolean SVC_RateLimitAddress( netadr_t from, int burst, int period ); qboolean SVC_RateLimitAddress( netadr_t from, int burst, int period );
void SV_FinalMessage( const char *message ); void SV_FinalMessage (const char *message);
void QDECL SV_SendServerCommand( client_t *cl, const char *fmt, ...); void QDECL SV_SendServerCommand( client_t *cl, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
void SV_AddOperatorCommands (void); void SV_AddOperatorCommands (void);
@ -444,8 +479,8 @@ void SV_SpawnServer( const char *server, qboolean loadgame, qboolean restart, qb
// //
// sv_client.c // sv_client.c
// //
challenge_t* FindChallenge(netadr_t from, qboolean connecting); challenge_t* FindChallenge(netadr_t from, qboolean connecting);
void SV_GetChallenge( netadr_t from ); void SV_GetChallenge(netadr_t from);
void SV_DirectConnect( 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_UserinfoChanged( client_t *cl );
void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ); 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_DropClient( client_t *drop, const char *reason );
void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ); 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_SendDownloadMessages(void);
int SV_SendQueuedMessages(void); int SV_SendQueuedMessages(void);
// //
// sv_ccmds.c // sv_ccmds.c
// //
@ -524,7 +561,7 @@ void SV_UnlinkEntity( gentity_t *ent );
void SV_LinkEntity( gentity_t *ent ); void SV_LinkEntity( gentity_t *ent );
// Needs to be called any time an entity changes origin, mins, maxs, // Needs to be called any time an entity changes origin, mins, maxs,
// or solid. Automatically unlinks if needed. // 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 // sets ent->leafnums[] for pvs determination even if the entity
// is not solid // is not solid
@ -585,5 +622,3 @@ void SV_ShutdownGamespy();
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif // __SERVER_H__

View file

@ -46,10 +46,19 @@ to be sent to the authorize server.
When an authorizeip is returned, a challenge response will be When an authorizeip is returned, a challenge response will be
sent to that ip. 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; challenge_t *challenge;
qboolean wasfound = qfalse;
// ignore if we are in single player // ignore if we are in single player
// Removed in OPM // Removed in OPM
@ -58,9 +67,23 @@ void SV_GetChallenge( netadr_t from ) {
// return; // 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 // we send the challengeResponse immediately as there is no AUTH server for us
// it's also way more cool this way :) // it's also way more cool this way :)
@ -89,8 +112,10 @@ void SV_GetChallenge( netadr_t from ) {
// check client's cd key // check client's cd key
NET_OutOfBandPrint(NS_SERVER, from, "getKey %s", challenge->gsChallenge); NET_OutOfBandPrint(NS_SERVER, from, "getKey %s", challenge->gsChallenge);
challenge->pingTime = svs.time;
} }
#ifndef STANDALONE
/* /*
==================== ====================
SV_AuthorizeIpPacket SV_AuthorizeIpPacket
@ -98,8 +123,6 @@ SV_AuthorizeIpPacket
A packet has been returned from the authorize server. A packet has been returned from the authorize server.
If we have a challenge adr for that ip, send the If we have a challenge adr for that ip, send the
challengeResponse to it challengeResponse to it
NOTE: This function is deprecated, SV_GamespyAuthorize must be used instead.
==================== ====================
*/ */
void SV_AuthorizeIpPacket( netadr_t from ) { void SV_AuthorizeIpPacket( netadr_t from ) {
@ -107,6 +130,7 @@ void SV_AuthorizeIpPacket( netadr_t from ) {
int i; int i;
char *s; char *s;
char *r; char *r;
challenge_t *challengeptr;
if ( !NET_CompareBaseAdr( from, svs.authorizeAddress ) ) { if ( !NET_CompareBaseAdr( from, svs.authorizeAddress ) ) {
Com_Printf( "SV_AuthorizeIpPacket: not from authorize server\n" ); 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" ); Com_Printf( "SV_AuthorizeIpPacket: challenge not found\n" );
return; return;
} }
challengeptr = &svs.challenges[i];
// send a packet back to the original client // send a packet back to the original client
svs.challenges[i].pingTime = svs.time; challengeptr->pingTime = svs.time;
s = Cmd_Argv( 2 ); s = Cmd_Argv( 2 );
r = Cmd_Argv( 3 ); // reason r = Cmd_Argv( 3 ); // reason
if ( !Q_stricmp( s, "demo" ) ) { if ( !Q_stricmp( s, "demo" ) ) {
// they are a demo client trying to connect to a real server // 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 // 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; return;
} }
if ( !Q_stricmp( s, "accept" ) ) { if ( !Q_stricmp( s, "accept" ) ) {
NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, NET_OutOfBandPrint(NS_SERVER, challengeptr->adr,
"challengeResponse %i", svs.challenges[i].challenge ); "challengeResponse %d %d %d", challengeptr->challenge, challengeptr->clientChallenge, com_protocol->integer);
return; return;
} }
if ( !Q_stricmp( s, "unknown" ) ) { if ( !Q_stricmp( s, "unknown" ) ) {
if (!r) { 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 { } 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 // 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; return;
} }
// authorization failed // authorization failed
if (!r) { 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 { } 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 // 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 ) { void SV_DirectConnect( netadr_t from ) {
char userinfo[MAX_INFO_STRING]; char userinfo[MAX_INFO_STRING];
int i; int i;
client_t *cl, *newcl; client_t *cl, *newcl;
// Make this a temp variable to avoid stack overflow issues static client_t temp; // static variable, otherwise it causes a stack overflow
static client_t temp;
gentity_t *ent; gentity_t *ent;
int clientNum; int clientNum;
int version; int version;
@ -290,7 +312,7 @@ void SV_DirectConnect( netadr_t from ) {
#endif #endif
challenge_t* ch; challenge_t* ch;
Com_DPrintf( "SVC_DirectConnect ()\n" ); Com_DPrintf ("SVC_DirectConnect ()\n");
// Check whether this client is banned. // Check whether this client is banned.
if(SV_IsBanned(&from, qfalse)) if(SV_IsBanned(&from, qfalse))
@ -299,9 +321,10 @@ void SV_DirectConnect( netadr_t from ) {
return; 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 #ifdef LEGACY_PROTOCOL
if(version > 0 && com_legacyprotocol->integer == version) if(version > 0 && com_legacyprotocol->integer == version)
compat = qtrue; compat = qtrue;
@ -351,42 +374,54 @@ void SV_DirectConnect( netadr_t from ) {
Info_SetValueForKey( userinfo, "ip", ip ); Info_SetValueForKey( userinfo, "ip", ip );
// see if the challenge is valid (LAN clients don't need to challenge) // see if the challenge is valid (LAN clients don't need to challenge)
if ( !NET_IsLocalAddress (from) ) { if (!NET_IsLocalAddress(from))
int ping; {
int ping;
challenge_t *challengeptr;
for (i=0 ; i<MAX_CHALLENGES ; i++) { for (i=0; i<MAX_CHALLENGES; i++)
if (NET_CompareAdr(from, svs.challenges[i].adr)) { {
if ( challenge == svs.challenges[i].challenge ) { if (NET_CompareAdr(from, svs.challenges[i].adr))
break; // good {
} if(challenge == svs.challenges[i].challenge)
break;
} }
} }
if (i == MAX_CHALLENGES) {
NET_OutOfBandPrint( NS_SERVER, from, "droperror\nNo or bad challenge for address.\n" ); if (i == MAX_CHALLENGES)
{
NET_OutOfBandPrint( NS_SERVER, from, "print\nNo or bad challenge for your address.\n" );
return;
}
challengeptr = &svs.challenges[i];
if(challengeptr->wasrefused)
{
// Return silently, so that error messages written by the server keep being displayed.
return; return;
} }
ping = svs.time - svs.challenges[i].pingTime; ping = svs.time - challengeptr->pingTime;
Com_Printf( "Client %i connecting with %i challenge ping\n", i, ping );
svs.challenges[i].connected = qtrue;
// never reject a LAN client based on ping // never reject a LAN client based on ping
if ( !Sys_IsLANAddress( from ) ) { if ( !Sys_IsLANAddress( from ) ) {
if ( sv_minPing->value && ping < sv_minPing->value ) { 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, "print\nServer is for high pings only\n" );
NET_OutOfBandPrint( NS_SERVER, from, "droperror\nServer is for high pings only\n" );
Com_DPrintf ("Client %i rejected on a too low ping\n", i); Com_DPrintf ("Client %i rejected on a too low ping\n", i);
// reset the address otherwise their ping will keep increasing challengeptr->wasrefused = qtrue;
// with each connect message and they'd eventually be able to connect
svs.challenges[i].adr.port = 0;
return; return;
} }
if ( sv_maxPing->value && ping > sv_maxPing->value ) { 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); Com_DPrintf ("Client %i rejected on a too high ping\n", i);
challengeptr->wasrefused = qtrue;
return; return;
} }
} }
Com_Printf("Client %i connecting with %i challenge ping\n", i, ping);
challengeptr->connected = qtrue;
} }
newcl = &temp; newcl = &temp;
@ -425,7 +460,7 @@ void SV_DirectConnect( netadr_t from ) {
// check for privateClient password // check for privateClient password
password = Info_ValueForKey( userinfo, "password" ); password = Info_ValueForKey( userinfo, "password" );
if ( !strcmp( password, sv_privatePassword->string ) ) { if ( *password && !strcmp( password, sv_privatePassword->string ) ) {
startIndex = 0; startIndex = 0;
} else { } else {
// skip past the reserved slots // skip past the reserved slots
@ -499,10 +534,9 @@ gotnewcl:
// get the game a chance to reject this connection or modify the userinfo // get the game a chance to reject this connection or modify the userinfo
denied = ge->ClientConnect( clientNum, qtrue, qfalse ); denied = ge->ClientConnect( clientNum, qtrue, qfalse );
if ( denied ) { if ( denied ) {
NET_OutOfBandPrint( NS_SERVER, from, "droperror\n%s\n", 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; return;
} }
@ -514,7 +548,7 @@ gotnewcl:
SV_UserinfoChanged( newcl ); SV_UserinfoChanged( newcl );
// send the connect packet to the client // 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 ); 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 Destructor for data allocated in a client structure
===================== =====================
*/ */
void SV_FreeClient(client_t* client) void SV_FreeClient(client_t *client)
{ {
#ifdef USE_VOIP #ifdef USE_VOIP
int index; int index;
for (index = client->queuedVoipIndex; index < client->queuedVoipPackets; index++) for(index = client->queuedVoipIndex; index < client->queuedVoipPackets; index++)
{ {
index %= ARRAY_LEN(client->voipPacket); index %= ARRAY_LEN(client->voipPacket);
Z_Free(client->voipPacket[index]); Z_Free(client->voipPacket[index]);
} }
client->queuedVoipPackets = 0; client->queuedVoipPackets = 0;
#endif #endif
SV_Netchan_FreeQueue(client); SV_Netchan_FreeQueue(client);
SV_CloseDownload(client); SV_CloseDownload(client);
} }
/* /*
@ -585,35 +619,32 @@ or crashing -- SV_FinalMessage() will handle that
void SV_DropClient( client_t *drop, const char *reason ) { void SV_DropClient( client_t *drop, const char *reason ) {
int i; int i;
challenge_t *challenge; challenge_t *challenge;
const qboolean isBot = drop->netchan.remoteAddress.type == NA_BOT;
if ( drop->state == CS_ZOMBIE ) { if ( drop->state == CS_ZOMBIE ) {
return; // already dropped return; // already dropped
} }
if (drop->netchan.remoteAddress.type != NA_BOT) { if ( !isBot ) {
// see if we already have a challenge for this ip // see if we already have a challenge for this ip
challenge = &svs.challenges[0]; challenge = &svs.challenges[0];
for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) { for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++)
if ( NET_CompareAdr( drop->netchan.remoteAddress, challenge->adr ) ) { {
challenge->connected = qfalse; if(NET_CompareAdr(drop->netchan.remoteAddress, challenge->adr))
{
Com_Memset(challenge, 0, sizeof(*challenge));
break; break;
} }
} }
} }
// Free all allocated data on the client structure // Free all allocated data on the client structure
SV_FreeClient(drop); SV_FreeClient(drop);
// tell everyone why they got dropped // tell everyone why they got dropped
SV_SendServerCommand( NULL, "print \"%s %s\n\"", drop->name, reason ); 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 // call the prog function for removing a client
// this will remove the body, among other things // this will remove the body, among other things
ge->ClientDisconnect( ( gentity_t * )SV_GentityNum( drop - svs.clients ) ); 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 // add the disconnect command
SV_SendServerCommand( drop, "disconnect \"%s\"", reason); 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 // nuke user info
SV_SetUserinfo( drop - svs.clients, "" ); 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 // if this was the last client on the server, send a heartbeat
// to the master so it is known the server is empty // 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. the wrong gamestate.
================ ================
*/ */
void SV_SendClientGameState( client_t *client ) { static void SV_SendClientGameState( client_t *client ) {
int start; int start;
entityState_t *base, nullstate; entityState_t *base, nullstate;
msg_t msg; msg_t msg;
@ -690,7 +728,7 @@ void SV_SendClientGameState( client_t *client ) {
SV_UpdateServerCommandsToClient( client, &msg ); SV_UpdateServerCommandsToClient( client, &msg );
// send the gamestate // send the gamestate
MSG_WriteSVC( &msg, svc_gamestate ); MSG_WriteSVC( &msg, svc_gamestate );
MSG_WriteLong( &msg, client->reliableSequence ); MSG_WriteLong( &msg, client->reliableSequence );
// write the configstrings // write the configstrings
@ -719,7 +757,7 @@ void SV_SendClientGameState( client_t *client ) {
MSG_WriteLong( &msg, client - svs.clients); MSG_WriteLong( &msg, client - svs.clients);
// write the checksum feed // write the checksum feed
MSG_WriteLong( &msg, 0); MSG_WriteLong( &msg, sv.checksumFeed);
// write the server frametime to the client (only on TA/TT) // write the server frametime to the client (only on TA/TT)
MSG_WriteServerFrameTime(&msg, sv.frameTime); MSG_WriteServerFrameTime(&msg, sv.frameTime);
@ -754,7 +792,7 @@ void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) {
client->deltaMessage = -1; client->deltaMessage = -1;
client->lastSnapshotTime = 0; // generate a snapshot immediately client->lastSnapshotTime = 0; // generate a snapshot immediately
if (cmd) if(cmd)
memcpy(&client->lastUsercmd, cmd, sizeof(client->lastUsercmd)); memcpy(&client->lastUsercmd, cmd, sizeof(client->lastUsercmd));
else else
memset(&client->lastUsercmd, '\0', sizeof(client->lastUsercmd)); memset(&client->lastUsercmd, '\0', sizeof(client->lastUsercmd));
@ -798,7 +836,7 @@ static void SV_CloseDownload( client_t *cl ) {
// Free the temporary buffer space // Free the temporary buffer space
for (i = 0; i < MAX_DOWNLOAD_WINDOW; i++) { for (i = 0; i < MAX_DOWNLOAD_WINDOW; i++) {
if (cl->downloadBlocks[i]) { if (cl->downloadBlocks[i]) {
Z_Free( cl->downloadBlocks[i] ); Z_Free(cl->downloadBlocks[i]);
cl->downloadBlocks[i] = NULL; cl->downloadBlocks[i] = NULL;
} }
} }
@ -812,7 +850,7 @@ SV_StopDownload_f
Abort a download if in progress Abort a download if in progress
================== ==================
*/ */
void SV_StopDownload_f( client_t *cl ) { static void SV_StopDownload_f( client_t *cl ) {
if (*cl->downloadName) if (*cl->downloadName)
Com_DPrintf( "clientDownload: %d : file \"%s\" aborted\n", (int) (cl - svs.clients), 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 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); Com_DPrintf( "clientDownload: %s Done\n", cl->name);
// resend the game state to update any clients that entered during the download // resend the game state to update any clients that entered during the download
SV_SendClientGameState(cl); 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 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) ); int block = atoi( Cmd_Argv(1) );
@ -869,7 +910,7 @@ void SV_NextDownload_f( client_t *cl )
SV_BeginDownload_f SV_BeginDownload_f
================== ==================
*/ */
void SV_BeginDownload_f( client_t *cl ) { static void SV_BeginDownload_f( client_t *cl ) {
// Kill any existing download // Kill any existing download
SV_CloseDownload( cl ); SV_CloseDownload( cl );
@ -884,15 +925,13 @@ void SV_BeginDownload_f( client_t *cl ) {
SV_WriteDownloadToClient SV_WriteDownloadToClient
Check to see if the client wants a file, open it if needed and start pumping the client 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 curindex;
int rate; int unreferenced = 1;
int blockspersnap;
int idPack = 0, missionPack = 0, unreferenced = 1;
char errorMessage[1024]; char errorMessage[1024];
char pakbuf[MAX_QPATH], *pakptr; char pakbuf[MAX_QPATH], *pakptr;
int numRefPaks; int numRefPaks;
@ -900,10 +939,16 @@ int SV_WriteDownloadToClient( client_t *cl , msg_t *msg )
if (!*cl->downloadName) if (!*cl->downloadName)
return 0; // Nothing being downloaded return 0; // Nothing being downloaded
if (!cl->download) { if(!cl->download)
{
qboolean idPack = qfalse;
#ifndef STANDALONE
qboolean missionPack = qfalse;
#endif
// Chop off filename extension. // Chop off filename extension.
Com_sprintf(pakbuf, sizeof(pakbuf), "%s", cl->downloadName); Com_sprintf(pakbuf, sizeof(pakbuf), "%s", cl->downloadName);
pakptr = Q_strrchr(pakbuf, '.'); pakptr = strrchr(pakbuf, '.');
if(pakptr) if(pakptr)
{ {
@ -927,8 +972,11 @@ int SV_WriteDownloadToClient( client_t *cl , msg_t *msg )
// now that we know the file is referenced, // now that we know the file is referenced,
// check whether it's legal to download it. // check whether it's legal to download it.
missionPack = FS_idPak(pakbuf, "missionpack"); #ifndef STANDALONE
idPack = missionPack || FS_idPak(pakbuf, BASEGAME); //missionPack = FS_idPak(pakbuf, BASETA, NUM_TA_PAKS);
//idPack = missionPack;
#endif
//idPack = idPack || FS_idPak(pakbuf, BASEGAME, NUM_ID_PAKS);
break; break;
} }
@ -936,11 +984,13 @@ int SV_WriteDownloadToClient( client_t *cl , msg_t *msg )
} }
} }
cl->download = 0;
// We open the file here // We open the file here
if ( !(sv_allowDownload->integer & DLF_ENABLE) || if ( !(sv_allowDownload->integer & DLF_ENABLE) ||
(sv_allowDownload->integer & DLF_NO_UDP) || (sv_allowDownload->integer & DLF_NO_UDP) ||
idPack || unreferenced || 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 // cannot auto-download file
if(unreferenced) if(unreferenced)
{ {
@ -949,18 +999,22 @@ int SV_WriteDownloadToClient( client_t *cl , msg_t *msg )
} }
else if (idPack) { else if (idPack) {
Com_Printf("clientDownload: %d : \"%s\" cannot download id pk3 files\n", (int) (cl - svs.clients), cl->downloadName); 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" 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); "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); Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload id pk3 file \"%s\"", cl->downloadName);
} }
} }
else if ( !(sv_allowDownload->integer & DLF_ENABLE) || else if ( !(sv_allowDownload->integer & DLF_ENABLE) ||
(sv_allowDownload->integer & DLF_NO_UDP) ) { (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) { if (sv_pure->integer) {
Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" 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 " "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 ); MSG_WriteString( msg, errorMessage );
*cl->downloadName = 0; *cl->downloadName = 0;
if(cl->download)
FS_FCloseFile(cl->download);
return 1; return 1;
} }
@ -1001,7 +1059,7 @@ int SV_WriteDownloadToClient( client_t *cl , msg_t *msg )
curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW); curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW);
if (!cl->downloadBlocks[curindex]) 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 ); 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 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 if (cl->downloadClientBlock == cl->downloadCurrentBlock)
// client snapMsec and rate return 0; // Nothing to transmit
// based on the rate, how many bytes can we fit in the snapMsec time of the client // Write out the next section of the file, if we have already reached our window,
// normal rate / snapshotMsec calculation // automatically start retransmitting
rate = cl->rate; if (cl->downloadXmitBlock == cl->downloadCurrentBlock)
if ( sv_maxRate->integer ) { {
if ( sv_maxRate->integer < 1000 ) { // We have transmitted the complete window, should we start resending?
Cvar_Set( "sv_MaxRate", "1000" ); if (svs.time - cl->downloadSendTime > 1000)
} cl->downloadXmitBlock = cl->downloadClientBlock;
if ( sv_maxRate->integer < rate ) { else
rate = sv_maxRate->integer; return 0;
}
} }
if (!rate) { // Send current block
blockspersnap = 1; curindex = (cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW);
} else {
blockspersnap = ( (rate * cl->snapshotMsec) / 1000 + MAX_DOWNLOAD_BLKSIZE ) /
MAX_DOWNLOAD_BLKSIZE;
}
if (blockspersnap < 0) MSG_WriteByte( msg, svc_download );
blockspersnap = 1; 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, MSG_WriteShort( msg, cl->downloadBlockSize[curindex] );
// automatically start retransmitting
if (cl->downloadClientBlock == cl->downloadCurrentBlock) // Write the block
return 0; // Nothing to transmit if(cl->downloadBlockSize[curindex])
MSG_WriteData(msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex]);
if (cl->downloadXmitBlock == cl->downloadCurrentBlock) { Com_DPrintf( "clientDownload: %d : writing block %d\n", (int) (cl - svs.clients), cl->downloadXmitBlock );
// We have transmitted the complete window, should we start resending?
//FIXME: This uses a hardcoded one second timeout for lost blocks // Move on to the next block
//the timeout should be based on client rate somehow // It will get sent with next snap shot. The rate will keep us in line.
if (svs.time - cl->downloadSendTime > 1000) cl->downloadXmitBlock++;
cl->downloadXmitBlock = cl->downloadClientBlock; cl->downloadSendTime = svs.time;
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;
}
return 1; return 1;
} }
@ -1375,7 +1400,7 @@ void SV_UserinfoChanged( client_t *cl ) {
char *val; char *val;
char *ip; char *ip;
int i; int i;
size_t len; int len;
// name for C code // name for C code
Q_strncpyz( cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name) ); 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 ) { 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 ); SV_UserinfoChanged( cl );
// call prog code to allow overrides // call prog code to allow overrides
ge->ClientUserinfoChanged( ( gentity_t * )SV_GentityNum( cl - svs.clients ), cl->userinfo ); 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 { typedef struct {
char *name; char *name;
void ( *func )( client_t *cl ); void (*func)( client_t *cl );
} ucmd_t; } ucmd_t;
static ucmd_t ucmds[] = { static ucmd_t ucmds[] = {
@ -1495,6 +1552,10 @@ static ucmd_t ucmds[] = {
{"stopdl", SV_StopDownload_f}, {"stopdl", SV_StopDownload_f},
{"donedl", SV_DoneDownload_f}, {"donedl", SV_DoneDownload_f},
#ifdef USE_VOIP
{"voip", SV_Voip_f},
#endif
{NULL, NULL} {NULL, NULL}
}; };
@ -1533,7 +1594,8 @@ void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ) {
if (clientOK) { if (clientOK) {
// pass unknown strings to the game // 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 ) ); ge->ClientCommand( ( gentity_t * )SV_GentityNum( cl - svs.clients ) );
if (ge->errorMessage) if (ge->errorMessage)
@ -1566,6 +1628,7 @@ static qboolean SV_ClientCommand( client_t *cl, msg_t *msg ) {
} }
if( developer->integer == 2 ) { 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 ); 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, // the command, we will stop processing the rest of the packet,
// including the usercmd. This causes flooders to lag themselves // including the usercmd. This causes flooders to lag themselves
// but not other people // 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 // 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 && sv_floodProtect->integer &&
svs.time < cl->nextReliableTime ) { svs.time < cl->nextReliableTime ) {
// ignore any other text messages from this client but let them keep playing // 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 // Actual execution of the command
SV_ExecuteClientCommand( cl, s, clientOk ); SV_ExecuteClientCommand( cl, s, clientOk );
// continue procesing return qtrue; // continue procesing
return qtrue;
} }
@ -1624,8 +1687,7 @@ SV_ClientThink
Also called by bot code 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; const char *err;
cl->lastUsercmd = *cmd; cl->lastUsercmd = *cmd;
@ -1686,7 +1748,7 @@ static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) {
// also use the message acknowledge // also use the message acknowledge
key ^= cl->messageAcknowledge; key ^= cl->messageAcknowledge;
// also use the last acknowledged server command in the key // 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 ); 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 ); SV_SendClientGameState( cl );
} }
return; return;
} }
// if this is the first usercmd we have received // if this is the first usercmd we have received
// this gamestate, put the client into the world // 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] ); SV_ClientEnterWorld( cl, &cmds[0] );
// the moves can be processed normaly // the moves can be processed normaly
} }
// a bad cp command was sent, drop the client // 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) { if (sv_pure->integer != 0 && cl->pureAuthentic == 0) {
SV_DropClient( cl, "Cannot validate pure client!"); SV_DropClient( cl, "Cannot validate pure client!");
return; return;
} }
@ -1750,12 +1812,122 @@ static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) {
if ( cmds[i].serverTime <= cl->lastUsercmd.serverTime ) { if ( cmds[i].serverTime <= cl->lastUsercmd.serverTime ) {
continue; 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); MSG_Bitstream(msg);
serverId = MSG_ReadLong(msg); serverId = MSG_ReadLong( msg );
cl->serverIdAcknowledge = serverId; cl->serverIdAcknowledge = serverId;
cl->messageAcknowledge = MSG_ReadLong( msg ); 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 // 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 // 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 // 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 // usually only hackers create messages like this
// it is more annoying for them to let them hanging // it is more annoying for them to let them hanging
#ifndef NDEBUG #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 "" // 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 // 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 // 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 // if we can tell that the client has dropped the last
// gamestate we sent them, resend it // gamestate we sent them, resend it
// also, don't resend the gamestate if the server just restarted if ( cl->state != CS_ACTIVE && cl->messageAcknowledge > cl->gamestateMessageNum ) {
if ( serverId != sv.restartedServerId && cl->messageAcknowledge > cl->gamestateMessageNum ) {
Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name );
SV_SendClientGameState( cl ); SV_SendClientGameState( cl );
} }
return; 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 // read optional clientCommand strings
do { do {
c = MSG_ReadByte( msg ); c = MSG_ReadByte( msg );
//Com_DPrintf( "SV_ExecuteClientMessage: %i\n", c );
if ( c == clc_EOF ) { if ( c == clc_EOF ) {
break; break;
} }
if ( c != clc_clientCommand ) { if ( c != clc_clientCommand ) {
break; break;
} }
@ -1845,6 +2029,22 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) {
} }
} while ( 1 ); } 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 // read the usercmd_t
if ( c == clc_move ) { if ( c == clc_move ) {
SV_UserMove( cl, msg, qtrue ); SV_UserMove( cl, msg, qtrue );

View file

@ -73,12 +73,11 @@ SV_SetConfigstring
=============== ===============
*/ */
void SV_SetConfigstring (int index, const char *val) { void SV_SetConfigstring (int index, const char *val) {
int i; int i;
size_t len;
client_t *client; client_t *client;
if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { 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 ) { if ( !val ) {
@ -98,7 +97,7 @@ void SV_SetConfigstring (int index, const char *val) {
// spawning a new server // spawning a new server
if (sv.state == SS_LOADING2 || sv.state == SS_GAME || sv.restarting ) { 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++) { for (i = 0, client = svs.clients; i < svs.iNumClients ; i++, client++) {
if ( client->state < CS_ACTIVE ) { if ( client->state < CS_ACTIVE ) {
if ( client->state == CS_PRIMED ) if ( client->state == CS_PRIMED )
@ -111,7 +110,6 @@ void SV_SetConfigstring (int index, const char *val) {
} }
len = strlen( val );
SV_SendConfigstring(client, index); SV_SendConfigstring(client, index);
} }
} }
@ -316,7 +314,7 @@ void SV_UpdateConfigstrings(client_t *client)
{ {
int index; 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 the CS hasn't changed since we went to CS_PRIMED, ignore
if(!client->csUpdated[index]) if(!client->csUpdated[index])
continue; continue;
@ -452,6 +450,9 @@ void SV_Startup( void ) {
SV_InitGamespy(); SV_InitGamespy();
Cvar_Set( "sv_running", "1" ); 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 ) { void SV_ChangeMaxClients( void ) {
int oldMaxClients; int oldMaxClients;
int i; int i;
client_t *oldClients; client_t *oldClients;
int count; int count;
// get the highest client number in use // get the highest client number in use
count = 0; count = 0;
@ -538,6 +539,20 @@ void SV_ClearServer(void) {
sv.frameTime = 1.0 / sv_fps->value; 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 SV_SpawnServer
@ -980,15 +995,16 @@ SV_Init
Only called at main exe startup, not for each game Only called at main exe startup, not for each game
=============== ===============
*/ */
void SV_Init( void ) { void SV_Init (void)
{
int index; int index;
SV_AddOperatorCommands(); SV_AddOperatorCommands();
// serverinfo vars // serverinfo vars
Cvar_Get( "dmflags", "0", CVAR_SERVERINFO ); Cvar_Get ("dmflags", "0", CVAR_SERVERINFO);
Cvar_Get( "fraglimit", "20", CVAR_SERVERINFO ); Cvar_Get ("fraglimit", "20", CVAR_SERVERINFO);
Cvar_Get( "timelimit", "0", CVAR_SERVERINFO ); Cvar_Get ("timelimit", "0", CVAR_SERVERINFO);
g_gametype = Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_LATCH ); g_gametype = Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_LATCH );
g_gametypestring = Cvar_Get( "g_gametypestring", "0", CVAR_SERVERINFO | CVAR_LATCH ); g_gametypestring = Cvar_Get( "g_gametypestring", "0", CVAR_SERVERINFO | CVAR_LATCH );
Cvar_Get( "sv_keywords", "", CVAR_SERVERINFO ); Cvar_Get( "sv_keywords", "", CVAR_SERVERINFO );
@ -1013,12 +1029,11 @@ void SV_Init( void ) {
// sv_pure is disabled by default in mohaa // sv_pure is disabled by default in mohaa
// the problem is because of difference in pak files between languages // the problem is because of difference in pak files between languages
sv_pure = Cvar_Get ("sv_pure", "0", CVAR_SYSTEMINFO ); sv_pure = Cvar_Get ("sv_pure", "0", CVAR_SYSTEMINFO );
#ifdef USE_VOIP #ifdef USE_VOIP
sv_voip = Cvar_Get("sv_voip", "1", CVAR_LATCH); sv_voip = Cvar_Get("sv_voip", "1", CVAR_LATCH);
Cvar_CheckRange(sv_voip, 0, 1, qtrue); Cvar_CheckRange(sv_voip, 0, 1, qtrue);
sv_voipProtocol = Cvar_Get("sv_voipProtocol", sv_voip->integer ? "opus" : "", CVAR_SYSTEMINFO | CVAR_ROM ); sv_voipProtocol = Cvar_Get("sv_voipProtocol", sv_voip->integer ? "opus" : "", CVAR_SYSTEMINFO | CVAR_ROM );
#endif #endif
Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM ); Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM );
Cvar_Get ("sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); Cvar_Get ("sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM );
Cvar_Get ("sv_referencedPaks", "", CVAR_SYSTEMINFO | CVAR_ROM ); Cvar_Get ("sv_referencedPaks", "", CVAR_SYSTEMINFO | CVAR_ROM );
@ -1129,8 +1144,14 @@ void SV_Shutdown( const char *finalmsg ) {
SV_ClearServer(); SV_ClearServer();
// free server static data // free server static data
if ( svs.clients ) { if(svs.clients)
Z_Free( 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 ) ); Com_Memset( &svs, 0, sizeof( svs ) );

View file

@ -223,7 +223,7 @@ void QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) {
Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) ); 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++) { for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) {
SV_AddServerCommand( client, (char *)message ); 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 SVC_Status
@ -314,6 +475,24 @@ void SVC_Status( netadr_t from ) {
return; 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 ) ); strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );
// echo back the parameter to status. so master servers can use it as a challenge // 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; 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 * 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. * 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, "gametypestring", g_gametypestring->string );
Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) ); Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) );
#ifdef USE_VOIP
if (sv_voipProtocol->string && *sv_voipProtocol->string) {
Info_SetValueForKey( infostring, "voip", sv_voipProtocol->string );
}
#endif
if( sv_minPing->integer ) { if( sv_minPing->integer ) {
Info_SetValueForKey( infostring, "minPing", va("%i", 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 ); NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf );
} }
@ -430,26 +629,32 @@ Shift down the remaining args
Redirect all printfs Redirect all printfs
=============== ===============
*/ */
void SVC_RemoteCommand( netadr_t from, msg_t *msg ) { static void SVC_RemoteCommand( netadr_t from, msg_t *msg ) {
qboolean valid; qboolean valid;
unsigned int time;
char remaining[1024]; char remaining[1024];
// TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc. // TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc.
// (OOB messages are the bottleneck here) // (OOB messages are the bottleneck here)
#define SV_OUTPUTBUF_LENGTH (1024 - 16) #define SV_OUTPUTBUF_LENGTH (1024 - 16)
char sv_outputbuf[SV_OUTPUTBUF_LENGTH]; char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
static unsigned int lasttime = 0;
char *cmd_aux; char *cmd_aux;
// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534 // Prevent using rcon as an amplifier and make dictionary attacks impractical
time = Com_Milliseconds(); if ( SVC_RateLimitAddress( from, 10, 1000 ) ) {
if ( (unsigned)( time - lasttime ) < 500u ) { Com_DPrintf( "SVC_RemoteCommand: rate limit from %s exceeded, dropping request\n",
NET_AdrToString( from ) );
return; return;
} }
lasttime = time;
if ( !strlen( sv_rconPassword->string ) || if ( !strlen( sv_rconPassword->string ) ||
strcmp (Cmd_Argv(1), 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; valid = qfalse;
Com_Printf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) ); Com_Printf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
} else { } 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); Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c);
if (!Q_stricmp(c, "getstatus")) { if (!Q_stricmp(c, "getstatus")) {
SVC_Status( from ); SVC_Status( from );
} else if (!Q_stricmp(c, "getinfo")) { } else if (!Q_stricmp(c, "getinfo")) {
SVC_Info( from ); SVC_Info( from );
} else if (!Q_stricmp(c, "getchallenge")) { } else if (!Q_stricmp(c, "getchallenge")) {
SV_GetChallenge( from ); SV_GetChallenge(from);
} else if (!Q_stricmp(c, "connect")) { } else if (!Q_stricmp(c, "connect")) {
SV_DirectConnect( from ); SV_DirectConnect( from );
} else if (!Q_stricmp(c, "authorizeThis")) { } 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 // server disconnect messages when their new server sees our final
// sequenced messages to the old client // sequenced messages to the old client
} else { } else {
Com_DPrintf ("bad connectionless packet from %s:\n%s\n" Com_DPrintf ("bad connectionless packet from %s:\n%s\n",
, NET_AdrToString (from), s); 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 // read the qport out of the message so we can fix up
// stupid address translating routers // stupid address translating routers
MSG_BeginReadingOOB( msg ); MSG_BeginReadingOOB( msg );
i=MSG_ReadLong( msg ); // sequence number MSG_ReadLong( msg ); // sequence number
qport = MSG_ReadShort( msg ) & 0xffff; qport = MSG_ReadShort( msg ) & 0xffff;
// find which client the message is from // 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 // to make sure they don't need to retransmit the final
// reliable message, but they don't do any other processing // reliable message, but they don't do any other processing
if (cl->state != CS_ZOMBIE) { if (cl->state != CS_ZOMBIE) {
cl->lastPacketTime = svs.lastTime; // don't timeout cl->lastPacketTime = svs.time; // don't timeout
SV_ExecuteClientMessage( cl, msg ); SV_ExecuteClientMessage( cl, msg );
} }
} }
@ -613,7 +818,7 @@ SV_CalcPings
Updates the cl->ping variables Updates the cl->ping variables
=================== ===================
*/ */
void SV_CalcPings( void ) { static void SV_CalcPings( void ) {
int i, j; int i, j;
client_t *cl; client_t *cl;
int total, count; int total, count;
@ -673,7 +878,7 @@ for a few seconds to make sure any final reliable message gets resent
if necessary if necessary
================== ==================
*/ */
void SV_CheckTimeouts( void ) { static void SV_CheckTimeouts( void ) {
int i; int i;
client_t *cl; client_t *cl;
int droppoint; int droppoint;
@ -799,6 +1004,11 @@ void SV_Frame( int msec ) {
if( !com_sv_running->integer ) if( !com_sv_running->integer )
{ {
// Running as a server, but no map loaded // Running as a server, but no map loaded
#ifdef DEDICATED
// Block until something interesting happens
Sys_Sleep(-1);
#endif
return; return;
} }
@ -1010,42 +1220,42 @@ a client based on its rate settings
#define UDPIP_HEADER_SIZE 28 #define UDPIP_HEADER_SIZE 28
#define UDPIP6_HEADER_SIZE 48 #define UDPIP6_HEADER_SIZE 48
int SV_RateMsec(client_t* client) int SV_RateMsec(client_t *client)
{ {
int rate, rateMsec; int rate, rateMsec;
int messageSize; int messageSize;
messageSize = client->netchan.lastSentSize;
rate = client->rate;
messageSize = client->netchan.lastSentSize; if(sv_maxRate->integer)
rate = client->rate; {
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_minRate->integer)
{ {
if (sv_maxRate->integer < 1000) if(sv_minRate->integer < 1000)
Cvar_Set("sv_MaxRate", "1000"); Cvar_Set("sv_minRate", "1000");
if (sv_maxRate->integer < rate) if(sv_minRate->integer > rate)
rate = sv_maxRate->integer; rate = sv_minRate->integer;
} }
if (sv_minRate->integer) if(client->netchan.remoteAddress.type == NA_IP6)
{ messageSize += UDPIP6_HEADER_SIZE;
if (sv_minRate->integer < 1000) else
Cvar_Set("sv_minRate", "1000"); messageSize += UDPIP_HEADER_SIZE;
if (sv_minRate->integer > rate)
rate = sv_minRate->integer; rateMsec = messageSize * 1000 / ((int) (rate * com_timescale->value));
} rate = Sys_Milliseconds() - client->netchan.lastSentTime;
if (client->netchan.remoteAddress.type == NA_IP6) if(rate > rateMsec)
messageSize += UDPIP6_HEADER_SIZE; return 0;
else else
messageSize += UDPIP_HEADER_SIZE; return rateMsec - rate;
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 SV_SendQueuedPackets()
{ {
int numBlocks; int numBlocks;
int dlStart, deltaT, delayT; int dlStart, deltaT, delayT;
static int dlNextRound = 0; static int dlNextRound = 0;
int timeVal = INT_MAX; int timeVal = INT_MAX;
// Send out fragmented packets now that we're idle // Send out fragmented packets now that we're idle
delayT = SV_SendQueuedMessages(); delayT = SV_SendQueuedMessages();
if (delayT >= 0) if(delayT >= 0)
timeVal = delayT; timeVal = delayT;
if (sv_dlRate->integer) if(sv_dlRate->integer)
{ {
// Rate limiting. This is very imprecise for high // Rate limiting. This is very imprecise for high
// download rates due to millisecond timedelta resolution // download rates due to millisecond timedelta resolution
dlStart = Sys_Milliseconds(); dlStart = Sys_Milliseconds();
deltaT = dlNextRound - dlStart; deltaT = dlNextRound - dlStart;
if (deltaT > 0) if(deltaT > 0)
{ {
if (deltaT < timeVal) if(deltaT < timeVal)
timeVal = deltaT + 1; timeVal = deltaT + 1;
} }
else else
{ {
numBlocks = SV_SendDownloadMessages(); numBlocks = SV_SendDownloadMessages();
if (numBlocks) if(numBlocks)
{ {
// There are active downloads // There are active downloads
deltaT = Sys_Milliseconds() - dlStart; deltaT = Sys_Milliseconds() - dlStart;
delayT = 1000 * numBlocks * MAX_DOWNLOAD_BLKSIZE; delayT = 1000 * numBlocks * MAX_DOWNLOAD_BLKSIZE;
delayT /= sv_dlRate->integer * 1024; delayT /= sv_dlRate->integer * 1024;
if (delayT <= deltaT + 1) if(delayT <= deltaT + 1)
{ {
// Sending the last round of download messages // Sending the last round of download messages
// took too long for given rate, don't wait for // took too long for given rate, don't wait for
// next round, but always enforce a 1ms delay // next round, but always enforce a 1ms delay
// between DL message rounds so we don't hog // between DL message rounds so we don't hog
// all of the bandwidth. This will result in an // all of the bandwidth. This will result in an
// effective maximum rate of 1MB/s per user, but the // effective maximum rate of 1MB/s per user, but the
// low download window size limits this anyways. // low download window size limits this anyways.
if (timeVal > 2) if(timeVal > 2)
timeVal = 2; timeVal = 2;
dlNextRound = dlStart + deltaT + 1; dlNextRound = dlStart + deltaT + 1;
} }
else else
{ {
dlNextRound = dlStart + delayT; dlNextRound = dlStart + delayT;
delayT -= deltaT; delayT -= deltaT;
if (delayT < timeVal) if(delayT < timeVal)
timeVal = delayT; timeVal = delayT;
} }
} }
} }
} }
else else
{ {
if (SV_SendDownloadMessages()) if(SV_SendDownloadMessages())
timeVal = 0; timeVal = 0;
} }
return timeVal; return timeVal;
} }

View file

@ -259,17 +259,17 @@ void SV_Netchan_Transmit( client_t *client, msg_t *msg)
Netchan_SV_Process Netchan_SV_Process
================= =================
*/ */
qboolean SV_Netchan_Process(client_t* client, msg_t* msg) { qboolean SV_Netchan_Process( client_t *client, msg_t *msg ) {
int ret; int ret;
ret = Netchan_Process(&client->netchan, msg); ret = Netchan_Process( &client->netchan, msg );
if (!ret) if (!ret)
return qfalse; return qfalse;
#ifdef LEGACY_PROTOCOL #ifdef LEGACY_PROTOCOL
if (client->compat) if(client->compat)
SV_Netchan_Decode(client, msg); SV_Netchan_Decode(client, msg);
#endif #endif
return qtrue; return qtrue;
} }

View file

@ -91,7 +91,7 @@ static void SV_EmitPacketEntities( clientSnapshot_t *from, clientSnapshot_t *to,
if ( newnum == oldnum ) { if ( newnum == oldnum ) {
// delta update from old position // delta update from old position
// because the force parm is qfalse, this will not result // 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); MSG_WriteDeltaEntity (msg, oldent, newent, qfalse, sv.frameTime);
oldindex++; oldindex++;
newindex++; newindex++;
@ -1049,6 +1049,53 @@ static void SV_BuildClientSnapshot( client_t *client ) {
frame->ps.radarInfo = client->radarInfo; 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 SV_SendMessageToClient
@ -1056,7 +1103,8 @@ SV_SendMessageToClient
Called by SV_SendClientSnapshot and SV_SendClientGameState 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 // record information about the message
client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize; client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize;
client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time; client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time;
@ -1088,7 +1136,6 @@ void SV_SendClientSnapshot( client_t *client ) {
return; return;
} }
memset(msg_buf, 0, sizeof(msg_buf));
MSG_Init (&msg, msg_buf, sizeof(msg_buf)); MSG_Init (&msg, msg_buf, sizeof(msg_buf));
msg.allowoverflow = qtrue; msg.allowoverflow = qtrue;
@ -1115,6 +1162,10 @@ void SV_SendClientSnapshot( client_t *client ) {
// Add any download data if the client is downloading // Add any download data if the client is downloading
SV_WriteDownloadToClient( client, &msg ); SV_WriteDownloadToClient( client, &msg );
#ifdef USE_VOIP
SV_WriteVoipToClient( client, &msg );
#endif
// check for overflow // check for overflow
if ( msg.overflowed ) { if ( msg.overflowed ) {
Com_Printf ("WARNING: msg overflowed for %s\n", client->name); Com_Printf ("WARNING: msg overflowed for %s\n", client->name);
@ -1130,16 +1181,19 @@ void SV_SendClientSnapshot( client_t *client ) {
SV_SendClientMessages SV_SendClientMessages
======================= =======================
*/ */
void SV_SendClientMessages( void ) { void SV_SendClientMessages(void)
int i; {
int i;
int rate; int rate;
client_t *c; client_t *c;
// send a message to each connected client // send a message to each connected client
for (i=0, c = svs.clients ; i < sv_maxclients->integer ; i++, c++) { for(i=0; i < sv_maxclients->integer; i++)
if (!c->state) { {
c = &svs.clients[i];
if(!c->state)
continue; // not connected continue; // not connected
}
if(svs.time - c->lastSnapshotTime < c->snapshotMsec * com_timescale->value) if(svs.time - c->lastSnapshotTime < c->snapshotMsec * com_timescale->value)
continue; // It's not time yet continue; // It's not time yet
@ -1171,8 +1225,8 @@ void SV_SendClientMessages( void ) {
} }
// generate and send a new message // generate and send a new message
SV_SendClientSnapshot(c); SV_SendClientSnapshot(c);
c->lastSnapshotTime = svs.time; c->lastSnapshotTime = svs.time;
c->rateDelayed = qfalse; c->rateDelayed = qfalse;
} }

View file

@ -100,7 +100,7 @@ SV_CreateworldSector
Builds a uniformly subdivided tree for the given world size 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; worldSector_t *anode;
vec3_t size; vec3_t size;
vec3_t mins1, maxs1, mins2, maxs2; vec3_t mins1, maxs1, mins2, maxs2;
@ -388,8 +388,8 @@ SV_AreaEntities_r
==================== ====================
*/ */
void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) { static void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) {
svEntity_t *check, *next; svEntity_t *check, *next;
gentity_t *gcheck; gentity_t *gcheck;
worldSector_t *nodestack[ 8 ]; worldSector_t *nodestack[ 8 ];
int iStackPos; 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 ); 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 // if it doesn't have any brushes of a type we
// are looking for, ignore it // 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 i, num;
int touchlist[MAX_GENTITIES]; int touchlist[MAX_GENTITIES];
gentity_t *touch; 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; moveclip_t clip;
int i; int i;
Com_Memset( &clip, 0, sizeof( moveclip_t ) ); Com_Memset ( &clip, 0, sizeof ( moveclip_t ) );
// clip to world // clip to world
CM_BoxTrace( &clip.trace, start, end, mins, maxs, 0, contentmask, cylinder ); CM_BoxTrace( &clip.trace, start, end, mins, maxs, 0, contentmask, cylinder );