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_MIN_TITLE PRODUCT_NAME
#define MAX_MASTER_SERVERS 5 // number of supported master servers
#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_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, //
};
/*

View file

@ -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__

View file

@ -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 ; i<MAX_CHALLENGES ; i++) {
if (NET_CompareAdr(from, svs.challenges[i].adr)) {
if ( challenge == svs.challenges[i].challenge ) {
break; // good
}
for (i=0; i<MAX_CHALLENGES; i++)
{
if (NET_CompareAdr(from, svs.challenges[i].adr))
{
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;
}
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 );

View file

@ -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 ) );

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) );
}
// 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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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 );