mirror of
https://github.com/DrBeef/QVR.git
synced 2024-11-29 07:02:14 +00:00
3766 lines
136 KiB
C
3766 lines
136 KiB
C
/*
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
Copyright (C) 2002 Mathieu Olivier
|
|
Copyright (C) 2003 Forest Hale
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
#include "quakedef.h"
|
|
#include "thread.h"
|
|
#include "lhnet.h"
|
|
|
|
#define HAVE_SNPRINTF
|
|
#define PREFER_PORTABLE_SNPRINTF
|
|
#include "snprintf.h"
|
|
|
|
// for secure rcon authentication
|
|
#include "hmac.h"
|
|
#include "mdfour.h"
|
|
#include <time.h>
|
|
|
|
#define QWMASTER_PORT 27000
|
|
#define DPMASTER_PORT 27950
|
|
|
|
// note this defaults on for dedicated servers, off for listen servers
|
|
cvar_t sv_public = {0, "sv_public", "0", "1: advertises this server on the master server (so that players can find it in the server browser); 0: allow direct queries only; -1: do not respond to direct queries; -2: do not allow anyone to connect; -3: already block at getchallenge level"};
|
|
cvar_t sv_public_rejectreason = {0, "sv_public_rejectreason", "The server is closing.", "Rejection reason for connects when sv_public is -2"};
|
|
static cvar_t sv_heartbeatperiod = {CVAR_SAVE, "sv_heartbeatperiod", "120", "how often to send heartbeat in seconds (only used if sv_public is 1)"};
|
|
extern cvar_t sv_status_privacy;
|
|
|
|
static cvar_t sv_masters [] =
|
|
{
|
|
{CVAR_SAVE, "sv_master1", "", "user-chosen master server 1"},
|
|
{CVAR_SAVE, "sv_master2", "", "user-chosen master server 2"},
|
|
{CVAR_SAVE, "sv_master3", "", "user-chosen master server 3"},
|
|
{CVAR_SAVE, "sv_master4", "", "user-chosen master server 4"},
|
|
{0, "sv_masterextra1", "69.59.212.88", "ghdigital.com - default master server 1 (admin: LordHavoc)"}, // admin: LordHavoc
|
|
{0, "sv_masterextra2", "64.22.107.125", "dpmaster.deathmask.net - default master server 2 (admin: Willis)"}, // admin: Willis
|
|
{0, "sv_masterextra3", "92.62.40.73", "dpmaster.tchr.no - default master server 3 (admin: tChr)"}, // admin: tChr
|
|
#ifdef SUPPORTIPV6
|
|
{0, "sv_masterextra4", "[2a03:4000:2:225::51:334d]:27950", "dpmaster.sudo.rm-f.org - default master server 4 (admin: divVerent)"}, // admin: divVerent
|
|
#endif
|
|
{0, NULL, NULL, NULL}
|
|
};
|
|
|
|
static cvar_t sv_qwmasters [] =
|
|
{
|
|
{CVAR_SAVE, "sv_qwmaster1", "", "user-chosen qwmaster server 1"},
|
|
{CVAR_SAVE, "sv_qwmaster2", "", "user-chosen qwmaster server 2"},
|
|
{CVAR_SAVE, "sv_qwmaster3", "", "user-chosen qwmaster server 3"},
|
|
{CVAR_SAVE, "sv_qwmaster4", "", "user-chosen qwmaster server 4"},
|
|
{0, "sv_qwmasterextra1", "master.quakeservers.net:27000", "Global master server. (admin: unknown)"},
|
|
{0, "sv_qwmasterextra2", "asgaard.morphos-team.net:27000", "Global master server. (admin: unknown)"},
|
|
{0, "sv_qwmasterextra3", "qwmaster.ocrana.de:27000", "German master server. (admin: unknown)"},
|
|
{0, "sv_qwmasterextra4", "masterserver.exhale.de:27000", "German master server. (admin: unknown)"},
|
|
{0, "sv_qwmasterextra5", "qwmaster.fodquake.net:27000", "Global master server. (admin: unknown)"},
|
|
{0, NULL, NULL, NULL}
|
|
};
|
|
|
|
static double nextheartbeattime = 0;
|
|
|
|
sizebuf_t cl_message;
|
|
sizebuf_t sv_message;
|
|
static unsigned char cl_message_buf[NET_MAXMESSAGE];
|
|
static unsigned char sv_message_buf[NET_MAXMESSAGE];
|
|
char cl_readstring[MAX_INPUTLINE];
|
|
char sv_readstring[MAX_INPUTLINE];
|
|
|
|
cvar_t net_messagetimeout = {0, "net_messagetimeout","300", "drops players who have not sent any packets for this many seconds"};
|
|
cvar_t net_connecttimeout = {0, "net_connecttimeout","15", "after requesting a connection, the client must reply within this many seconds or be dropped (cuts down on connect floods). Must be above 10 seconds."};
|
|
cvar_t net_connectfloodblockingtimeout = {0, "net_connectfloodblockingtimeout", "5", "when a connection packet is received, it will block all future connect packets from that IP address for this many seconds (cuts down on connect floods). Note that this does not include retries from the same IP; these are handled earlier and let in."};
|
|
cvar_t net_challengefloodblockingtimeout = {0, "net_challengefloodblockingtimeout", "0.5", "when a challenge packet is received, it will block all future challenge packets from that IP address for this many seconds (cuts down on challenge floods). DarkPlaces clients retry once per second, so this should be <= 1. Failure here may lead to connect attempts failing."};
|
|
cvar_t net_getstatusfloodblockingtimeout = {0, "net_getstatusfloodblockingtimeout", "1", "when a getstatus packet is received, it will block all future getstatus packets from that IP address for this many seconds (cuts down on getstatus floods). DarkPlaces retries every 4 seconds, and qstat retries once per second, so this should be <= 1. Failure here may lead to server not showing up in the server list."};
|
|
cvar_t hostname = {CVAR_SAVE, "hostname", "UNNAMED", "server message to show in server browser"};
|
|
cvar_t developer_networking = {0, "developer_networking", "0", "prints all received and sent packets (recommended only for debugging)"};
|
|
|
|
cvar_t cl_netlocalping = {0, "cl_netlocalping","0", "lags local loopback connection by this much ping time (useful to play more fairly on your own server with people with higher pings)"};
|
|
static cvar_t cl_netpacketloss_send = {0, "cl_netpacketloss_send","0", "drops this percentage of outgoing packets, useful for testing network protocol robustness (jerky movement, prediction errors, etc)"};
|
|
static cvar_t cl_netpacketloss_receive = {0, "cl_netpacketloss_receive","0", "drops this percentage of incoming packets, useful for testing network protocol robustness (jerky movement, effects failing to start, sounds failing to play, etc)"};
|
|
static cvar_t net_slist_queriespersecond = {0, "net_slist_queriespersecond", "20", "how many server information requests to send per second"};
|
|
static cvar_t net_slist_queriesperframe = {0, "net_slist_queriesperframe", "4", "maximum number of server information requests to send each rendered frame (guards against low framerates causing problems)"};
|
|
static cvar_t net_slist_timeout = {0, "net_slist_timeout", "4", "how long to listen for a server information response before giving up"};
|
|
static cvar_t net_slist_pause = {0, "net_slist_pause", "0", "when set to 1, the server list won't update until it is set back to 0"};
|
|
static cvar_t net_slist_maxtries = {0, "net_slist_maxtries", "3", "how many times to ask the same server for information (more times gives better ping reports but takes longer)"};
|
|
static cvar_t net_slist_favorites = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "net_slist_favorites", "", "contains a list of IP addresses and ports to always query explicitly"};
|
|
static cvar_t net_tos_dscp = {CVAR_SAVE, "net_tos_dscp", "32", "DiffServ Codepoint for network sockets (may need game restart to apply)"};
|
|
static cvar_t gameversion = {0, "gameversion", "0", "version of game data (mod-specific) to be sent to querying clients"};
|
|
static cvar_t gameversion_min = {0, "gameversion_min", "-1", "minimum version of game data (mod-specific), when client and server gameversion mismatch in the server browser the server is shown as incompatible; if -1, gameversion is used alone"};
|
|
static cvar_t gameversion_max = {0, "gameversion_max", "-1", "maximum version of game data (mod-specific), when client and server gameversion mismatch in the server browser the server is shown as incompatible; if -1, gameversion is used alone"};
|
|
static cvar_t rcon_restricted_password = {CVAR_PRIVATE, "rcon_restricted_password", "", "password to authenticate rcon commands in restricted mode; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"};
|
|
static cvar_t rcon_restricted_commands = {0, "rcon_restricted_commands", "", "allowed commands for rcon when the restricted mode password was used"};
|
|
static cvar_t rcon_secure_maxdiff = {0, "rcon_secure_maxdiff", "5", "maximum time difference between rcon request and server system clock (to protect against replay attack)"};
|
|
extern cvar_t rcon_secure;
|
|
extern cvar_t rcon_secure_challengetimeout;
|
|
|
|
double masterquerytime = -1000;
|
|
int masterquerycount = 0;
|
|
int masterreplycount = 0;
|
|
int serverquerycount = 0;
|
|
int serverreplycount = 0;
|
|
|
|
challenge_t challenge[MAX_CHALLENGES];
|
|
|
|
/// this is only false if there are still servers left to query
|
|
static qboolean serverlist_querysleep = true;
|
|
static qboolean serverlist_paused = false;
|
|
/// this is pushed a second or two ahead of realtime whenever a master server
|
|
/// reply is received, to avoid issuing queries while master replies are still
|
|
/// flooding in (which would make a mess of the ping times)
|
|
static double serverlist_querywaittime = 0;
|
|
|
|
static int cl_numsockets;
|
|
static lhnetsocket_t *cl_sockets[16];
|
|
static int sv_numsockets;
|
|
static lhnetsocket_t *sv_sockets[16];
|
|
|
|
netconn_t *netconn_list = NULL;
|
|
mempool_t *netconn_mempool = NULL;
|
|
void *netconn_mutex = NULL;
|
|
|
|
cvar_t cl_netport = {0, "cl_port", "0", "forces client to use chosen port number if not 0"};
|
|
cvar_t sv_netport = {0, "port", "26000", "server port for players to connect to"};
|
|
cvar_t net_address = {0, "net_address", "", "network address to open ipv4 ports on (if empty, use default interfaces)"};
|
|
cvar_t net_address_ipv6 = {0, "net_address_ipv6", "", "network address to open ipv6 ports on (if empty, use default interfaces)"};
|
|
|
|
char cl_net_extresponse[NET_EXTRESPONSE_MAX][1400];
|
|
int cl_net_extresponse_count = 0;
|
|
int cl_net_extresponse_last = 0;
|
|
|
|
char sv_net_extresponse[NET_EXTRESPONSE_MAX][1400];
|
|
int sv_net_extresponse_count = 0;
|
|
int sv_net_extresponse_last = 0;
|
|
|
|
// ServerList interface
|
|
serverlist_mask_t serverlist_andmasks[SERVERLIST_ANDMASKCOUNT];
|
|
serverlist_mask_t serverlist_ormasks[SERVERLIST_ORMASKCOUNT];
|
|
|
|
serverlist_infofield_t serverlist_sortbyfield;
|
|
int serverlist_sortflags;
|
|
|
|
int serverlist_viewcount = 0;
|
|
unsigned short serverlist_viewlist[SERVERLIST_VIEWLISTSIZE];
|
|
|
|
int serverlist_maxcachecount = 0;
|
|
int serverlist_cachecount = 0;
|
|
serverlist_entry_t *serverlist_cache = NULL;
|
|
|
|
qboolean serverlist_consoleoutput;
|
|
|
|
static int nFavorites = 0;
|
|
static lhnetaddress_t favorites[MAX_FAVORITESERVERS];
|
|
static int nFavorites_idfp = 0;
|
|
static char favorites_idfp[MAX_FAVORITESERVERS][FP64_SIZE+1];
|
|
|
|
void NetConn_UpdateFavorites(void)
|
|
{
|
|
const char *p;
|
|
nFavorites = 0;
|
|
nFavorites_idfp = 0;
|
|
p = net_slist_favorites.string;
|
|
while((size_t) nFavorites < sizeof(favorites) / sizeof(*favorites) && COM_ParseToken_Console(&p))
|
|
{
|
|
if(com_token[0] != '[' && strlen(com_token) == FP64_SIZE && !strchr(com_token, '.'))
|
|
// currently 44 bytes, longest possible IPv6 address: 39 bytes, so this works
|
|
// (if v6 address contains port, it must start with '[')
|
|
{
|
|
strlcpy(favorites_idfp[nFavorites_idfp], com_token, sizeof(favorites_idfp[nFavorites_idfp]));
|
|
++nFavorites_idfp;
|
|
}
|
|
else
|
|
{
|
|
if(LHNETADDRESS_FromString(&favorites[nFavorites], com_token, 26000))
|
|
++nFavorites;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// helper function to insert a value into the viewset
|
|
/// spare entries will be removed
|
|
static void _ServerList_ViewList_Helper_InsertBefore( int index, serverlist_entry_t *entry )
|
|
{
|
|
int i;
|
|
if( serverlist_viewcount < SERVERLIST_VIEWLISTSIZE ) {
|
|
i = serverlist_viewcount++;
|
|
} else {
|
|
i = SERVERLIST_VIEWLISTSIZE - 1;
|
|
}
|
|
|
|
for( ; i > index ; i-- )
|
|
serverlist_viewlist[ i ] = serverlist_viewlist[ i - 1 ];
|
|
|
|
serverlist_viewlist[index] = (int)(entry - serverlist_cache);
|
|
}
|
|
|
|
/// we suppose serverlist_viewcount to be valid, ie > 0
|
|
static void _ServerList_ViewList_Helper_Remove( int index )
|
|
{
|
|
serverlist_viewcount--;
|
|
for( ; index < serverlist_viewcount ; index++ )
|
|
serverlist_viewlist[index] = serverlist_viewlist[index + 1];
|
|
}
|
|
|
|
/// \returns true if A should be inserted before B
|
|
static qboolean _ServerList_Entry_Compare( serverlist_entry_t *A, serverlist_entry_t *B )
|
|
{
|
|
int result = 0; // > 0 if for numbers A > B and for text if A < B
|
|
|
|
if( serverlist_sortflags & SLSF_FAVORITESFIRST )
|
|
{
|
|
if(A->info.isfavorite != B->info.isfavorite)
|
|
return A->info.isfavorite;
|
|
}
|
|
|
|
switch( serverlist_sortbyfield ) {
|
|
case SLIF_PING:
|
|
result = A->info.ping - B->info.ping;
|
|
break;
|
|
case SLIF_MAXPLAYERS:
|
|
result = A->info.maxplayers - B->info.maxplayers;
|
|
break;
|
|
case SLIF_NUMPLAYERS:
|
|
result = A->info.numplayers - B->info.numplayers;
|
|
break;
|
|
case SLIF_NUMBOTS:
|
|
result = A->info.numbots - B->info.numbots;
|
|
break;
|
|
case SLIF_NUMHUMANS:
|
|
result = A->info.numhumans - B->info.numhumans;
|
|
break;
|
|
case SLIF_FREESLOTS:
|
|
result = A->info.freeslots - B->info.freeslots;
|
|
break;
|
|
case SLIF_PROTOCOL:
|
|
result = A->info.protocol - B->info.protocol;
|
|
break;
|
|
case SLIF_CNAME:
|
|
result = strcmp( B->info.cname, A->info.cname );
|
|
break;
|
|
case SLIF_GAME:
|
|
result = strcasecmp( B->info.game, A->info.game );
|
|
break;
|
|
case SLIF_MAP:
|
|
result = strcasecmp( B->info.map, A->info.map );
|
|
break;
|
|
case SLIF_MOD:
|
|
result = strcasecmp( B->info.mod, A->info.mod );
|
|
break;
|
|
case SLIF_NAME:
|
|
result = strcasecmp( B->info.name, A->info.name );
|
|
break;
|
|
case SLIF_QCSTATUS:
|
|
result = strcasecmp( B->info.qcstatus, A->info.qcstatus ); // not really THAT useful, though
|
|
break;
|
|
case SLIF_ISFAVORITE:
|
|
result = !!B->info.isfavorite - !!A->info.isfavorite;
|
|
break;
|
|
default:
|
|
Con_DPrint( "_ServerList_Entry_Compare: Bad serverlist_sortbyfield!\n" );
|
|
break;
|
|
}
|
|
|
|
if (result != 0)
|
|
{
|
|
if( serverlist_sortflags & SLSF_DESCENDING )
|
|
return result > 0;
|
|
else
|
|
return result < 0;
|
|
}
|
|
|
|
// if the chosen sort key is identical, sort by index
|
|
// (makes this a stable sort, so that later replies from servers won't
|
|
// shuffle the servers around when they have the same ping)
|
|
return A < B;
|
|
}
|
|
|
|
static qboolean _ServerList_CompareInt( int A, serverlist_maskop_t op, int B )
|
|
{
|
|
// This should actually be done with some intermediate and end-of-function return
|
|
switch( op ) {
|
|
case SLMO_LESS:
|
|
return A < B;
|
|
case SLMO_LESSEQUAL:
|
|
return A <= B;
|
|
case SLMO_EQUAL:
|
|
return A == B;
|
|
case SLMO_GREATER:
|
|
return A > B;
|
|
case SLMO_NOTEQUAL:
|
|
return A != B;
|
|
case SLMO_GREATEREQUAL:
|
|
case SLMO_CONTAINS:
|
|
case SLMO_NOTCONTAIN:
|
|
case SLMO_STARTSWITH:
|
|
case SLMO_NOTSTARTSWITH:
|
|
return A >= B;
|
|
default:
|
|
Con_DPrint( "_ServerList_CompareInt: Bad op!\n" );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static qboolean _ServerList_CompareStr( const char *A, serverlist_maskop_t op, const char *B )
|
|
{
|
|
int i;
|
|
char bufferA[ 1400 ], bufferB[ 1400 ]; // should be more than enough
|
|
COM_StringDecolorize(A, 0, bufferA, sizeof(bufferA), false);
|
|
for (i = 0;i < (int)sizeof(bufferA)-1 && bufferA[i];i++)
|
|
bufferA[i] = (bufferA[i] >= 'A' && bufferA[i] <= 'Z') ? (bufferA[i] + 'a' - 'A') : bufferA[i];
|
|
bufferA[i] = 0;
|
|
for (i = 0;i < (int)sizeof(bufferB)-1 && B[i];i++)
|
|
bufferB[i] = (B[i] >= 'A' && B[i] <= 'Z') ? (B[i] + 'a' - 'A') : B[i];
|
|
bufferB[i] = 0;
|
|
|
|
// Same here, also using an intermediate & final return would be more appropriate
|
|
// A info B mask
|
|
switch( op ) {
|
|
case SLMO_CONTAINS:
|
|
return *bufferB && !!strstr( bufferA, bufferB ); // we want a real bool
|
|
case SLMO_NOTCONTAIN:
|
|
return !*bufferB || !strstr( bufferA, bufferB );
|
|
case SLMO_STARTSWITH:
|
|
//Con_Printf("startsWith: %s %s\n", bufferA, bufferB);
|
|
return *bufferB && !memcmp(bufferA, bufferB, strlen(bufferB));
|
|
case SLMO_NOTSTARTSWITH:
|
|
return !*bufferB || memcmp(bufferA, bufferB, strlen(bufferB));
|
|
case SLMO_LESS:
|
|
return strcmp( bufferA, bufferB ) < 0;
|
|
case SLMO_LESSEQUAL:
|
|
return strcmp( bufferA, bufferB ) <= 0;
|
|
case SLMO_EQUAL:
|
|
return strcmp( bufferA, bufferB ) == 0;
|
|
case SLMO_GREATER:
|
|
return strcmp( bufferA, bufferB ) > 0;
|
|
case SLMO_NOTEQUAL:
|
|
return strcmp( bufferA, bufferB ) != 0;
|
|
case SLMO_GREATEREQUAL:
|
|
return strcmp( bufferA, bufferB ) >= 0;
|
|
default:
|
|
Con_DPrint( "_ServerList_CompareStr: Bad op!\n" );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static qboolean _ServerList_Entry_Mask( serverlist_mask_t *mask, serverlist_info_t *info )
|
|
{
|
|
if( !_ServerList_CompareInt( info->ping, mask->tests[SLIF_PING], mask->info.ping ) )
|
|
return false;
|
|
if( !_ServerList_CompareInt( info->maxplayers, mask->tests[SLIF_MAXPLAYERS], mask->info.maxplayers ) )
|
|
return false;
|
|
if( !_ServerList_CompareInt( info->numplayers, mask->tests[SLIF_NUMPLAYERS], mask->info.numplayers ) )
|
|
return false;
|
|
if( !_ServerList_CompareInt( info->numbots, mask->tests[SLIF_NUMBOTS], mask->info.numbots ) )
|
|
return false;
|
|
if( !_ServerList_CompareInt( info->numhumans, mask->tests[SLIF_NUMHUMANS], mask->info.numhumans ) )
|
|
return false;
|
|
if( !_ServerList_CompareInt( info->freeslots, mask->tests[SLIF_FREESLOTS], mask->info.freeslots ) )
|
|
return false;
|
|
if( !_ServerList_CompareInt( info->protocol, mask->tests[SLIF_PROTOCOL], mask->info.protocol ))
|
|
return false;
|
|
if( *mask->info.cname
|
|
&& !_ServerList_CompareStr( info->cname, mask->tests[SLIF_CNAME], mask->info.cname ) )
|
|
return false;
|
|
if( *mask->info.game
|
|
&& !_ServerList_CompareStr( info->game, mask->tests[SLIF_GAME], mask->info.game ) )
|
|
return false;
|
|
if( *mask->info.mod
|
|
&& !_ServerList_CompareStr( info->mod, mask->tests[SLIF_MOD], mask->info.mod ) )
|
|
return false;
|
|
if( *mask->info.map
|
|
&& !_ServerList_CompareStr( info->map, mask->tests[SLIF_MAP], mask->info.map ) )
|
|
return false;
|
|
if( *mask->info.name
|
|
&& !_ServerList_CompareStr( info->name, mask->tests[SLIF_NAME], mask->info.name ) )
|
|
return false;
|
|
if( *mask->info.qcstatus
|
|
&& !_ServerList_CompareStr( info->qcstatus, mask->tests[SLIF_QCSTATUS], mask->info.qcstatus ) )
|
|
return false;
|
|
if( *mask->info.players
|
|
&& !_ServerList_CompareStr( info->players, mask->tests[SLIF_PLAYERS], mask->info.players ) )
|
|
return false;
|
|
if( !_ServerList_CompareInt( info->isfavorite, mask->tests[SLIF_ISFAVORITE], mask->info.isfavorite ))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static void ServerList_ViewList_Insert( serverlist_entry_t *entry )
|
|
{
|
|
int start, end, mid, i;
|
|
lhnetaddress_t addr;
|
|
|
|
// reject incompatible servers
|
|
if(
|
|
entry->info.gameversion != gameversion.integer
|
|
&&
|
|
!(
|
|
gameversion_min.integer >= 0 // min/max range set by user/mod?
|
|
&& gameversion_max.integer >= 0
|
|
&& gameversion_min.integer <= entry->info.gameversion // version of server in min/max range?
|
|
&& gameversion_max.integer >= entry->info.gameversion
|
|
)
|
|
)
|
|
return;
|
|
|
|
// refresh the "favorite" status
|
|
entry->info.isfavorite = false;
|
|
if(LHNETADDRESS_FromString(&addr, entry->info.cname, 26000))
|
|
{
|
|
char idfp[FP64_SIZE+1];
|
|
for(i = 0; i < nFavorites; ++i)
|
|
{
|
|
if(LHNETADDRESS_Compare(&addr, &favorites[i]) == 0)
|
|
{
|
|
entry->info.isfavorite = true;
|
|
break;
|
|
}
|
|
}
|
|
if(Crypto_RetrieveHostKey(&addr, 0, NULL, 0, idfp, sizeof(idfp), NULL))
|
|
{
|
|
for(i = 0; i < nFavorites_idfp; ++i)
|
|
{
|
|
if(!strcmp(idfp, favorites_idfp[i]))
|
|
{
|
|
entry->info.isfavorite = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME: change this to be more readable (...)
|
|
// now check whether it passes through the masks
|
|
for( start = 0 ; start < SERVERLIST_ANDMASKCOUNT && serverlist_andmasks[start].active; start++ )
|
|
if( !_ServerList_Entry_Mask( &serverlist_andmasks[start], &entry->info ) )
|
|
return;
|
|
|
|
for( start = 0 ; start < SERVERLIST_ORMASKCOUNT && serverlist_ormasks[start].active ; start++ )
|
|
if( _ServerList_Entry_Mask( &serverlist_ormasks[start], &entry->info ) )
|
|
break;
|
|
if( start == SERVERLIST_ORMASKCOUNT || (start > 0 && !serverlist_ormasks[start].active) )
|
|
return;
|
|
|
|
if( !serverlist_viewcount ) {
|
|
_ServerList_ViewList_Helper_InsertBefore( 0, entry );
|
|
return;
|
|
}
|
|
// ok, insert it, we just need to find out where exactly:
|
|
|
|
// two special cases
|
|
// check whether to insert it as new first item
|
|
if( _ServerList_Entry_Compare( entry, ServerList_GetViewEntry(0) ) ) {
|
|
_ServerList_ViewList_Helper_InsertBefore( 0, entry );
|
|
return;
|
|
} // check whether to insert it as new last item
|
|
else if( !_ServerList_Entry_Compare( entry, ServerList_GetViewEntry(serverlist_viewcount - 1) ) ) {
|
|
_ServerList_ViewList_Helper_InsertBefore( serverlist_viewcount, entry );
|
|
return;
|
|
}
|
|
start = 0;
|
|
end = serverlist_viewcount - 1;
|
|
while( end > start + 1 )
|
|
{
|
|
mid = (start + end) / 2;
|
|
// test the item that lies in the middle between start and end
|
|
if( _ServerList_Entry_Compare( entry, ServerList_GetViewEntry(mid) ) )
|
|
// the item has to be in the upper half
|
|
end = mid;
|
|
else
|
|
// the item has to be in the lower half
|
|
start = mid;
|
|
}
|
|
_ServerList_ViewList_Helper_InsertBefore( start + 1, entry );
|
|
}
|
|
|
|
static void ServerList_ViewList_Remove( serverlist_entry_t *entry )
|
|
{
|
|
int i;
|
|
for( i = 0; i < serverlist_viewcount; i++ )
|
|
{
|
|
if (ServerList_GetViewEntry(i) == entry)
|
|
{
|
|
_ServerList_ViewList_Helper_Remove(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ServerList_RebuildViewList(void)
|
|
{
|
|
int i;
|
|
|
|
serverlist_viewcount = 0;
|
|
for( i = 0 ; i < serverlist_cachecount ; i++ ) {
|
|
serverlist_entry_t *entry = &serverlist_cache[i];
|
|
// also display entries that are currently being refreshed [11/8/2007 Black]
|
|
if( entry->query == SQS_QUERIED || entry->query == SQS_REFRESHING )
|
|
ServerList_ViewList_Insert( entry );
|
|
}
|
|
}
|
|
|
|
void ServerList_ResetMasks(void)
|
|
{
|
|
int i;
|
|
|
|
memset( &serverlist_andmasks, 0, sizeof( serverlist_andmasks ) );
|
|
memset( &serverlist_ormasks, 0, sizeof( serverlist_ormasks ) );
|
|
// numbots needs to be compared to -1 to always succeed
|
|
for(i = 0; i < SERVERLIST_ANDMASKCOUNT; ++i)
|
|
serverlist_andmasks[i].info.numbots = -1;
|
|
for(i = 0; i < SERVERLIST_ORMASKCOUNT; ++i)
|
|
serverlist_ormasks[i].info.numbots = -1;
|
|
}
|
|
|
|
void ServerList_GetPlayerStatistics(int *numplayerspointer, int *maxplayerspointer)
|
|
{
|
|
int i;
|
|
int numplayers = 0, maxplayers = 0;
|
|
for (i = 0;i < serverlist_cachecount;i++)
|
|
{
|
|
if (serverlist_cache[i].query == SQS_QUERIED)
|
|
{
|
|
numplayers += serverlist_cache[i].info.numhumans;
|
|
maxplayers += serverlist_cache[i].info.maxplayers;
|
|
}
|
|
}
|
|
*numplayerspointer = numplayers;
|
|
*maxplayerspointer = maxplayers;
|
|
}
|
|
|
|
#if 0
|
|
static void _ServerList_Test(void)
|
|
{
|
|
int i;
|
|
if (serverlist_maxcachecount <= 1024)
|
|
{
|
|
serverlist_maxcachecount = 1024;
|
|
serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount);
|
|
}
|
|
for( i = 0 ; i < 1024 ; i++ ) {
|
|
memset( &serverlist_cache[serverlist_cachecount], 0, sizeof( serverlist_entry_t ) );
|
|
serverlist_cache[serverlist_cachecount].info.ping = 1000 + 1024 - i;
|
|
dpsnprintf( serverlist_cache[serverlist_cachecount].info.name, sizeof(serverlist_cache[serverlist_cachecount].info.name), "Black's ServerList Test %i", i );
|
|
serverlist_cache[serverlist_cachecount].finished = true;
|
|
dpsnprintf( serverlist_cache[serverlist_cachecount].line1, sizeof(serverlist_cache[serverlist_cachecount].info.line1), "%i %s", serverlist_cache[serverlist_cachecount].info.ping, serverlist_cache[serverlist_cachecount].info.name );
|
|
ServerList_ViewList_Insert( &serverlist_cache[serverlist_cachecount] );
|
|
serverlist_cachecount++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void ServerList_QueryList(qboolean resetcache, qboolean querydp, qboolean queryqw, qboolean consoleoutput)
|
|
{
|
|
masterquerytime = realtime;
|
|
masterquerycount = 0;
|
|
masterreplycount = 0;
|
|
if( resetcache ) {
|
|
serverquerycount = 0;
|
|
serverreplycount = 0;
|
|
serverlist_cachecount = 0;
|
|
serverlist_viewcount = 0;
|
|
serverlist_maxcachecount = 0;
|
|
serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount);
|
|
} else {
|
|
// refresh all entries
|
|
int n;
|
|
for( n = 0 ; n < serverlist_cachecount ; n++ ) {
|
|
serverlist_entry_t *entry = &serverlist_cache[ n ];
|
|
entry->query = SQS_REFRESHING;
|
|
entry->querycounter = 0;
|
|
}
|
|
}
|
|
serverlist_consoleoutput = consoleoutput;
|
|
|
|
//_ServerList_Test();
|
|
|
|
NetConn_QueryMasters(querydp, queryqw);
|
|
}
|
|
|
|
// rest
|
|
|
|
int NetConn_Read(lhnetsocket_t *mysocket, void *data, int maxlength, lhnetaddress_t *peeraddress)
|
|
{
|
|
int length;
|
|
int i;
|
|
if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex)
|
|
Thread_LockMutex(netconn_mutex);
|
|
length = LHNET_Read(mysocket, data, maxlength, peeraddress);
|
|
if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex)
|
|
Thread_UnlockMutex(netconn_mutex);
|
|
if (length == 0)
|
|
return 0;
|
|
if (cl_netpacketloss_receive.integer)
|
|
for (i = 0;i < cl_numsockets;i++)
|
|
if (cl_sockets[i] == mysocket && (rand() % 100) < cl_netpacketloss_receive.integer)
|
|
return 0;
|
|
if (developer_networking.integer)
|
|
{
|
|
char addressstring[128], addressstring2[128];
|
|
LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), addressstring, sizeof(addressstring), true);
|
|
if (length > 0)
|
|
{
|
|
LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true);
|
|
Con_Printf("LHNET_Read(%p (%s), %p, %i, %p) = %i from %s:\n", (void *)mysocket, addressstring, (void *)data, maxlength, (void *)peeraddress, length, addressstring2);
|
|
Com_HexDumpToConsole((unsigned char *)data, length);
|
|
}
|
|
else
|
|
Con_Printf("LHNET_Read(%p (%s), %p, %i, %p) = %i\n", (void *)mysocket, addressstring, (void *)data, maxlength, (void *)peeraddress, length);
|
|
}
|
|
return length;
|
|
}
|
|
|
|
int NetConn_Write(lhnetsocket_t *mysocket, const void *data, int length, const lhnetaddress_t *peeraddress)
|
|
{
|
|
int ret;
|
|
int i;
|
|
if (cl_netpacketloss_send.integer)
|
|
for (i = 0;i < cl_numsockets;i++)
|
|
if (cl_sockets[i] == mysocket && (rand() % 100) < cl_netpacketloss_send.integer)
|
|
return length;
|
|
if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex)
|
|
Thread_LockMutex(netconn_mutex);
|
|
ret = LHNET_Write(mysocket, data, length, peeraddress);
|
|
if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex)
|
|
Thread_UnlockMutex(netconn_mutex);
|
|
if (developer_networking.integer)
|
|
{
|
|
char addressstring[128], addressstring2[128];
|
|
LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), addressstring, sizeof(addressstring), true);
|
|
LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true);
|
|
Con_Printf("LHNET_Write(%p (%s), %p, %i, %p (%s)) = %i%s\n", (void *)mysocket, addressstring, (void *)data, length, (void *)peeraddress, addressstring2, length, ret == length ? "" : " (ERROR)");
|
|
Com_HexDumpToConsole((unsigned char *)data, length);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int NetConn_WriteString(lhnetsocket_t *mysocket, const char *string, const lhnetaddress_t *peeraddress)
|
|
{
|
|
// note this does not include the trailing NULL because we add that in the parser
|
|
return NetConn_Write(mysocket, string, (int)strlen(string), peeraddress);
|
|
}
|
|
|
|
qboolean NetConn_CanSend(netconn_t *conn)
|
|
{
|
|
conn->outgoing_packetcounter = (conn->outgoing_packetcounter + 1) % NETGRAPH_PACKETS;
|
|
conn->outgoing_netgraph[conn->outgoing_packetcounter].time = realtime;
|
|
conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_NOPACKET;
|
|
conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes = NETGRAPH_NOPACKET;
|
|
conn->outgoing_netgraph[conn->outgoing_packetcounter].ackbytes = NETGRAPH_NOPACKET;
|
|
if (realtime > conn->cleartime)
|
|
return true;
|
|
else
|
|
{
|
|
conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_CHOKEDPACKET;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol, int rate, qboolean quakesignon_suppressreliables)
|
|
{
|
|
int totallen = 0;
|
|
unsigned char sendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE];
|
|
unsigned char cryptosendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE];
|
|
|
|
// if this packet was supposedly choked, but we find ourselves sending one
|
|
// anyway, make sure the size counting starts at zero
|
|
// (this mostly happens on level changes and disconnects and such)
|
|
if (conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes == NETGRAPH_CHOKEDPACKET)
|
|
conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_NOPACKET;
|
|
|
|
if (protocol == PROTOCOL_QUAKEWORLD)
|
|
{
|
|
int packetLen;
|
|
qboolean sendreliable;
|
|
|
|
// note that it is ok to send empty messages to the qw server,
|
|
// otherwise it won't respond to us at all
|
|
|
|
sendreliable = false;
|
|
// if the remote side dropped the last reliable message, resend it
|
|
if (conn->qw.incoming_acknowledged > conn->qw.last_reliable_sequence && conn->qw.incoming_reliable_acknowledged != conn->qw.reliable_sequence)
|
|
sendreliable = true;
|
|
// if the reliable transmit buffer is empty, copy the current message out
|
|
if (!conn->sendMessageLength && conn->message.cursize)
|
|
{
|
|
memcpy (conn->sendMessage, conn->message.data, conn->message.cursize);
|
|
conn->sendMessageLength = conn->message.cursize;
|
|
SZ_Clear(&conn->message); // clear the message buffer
|
|
conn->qw.reliable_sequence ^= 1;
|
|
sendreliable = true;
|
|
}
|
|
// outgoing unreliable packet number, and outgoing reliable packet number (0 or 1)
|
|
StoreLittleLong(sendbuffer, (unsigned int)conn->outgoing_unreliable_sequence | ((unsigned int)sendreliable<<31));
|
|
// last received unreliable packet number, and last received reliable packet number (0 or 1)
|
|
StoreLittleLong(sendbuffer + 4, (unsigned int)conn->qw.incoming_sequence | ((unsigned int)conn->qw.incoming_reliable_sequence<<31));
|
|
packetLen = 8;
|
|
conn->outgoing_unreliable_sequence++;
|
|
// client sends qport in every packet
|
|
if (conn == cls.netcon)
|
|
{
|
|
*((short *)(sendbuffer + 8)) = LittleShort(cls.qw_qport);
|
|
packetLen += 2;
|
|
// also update cls.qw_outgoing_sequence
|
|
cls.qw_outgoing_sequence = conn->outgoing_unreliable_sequence;
|
|
}
|
|
if (packetLen + (sendreliable ? conn->sendMessageLength : 0) > 1400)
|
|
{
|
|
Con_Printf ("NetConn_SendUnreliableMessage: reliable message too big %u\n", data->cursize);
|
|
return -1;
|
|
}
|
|
|
|
conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += packetLen + 28;
|
|
|
|
// add the reliable message if there is one
|
|
if (sendreliable)
|
|
{
|
|
conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += conn->sendMessageLength + 28;
|
|
memcpy(sendbuffer + packetLen, conn->sendMessage, conn->sendMessageLength);
|
|
packetLen += conn->sendMessageLength;
|
|
conn->qw.last_reliable_sequence = conn->outgoing_unreliable_sequence;
|
|
}
|
|
|
|
// add the unreliable message if possible
|
|
if (packetLen + data->cursize <= 1400)
|
|
{
|
|
conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += data->cursize + 28;
|
|
memcpy(sendbuffer + packetLen, data->data, data->cursize);
|
|
packetLen += data->cursize;
|
|
}
|
|
|
|
NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress);
|
|
|
|
conn->packetsSent++;
|
|
conn->unreliableMessagesSent++;
|
|
|
|
totallen += packetLen + 28;
|
|
}
|
|
else
|
|
{
|
|
unsigned int packetLen;
|
|
unsigned int dataLen;
|
|
unsigned int eom;
|
|
const void *sendme;
|
|
size_t sendmelen;
|
|
|
|
// if a reliable message fragment has been lost, send it again
|
|
if (conn->sendMessageLength && (realtime - conn->lastSendTime) > 1.0)
|
|
{
|
|
if (conn->sendMessageLength <= MAX_PACKETFRAGMENT)
|
|
{
|
|
dataLen = conn->sendMessageLength;
|
|
eom = NETFLAG_EOM;
|
|
}
|
|
else
|
|
{
|
|
dataLen = MAX_PACKETFRAGMENT;
|
|
eom = 0;
|
|
}
|
|
|
|
packetLen = NET_HEADERSIZE + dataLen;
|
|
|
|
StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom));
|
|
StoreBigLong(sendbuffer + 4, conn->nq.sendSequence - 1);
|
|
memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen);
|
|
|
|
conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += packetLen + 28;
|
|
|
|
sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer));
|
|
if (sendme && NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress) == (int)sendmelen)
|
|
{
|
|
conn->lastSendTime = realtime;
|
|
conn->packetsReSent++;
|
|
}
|
|
|
|
totallen += sendmelen + 28;
|
|
}
|
|
|
|
// if we have a new reliable message to send, do so
|
|
if (!conn->sendMessageLength && conn->message.cursize && !quakesignon_suppressreliables)
|
|
{
|
|
if (conn->message.cursize > (int)sizeof(conn->sendMessage))
|
|
{
|
|
Con_Printf("NetConn_SendUnreliableMessage: reliable message too big (%u > %u)\n", conn->message.cursize, (int)sizeof(conn->sendMessage));
|
|
conn->message.overflowed = true;
|
|
return -1;
|
|
}
|
|
|
|
if (developer_networking.integer && conn == cls.netcon)
|
|
{
|
|
Con_Print("client sending reliable message to server:\n");
|
|
SZ_HexDumpToConsole(&conn->message);
|
|
}
|
|
|
|
memcpy(conn->sendMessage, conn->message.data, conn->message.cursize);
|
|
conn->sendMessageLength = conn->message.cursize;
|
|
SZ_Clear(&conn->message);
|
|
|
|
if (conn->sendMessageLength <= MAX_PACKETFRAGMENT)
|
|
{
|
|
dataLen = conn->sendMessageLength;
|
|
eom = NETFLAG_EOM;
|
|
}
|
|
else
|
|
{
|
|
dataLen = MAX_PACKETFRAGMENT;
|
|
eom = 0;
|
|
}
|
|
|
|
packetLen = NET_HEADERSIZE + dataLen;
|
|
|
|
StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom));
|
|
StoreBigLong(sendbuffer + 4, conn->nq.sendSequence);
|
|
memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen);
|
|
|
|
conn->nq.sendSequence++;
|
|
|
|
conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += packetLen + 28;
|
|
|
|
sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer));
|
|
if(sendme)
|
|
NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress);
|
|
|
|
conn->lastSendTime = realtime;
|
|
conn->packetsSent++;
|
|
conn->reliableMessagesSent++;
|
|
|
|
totallen += sendmelen + 28;
|
|
}
|
|
|
|
// if we have an unreliable message to send, do so
|
|
if (data->cursize)
|
|
{
|
|
packetLen = NET_HEADERSIZE + data->cursize;
|
|
|
|
if (packetLen > (int)sizeof(sendbuffer))
|
|
{
|
|
Con_Printf("NetConn_SendUnreliableMessage: message too big %u\n", data->cursize);
|
|
return -1;
|
|
}
|
|
|
|
StoreBigLong(sendbuffer, packetLen | NETFLAG_UNRELIABLE);
|
|
StoreBigLong(sendbuffer + 4, conn->outgoing_unreliable_sequence);
|
|
memcpy(sendbuffer + NET_HEADERSIZE, data->data, data->cursize);
|
|
|
|
conn->outgoing_unreliable_sequence++;
|
|
|
|
conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += packetLen + 28;
|
|
|
|
sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer));
|
|
if(sendme)
|
|
NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress);
|
|
|
|
conn->packetsSent++;
|
|
conn->unreliableMessagesSent++;
|
|
|
|
totallen += sendmelen + 28;
|
|
}
|
|
}
|
|
|
|
// delay later packets to obey rate limit
|
|
if (conn->cleartime < realtime - 0.1)
|
|
conn->cleartime = realtime - 0.1;
|
|
conn->cleartime = conn->cleartime + (double)totallen / (double)rate;
|
|
if (conn->cleartime < realtime)
|
|
conn->cleartime = realtime;
|
|
|
|
return 0;
|
|
}
|
|
|
|
qboolean NetConn_HaveClientPorts(void)
|
|
{
|
|
return !!cl_numsockets;
|
|
}
|
|
|
|
qboolean NetConn_HaveServerPorts(void)
|
|
{
|
|
return !!sv_numsockets;
|
|
}
|
|
|
|
void NetConn_CloseClientPorts(void)
|
|
{
|
|
for (;cl_numsockets > 0;cl_numsockets--)
|
|
if (cl_sockets[cl_numsockets - 1])
|
|
LHNET_CloseSocket(cl_sockets[cl_numsockets - 1]);
|
|
}
|
|
|
|
static void NetConn_OpenClientPort(const char *addressstring, lhnetaddresstype_t addresstype, int defaultport)
|
|
{
|
|
lhnetaddress_t address;
|
|
lhnetsocket_t *s;
|
|
int success;
|
|
char addressstring2[1024];
|
|
if (addressstring && addressstring[0])
|
|
success = LHNETADDRESS_FromString(&address, addressstring, defaultport);
|
|
else
|
|
success = LHNETADDRESS_FromPort(&address, addresstype, defaultport);
|
|
if (success)
|
|
{
|
|
if ((s = LHNET_OpenSocket_Connectionless(&address)))
|
|
{
|
|
cl_sockets[cl_numsockets++] = s;
|
|
LHNETADDRESS_ToString(LHNET_AddressFromSocket(s), addressstring2, sizeof(addressstring2), true);
|
|
if (addresstype != LHNETADDRESSTYPE_LOOP)
|
|
Con_Printf("Client opened a socket on address %s\n", addressstring2);
|
|
}
|
|
else
|
|
{
|
|
LHNETADDRESS_ToString(&address, addressstring2, sizeof(addressstring2), true);
|
|
Con_Printf("Client failed to open a socket on address %s\n", addressstring2);
|
|
}
|
|
}
|
|
else
|
|
Con_Printf("Client unable to parse address %s\n", addressstring);
|
|
}
|
|
|
|
void NetConn_OpenClientPorts(void)
|
|
{
|
|
int port;
|
|
NetConn_CloseClientPorts();
|
|
|
|
SV_LockThreadMutex(); // FIXME recursive?
|
|
Crypto_LoadKeys(); // client sockets
|
|
SV_UnlockThreadMutex();
|
|
|
|
port = bound(0, cl_netport.integer, 65535);
|
|
if (cl_netport.integer != port)
|
|
Cvar_SetValueQuick(&cl_netport, port);
|
|
if(port == 0)
|
|
Con_Printf("Client using an automatically assigned port\n");
|
|
else
|
|
Con_Printf("Client using port %i\n", port);
|
|
NetConn_OpenClientPort(NULL, LHNETADDRESSTYPE_LOOP, 2);
|
|
NetConn_OpenClientPort(net_address.string, LHNETADDRESSTYPE_INET4, port);
|
|
#ifdef SUPPORTIPV6
|
|
NetConn_OpenClientPort(net_address_ipv6.string, LHNETADDRESSTYPE_INET6, port);
|
|
#endif
|
|
}
|
|
|
|
void NetConn_CloseServerPorts(void)
|
|
{
|
|
for (;sv_numsockets > 0;sv_numsockets--)
|
|
if (sv_sockets[sv_numsockets - 1])
|
|
LHNET_CloseSocket(sv_sockets[sv_numsockets - 1]);
|
|
}
|
|
|
|
static qboolean NetConn_OpenServerPort(const char *addressstring, lhnetaddresstype_t addresstype, int defaultport, int range)
|
|
{
|
|
lhnetaddress_t address;
|
|
lhnetsocket_t *s;
|
|
int port;
|
|
char addressstring2[1024];
|
|
int success;
|
|
|
|
for (port = defaultport; port <= defaultport + range; port++)
|
|
{
|
|
if (addressstring && addressstring[0])
|
|
success = LHNETADDRESS_FromString(&address, addressstring, port);
|
|
else
|
|
success = LHNETADDRESS_FromPort(&address, addresstype, port);
|
|
if (success)
|
|
{
|
|
if ((s = LHNET_OpenSocket_Connectionless(&address)))
|
|
{
|
|
sv_sockets[sv_numsockets++] = s;
|
|
LHNETADDRESS_ToString(LHNET_AddressFromSocket(s), addressstring2, sizeof(addressstring2), true);
|
|
if (addresstype != LHNETADDRESSTYPE_LOOP)
|
|
Con_Printf("Server listening on address %s\n", addressstring2);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
LHNETADDRESS_ToString(&address, addressstring2, sizeof(addressstring2), true);
|
|
Con_Printf("Server failed to open socket on address %s\n", addressstring2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("Server unable to parse address %s\n", addressstring);
|
|
// if it cant parse one address, it wont be able to parse another for sure
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void NetConn_OpenServerPorts(int opennetports)
|
|
{
|
|
int port;
|
|
NetConn_CloseServerPorts();
|
|
|
|
SV_LockThreadMutex(); // FIXME recursive?
|
|
Crypto_LoadKeys(); // server sockets
|
|
SV_UnlockThreadMutex();
|
|
|
|
NetConn_UpdateSockets();
|
|
port = bound(0, sv_netport.integer, 65535);
|
|
if (port == 0)
|
|
port = 26000;
|
|
Con_Printf("Server using port %i\n", port);
|
|
if (sv_netport.integer != port)
|
|
Cvar_SetValueQuick(&sv_netport, port);
|
|
if (cls.state != ca_dedicated)
|
|
NetConn_OpenServerPort(NULL, LHNETADDRESSTYPE_LOOP, 1, 1);
|
|
if (opennetports)
|
|
{
|
|
#ifdef SUPPORTIPV6
|
|
qboolean ip4success = NetConn_OpenServerPort(net_address.string, LHNETADDRESSTYPE_INET4, port, 100);
|
|
NetConn_OpenServerPort(net_address_ipv6.string, LHNETADDRESSTYPE_INET6, port, ip4success ? 1 : 100);
|
|
#else
|
|
NetConn_OpenServerPort(net_address.string, LHNETADDRESSTYPE_INET4, port, 100);
|
|
#endif
|
|
}
|
|
if (sv_numsockets == 0)
|
|
Host_Error("NetConn_OpenServerPorts: unable to open any ports!");
|
|
}
|
|
|
|
lhnetsocket_t *NetConn_ChooseClientSocketForAddress(lhnetaddress_t *address)
|
|
{
|
|
int i, a = LHNETADDRESS_GetAddressType(address);
|
|
for (i = 0;i < cl_numsockets;i++)
|
|
if (cl_sockets[i] && LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])) == a)
|
|
return cl_sockets[i];
|
|
return NULL;
|
|
}
|
|
|
|
lhnetsocket_t *NetConn_ChooseServerSocketForAddress(lhnetaddress_t *address)
|
|
{
|
|
int i, a = LHNETADDRESS_GetAddressType(address);
|
|
for (i = 0;i < sv_numsockets;i++)
|
|
if (sv_sockets[i] && LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(sv_sockets[i])) == a)
|
|
return sv_sockets[i];
|
|
return NULL;
|
|
}
|
|
|
|
netconn_t *NetConn_Open(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress)
|
|
{
|
|
netconn_t *conn;
|
|
conn = (netconn_t *)Mem_Alloc(netconn_mempool, sizeof(*conn));
|
|
conn->mysocket = mysocket;
|
|
conn->peeraddress = *peeraddress;
|
|
conn->lastMessageTime = realtime;
|
|
conn->message.data = conn->messagedata;
|
|
conn->message.maxsize = sizeof(conn->messagedata);
|
|
conn->message.cursize = 0;
|
|
// LordHavoc: (inspired by ProQuake) use a short connect timeout to
|
|
// reduce effectiveness of connection request floods
|
|
conn->timeout = realtime + net_connecttimeout.value;
|
|
LHNETADDRESS_ToString(&conn->peeraddress, conn->address, sizeof(conn->address), true);
|
|
conn->next = netconn_list;
|
|
netconn_list = conn;
|
|
return conn;
|
|
}
|
|
|
|
void NetConn_ClearFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength);
|
|
void NetConn_Close(netconn_t *conn)
|
|
{
|
|
netconn_t *c;
|
|
// remove connection from list
|
|
|
|
// allow the client to reconnect immediately
|
|
NetConn_ClearFlood(&(conn->peeraddress), sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0]));
|
|
|
|
if (conn == netconn_list)
|
|
netconn_list = conn->next;
|
|
else
|
|
{
|
|
for (c = netconn_list;c;c = c->next)
|
|
{
|
|
if (c->next == conn)
|
|
{
|
|
c->next = conn->next;
|
|
break;
|
|
}
|
|
}
|
|
// not found in list, we'll avoid crashing here...
|
|
if (!c)
|
|
return;
|
|
}
|
|
// free connection
|
|
Mem_Free(conn);
|
|
}
|
|
|
|
static int clientport = -1;
|
|
static int clientport2 = -1;
|
|
static int hostport = -1;
|
|
void NetConn_UpdateSockets(void)
|
|
{
|
|
int i, j;
|
|
|
|
// TODO add logic to automatically close sockets if needed
|
|
LHNET_DefaultDSCP(net_tos_dscp.integer);
|
|
|
|
if (cls.state != ca_dedicated)
|
|
{
|
|
if (clientport2 != cl_netport.integer)
|
|
{
|
|
clientport2 = cl_netport.integer;
|
|
if (cls.state == ca_connected)
|
|
Con_Print("Changing \"cl_port\" will not take effect until you reconnect.\n");
|
|
}
|
|
if (cls.state == ca_disconnected && clientport != clientport2)
|
|
{
|
|
clientport = clientport2;
|
|
NetConn_CloseClientPorts();
|
|
}
|
|
if (cl_numsockets == 0)
|
|
NetConn_OpenClientPorts();
|
|
}
|
|
|
|
if (hostport != sv_netport.integer)
|
|
{
|
|
hostport = sv_netport.integer;
|
|
if (sv.active)
|
|
Con_Print("Changing \"port\" will not take effect until \"map\" command is executed.\n");
|
|
}
|
|
|
|
for (j = 0;j < MAX_RCONS;j++)
|
|
{
|
|
i = (cls.rcon_ringpos + j + 1) % MAX_RCONS;
|
|
if(cls.rcon_commands[i][0])
|
|
{
|
|
if(realtime > cls.rcon_timeout[i])
|
|
{
|
|
char s[128];
|
|
LHNETADDRESS_ToString(&cls.rcon_addresses[i], s, sizeof(s), true);
|
|
Con_Printf("rcon to %s (for command %s) failed: challenge request timed out\n", s, cls.rcon_commands[i]);
|
|
cls.rcon_commands[i][0] = 0;
|
|
--cls.rcon_trying;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int NetConn_ReceivedMessage(netconn_t *conn, const unsigned char *data, size_t length, protocolversion_t protocol, double newtimeout)
|
|
{
|
|
int originallength = length;
|
|
unsigned char sendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE];
|
|
unsigned char cryptosendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE];
|
|
unsigned char cryptoreadbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE];
|
|
if (length < 8)
|
|
return 0;
|
|
|
|
if (protocol == PROTOCOL_QUAKEWORLD)
|
|
{
|
|
int sequence, sequence_ack;
|
|
int reliable_ack, reliable_message;
|
|
int count;
|
|
//int qport;
|
|
|
|
sequence = LittleLong(*((int *)(data + 0)));
|
|
sequence_ack = LittleLong(*((int *)(data + 4)));
|
|
data += 8;
|
|
length -= 8;
|
|
|
|
if (conn != cls.netcon)
|
|
{
|
|
// server only
|
|
if (length < 2)
|
|
return 0;
|
|
// TODO: use qport to identify that this client really is who they say they are? (and elsewhere in the code to identify the connection without a port match?)
|
|
//qport = LittleShort(*((int *)(data + 8)));
|
|
data += 2;
|
|
length -= 2;
|
|
}
|
|
|
|
conn->packetsReceived++;
|
|
reliable_message = (sequence >> 31) & 1;
|
|
reliable_ack = (sequence_ack >> 31) & 1;
|
|
sequence &= ~(1<<31);
|
|
sequence_ack &= ~(1<<31);
|
|
if (sequence <= conn->qw.incoming_sequence)
|
|
{
|
|
//Con_DPrint("Got a stale datagram\n");
|
|
return 0;
|
|
}
|
|
count = sequence - (conn->qw.incoming_sequence + 1);
|
|
if (count > 0)
|
|
{
|
|
conn->droppedDatagrams += count;
|
|
//Con_DPrintf("Dropped %u datagram(s)\n", count);
|
|
while (count--)
|
|
{
|
|
conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = NETGRAPH_LOSTPACKET;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET;
|
|
}
|
|
}
|
|
conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = originallength + 28;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET;
|
|
if (reliable_ack == conn->qw.reliable_sequence)
|
|
{
|
|
// received, now we will be able to send another reliable message
|
|
conn->sendMessageLength = 0;
|
|
conn->reliableMessagesReceived++;
|
|
}
|
|
conn->qw.incoming_sequence = sequence;
|
|
if (conn == cls.netcon)
|
|
cls.qw_incoming_sequence = conn->qw.incoming_sequence;
|
|
conn->qw.incoming_acknowledged = sequence_ack;
|
|
conn->qw.incoming_reliable_acknowledged = reliable_ack;
|
|
if (reliable_message)
|
|
conn->qw.incoming_reliable_sequence ^= 1;
|
|
conn->lastMessageTime = realtime;
|
|
conn->timeout = realtime + newtimeout;
|
|
conn->unreliableMessagesReceived++;
|
|
if (conn == cls.netcon)
|
|
{
|
|
SZ_Clear(&cl_message);
|
|
SZ_Write(&cl_message, data, length);
|
|
MSG_BeginReading(&cl_message);
|
|
}
|
|
else
|
|
{
|
|
SZ_Clear(&sv_message);
|
|
SZ_Write(&sv_message, data, length);
|
|
MSG_BeginReading(&sv_message);
|
|
}
|
|
return 2;
|
|
}
|
|
else
|
|
{
|
|
unsigned int count;
|
|
unsigned int flags;
|
|
unsigned int sequence;
|
|
size_t qlength;
|
|
const void *sendme;
|
|
size_t sendmelen;
|
|
|
|
originallength = length;
|
|
data = (const unsigned char *) Crypto_DecryptPacket(&conn->crypto, data, length, cryptoreadbuffer, &length, sizeof(cryptoreadbuffer));
|
|
if(!data)
|
|
return 0;
|
|
if(length < 8)
|
|
return 0;
|
|
|
|
qlength = (unsigned int)BuffBigLong(data);
|
|
flags = qlength & ~NETFLAG_LENGTH_MASK;
|
|
qlength &= NETFLAG_LENGTH_MASK;
|
|
// control packets were already handled
|
|
if (!(flags & NETFLAG_CTL) && qlength == length)
|
|
{
|
|
sequence = BuffBigLong(data + 4);
|
|
conn->packetsReceived++;
|
|
data += 8;
|
|
length -= 8;
|
|
if (flags & NETFLAG_UNRELIABLE)
|
|
{
|
|
if (sequence >= conn->nq.unreliableReceiveSequence)
|
|
{
|
|
if (sequence > conn->nq.unreliableReceiveSequence)
|
|
{
|
|
count = sequence - conn->nq.unreliableReceiveSequence;
|
|
conn->droppedDatagrams += count;
|
|
//Con_DPrintf("Dropped %u datagram(s)\n", count);
|
|
while (count--)
|
|
{
|
|
conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = NETGRAPH_LOSTPACKET;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET;
|
|
}
|
|
}
|
|
conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = originallength + 28;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET;
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET;
|
|
conn->nq.unreliableReceiveSequence = sequence + 1;
|
|
conn->lastMessageTime = realtime;
|
|
conn->timeout = realtime + newtimeout;
|
|
conn->unreliableMessagesReceived++;
|
|
if (length > 0)
|
|
{
|
|
if (conn == cls.netcon)
|
|
{
|
|
SZ_Clear(&cl_message);
|
|
SZ_Write(&cl_message, data, length);
|
|
MSG_BeginReading(&cl_message);
|
|
}
|
|
else
|
|
{
|
|
SZ_Clear(&sv_message);
|
|
SZ_Write(&sv_message, data, length);
|
|
MSG_BeginReading(&sv_message);
|
|
}
|
|
return 2;
|
|
}
|
|
}
|
|
//else
|
|
// Con_DPrint("Got a stale datagram\n");
|
|
return 1;
|
|
}
|
|
else if (flags & NETFLAG_ACK)
|
|
{
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes += originallength + 28;
|
|
if (sequence == (conn->nq.sendSequence - 1))
|
|
{
|
|
if (sequence == conn->nq.ackSequence)
|
|
{
|
|
conn->nq.ackSequence++;
|
|
if (conn->nq.ackSequence != conn->nq.sendSequence)
|
|
Con_DPrint("ack sequencing error\n");
|
|
conn->lastMessageTime = realtime;
|
|
conn->timeout = realtime + newtimeout;
|
|
if (conn->sendMessageLength > MAX_PACKETFRAGMENT)
|
|
{
|
|
unsigned int packetLen;
|
|
unsigned int dataLen;
|
|
unsigned int eom;
|
|
|
|
conn->sendMessageLength -= MAX_PACKETFRAGMENT;
|
|
memmove(conn->sendMessage, conn->sendMessage+MAX_PACKETFRAGMENT, conn->sendMessageLength);
|
|
|
|
if (conn->sendMessageLength <= MAX_PACKETFRAGMENT)
|
|
{
|
|
dataLen = conn->sendMessageLength;
|
|
eom = NETFLAG_EOM;
|
|
}
|
|
else
|
|
{
|
|
dataLen = MAX_PACKETFRAGMENT;
|
|
eom = 0;
|
|
}
|
|
|
|
packetLen = NET_HEADERSIZE + dataLen;
|
|
|
|
StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom));
|
|
StoreBigLong(sendbuffer + 4, conn->nq.sendSequence);
|
|
memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen);
|
|
|
|
conn->nq.sendSequence++;
|
|
|
|
sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer));
|
|
if (sendme && NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress) == (int)sendmelen)
|
|
{
|
|
conn->lastSendTime = realtime;
|
|
conn->packetsSent++;
|
|
}
|
|
}
|
|
else
|
|
conn->sendMessageLength = 0;
|
|
}
|
|
//else
|
|
// Con_DPrint("Duplicate ACK received\n");
|
|
}
|
|
//else
|
|
// Con_DPrint("Stale ACK received\n");
|
|
return 1;
|
|
}
|
|
else if (flags & NETFLAG_DATA)
|
|
{
|
|
unsigned char temppacket[8];
|
|
conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes += originallength + 28;
|
|
conn->outgoing_netgraph[conn->outgoing_packetcounter].ackbytes += 8 + 28;
|
|
StoreBigLong(temppacket, 8 | NETFLAG_ACK);
|
|
StoreBigLong(temppacket + 4, sequence);
|
|
sendme = Crypto_EncryptPacket(&conn->crypto, temppacket, 8, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer));
|
|
if(sendme)
|
|
NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress);
|
|
if (sequence == conn->nq.receiveSequence)
|
|
{
|
|
conn->lastMessageTime = realtime;
|
|
conn->timeout = realtime + newtimeout;
|
|
conn->nq.receiveSequence++;
|
|
if( conn->receiveMessageLength + length <= (int)sizeof( conn->receiveMessage ) ) {
|
|
memcpy(conn->receiveMessage + conn->receiveMessageLength, data, length);
|
|
conn->receiveMessageLength += length;
|
|
} else {
|
|
Con_Printf( "Reliable message (seq: %i) too big for message buffer!\n"
|
|
"Dropping the message!\n", sequence );
|
|
conn->receiveMessageLength = 0;
|
|
return 1;
|
|
}
|
|
if (flags & NETFLAG_EOM)
|
|
{
|
|
conn->reliableMessagesReceived++;
|
|
length = conn->receiveMessageLength;
|
|
conn->receiveMessageLength = 0;
|
|
if (length > 0)
|
|
{
|
|
if (conn == cls.netcon)
|
|
{
|
|
SZ_Clear(&cl_message);
|
|
SZ_Write(&cl_message, conn->receiveMessage, length);
|
|
MSG_BeginReading(&cl_message);
|
|
}
|
|
else
|
|
{
|
|
SZ_Clear(&sv_message);
|
|
SZ_Write(&sv_message, conn->receiveMessage, length);
|
|
MSG_BeginReading(&sv_message);
|
|
}
|
|
return 2;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
conn->receivedDuplicateCount++;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void NetConn_ConnectionEstablished(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, protocolversion_t initialprotocol)
|
|
{
|
|
crypto_t *crypto;
|
|
cls.connect_trying = false;
|
|
M_Update_Return_Reason("");
|
|
// the connection request succeeded, stop current connection and set up a new connection
|
|
CL_Disconnect();
|
|
// if we're connecting to a remote server, shut down any local server
|
|
if (LHNETADDRESS_GetAddressType(peeraddress) != LHNETADDRESSTYPE_LOOP && sv.active)
|
|
{
|
|
SV_LockThreadMutex();
|
|
Host_ShutdownServer ();
|
|
SV_UnlockThreadMutex();
|
|
}
|
|
// allocate a net connection to keep track of things
|
|
cls.netcon = NetConn_Open(mysocket, peeraddress);
|
|
crypto = &cls.crypto;
|
|
if(crypto && crypto->authenticated)
|
|
{
|
|
Crypto_ServerFinishInstance(&cls.netcon->crypto, crypto);
|
|
Con_Printf("%s connection to %s has been established: server is %s@%.*s, I am %.*s@%.*s\n",
|
|
crypto->use_aes ? "Encrypted" : "Authenticated",
|
|
cls.netcon->address,
|
|
crypto->server_idfp[0] ? crypto->server_idfp : "-",
|
|
crypto_keyfp_recommended_length, crypto->server_keyfp[0] ? crypto->server_keyfp : "-",
|
|
crypto_keyfp_recommended_length, crypto->client_idfp[0] ? crypto->client_idfp : "-",
|
|
crypto_keyfp_recommended_length, crypto->client_keyfp[0] ? crypto->client_keyfp : "-"
|
|
);
|
|
}
|
|
Con_Printf("Connection accepted to %s\n", cls.netcon->address);
|
|
key_dest = key_game;
|
|
m_state = m_none;
|
|
cls.demonum = -1; // not in the demo loop now
|
|
cls.state = ca_connected;
|
|
cls.signon = 0; // need all the signon messages before playing
|
|
cls.protocol = initialprotocol;
|
|
// reset move sequence numbering on this new connection
|
|
cls.servermovesequence = 0;
|
|
if (cls.protocol == PROTOCOL_QUAKEWORLD)
|
|
Cmd_ForwardStringToServer("new");
|
|
if (cls.protocol == PROTOCOL_QUAKE)
|
|
{
|
|
// write a keepalive (clc_nop) as it seems to greatly improve the
|
|
// chances of connecting to a netquake server
|
|
sizebuf_t msg;
|
|
unsigned char buf[4];
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.data = buf;
|
|
msg.maxsize = sizeof(buf);
|
|
MSG_WriteChar(&msg, clc_nop);
|
|
NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol, 10000, false);
|
|
}
|
|
}
|
|
|
|
int NetConn_IsLocalGame(void)
|
|
{
|
|
if (cls.state == ca_connected && sv.active && cl.maxclients == 1)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static int NetConn_ClientParsePacket_ServerList_ProcessReply(const char *addressstring)
|
|
{
|
|
int n;
|
|
int pingtime;
|
|
serverlist_entry_t *entry = NULL;
|
|
|
|
// search the cache for this server and update it
|
|
for (n = 0;n < serverlist_cachecount;n++) {
|
|
entry = &serverlist_cache[ n ];
|
|
if (!strcmp(addressstring, entry->info.cname))
|
|
break;
|
|
}
|
|
|
|
if (n == serverlist_cachecount)
|
|
{
|
|
// LAN search doesnt require an answer from the master server so we wont
|
|
// know the ping nor will it be initialized already...
|
|
|
|
// find a slot
|
|
if (serverlist_cachecount == SERVERLIST_TOTALSIZE)
|
|
return -1;
|
|
|
|
if (serverlist_maxcachecount <= serverlist_cachecount)
|
|
{
|
|
serverlist_maxcachecount += 64;
|
|
serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount);
|
|
}
|
|
entry = &serverlist_cache[n];
|
|
|
|
memset(entry, 0, sizeof(*entry));
|
|
// store the data the engine cares about (address and ping)
|
|
strlcpy(entry->info.cname, addressstring, sizeof(entry->info.cname));
|
|
entry->info.ping = 100000;
|
|
entry->querytime = realtime;
|
|
// if not in the slist menu we should print the server to console
|
|
if (serverlist_consoleoutput)
|
|
Con_Printf("querying %s\n", addressstring);
|
|
++serverlist_cachecount;
|
|
}
|
|
// if this is the first reply from this server, count it as having replied
|
|
pingtime = (int)((realtime - entry->querytime) * 1000.0 + 0.5);
|
|
pingtime = bound(0, pingtime, 9999);
|
|
if (entry->query == SQS_REFRESHING) {
|
|
entry->info.ping = pingtime;
|
|
entry->query = SQS_QUERIED;
|
|
} else {
|
|
// convert to unsigned to catch the -1
|
|
// I still dont like this but its better than the old 10000 magic ping number - as in easier to type and read :( [11/8/2007 Black]
|
|
entry->info.ping = min((unsigned) entry->info.ping, (unsigned) pingtime);
|
|
serverreplycount++;
|
|
}
|
|
|
|
// other server info is updated by the caller
|
|
return n;
|
|
}
|
|
|
|
static void NetConn_ClientParsePacket_ServerList_UpdateCache(int n)
|
|
{
|
|
serverlist_entry_t *entry = &serverlist_cache[n];
|
|
serverlist_info_t *info = &entry->info;
|
|
// update description strings for engine menu and console output
|
|
dpsnprintf(entry->line1, sizeof(serverlist_cache[n].line1), "^%c%5d^7 ^%c%3u^7/%3u %-65.65s", info->ping >= 300 ? '1' : (info->ping >= 200 ? '3' : '7'), (int)info->ping, ((info->numhumans > 0 && info->numhumans < info->maxplayers) ? (info->numhumans >= 4 ? '7' : '3') : '1'), info->numplayers, info->maxplayers, info->name);
|
|
dpsnprintf(entry->line2, sizeof(serverlist_cache[n].line2), "^4%-21.21s %-19.19s ^%c%-17.17s^4 %-20.20s", info->cname, info->game,
|
|
(
|
|
info->gameversion != gameversion.integer
|
|
&&
|
|
!(
|
|
gameversion_min.integer >= 0 // min/max range set by user/mod?
|
|
&& gameversion_max.integer >= 0
|
|
&& gameversion_min.integer <= info->gameversion // version of server in min/max range?
|
|
&& gameversion_max.integer >= info->gameversion
|
|
)
|
|
) ? '1' : '4',
|
|
info->mod, info->map);
|
|
if (entry->query == SQS_QUERIED)
|
|
{
|
|
if(!serverlist_paused)
|
|
ServerList_ViewList_Remove(entry);
|
|
}
|
|
// if not in the slist menu we should print the server to console (if wanted)
|
|
else if( serverlist_consoleoutput )
|
|
Con_Printf("%s\n%s\n", serverlist_cache[n].line1, serverlist_cache[n].line2);
|
|
// and finally, update the view set
|
|
if(!serverlist_paused)
|
|
ServerList_ViewList_Insert( entry );
|
|
// update the entry's state
|
|
serverlist_cache[n].query = SQS_QUERIED;
|
|
}
|
|
|
|
// returns true, if it's sensible to continue the processing
|
|
static qboolean NetConn_ClientParsePacket_ServerList_PrepareQuery( int protocol, const char *ipstring, qboolean isfavorite ) {
|
|
int n;
|
|
serverlist_entry_t *entry;
|
|
|
|
// ignore the rest of the message if the serverlist is full
|
|
if( serverlist_cachecount == SERVERLIST_TOTALSIZE )
|
|
return false;
|
|
// also ignore it if we have already queried it (other master server response)
|
|
for( n = 0 ; n < serverlist_cachecount ; n++ )
|
|
if( !strcmp( ipstring, serverlist_cache[ n ].info.cname ) )
|
|
break;
|
|
|
|
if( n < serverlist_cachecount ) {
|
|
// the entry has already been queried once or
|
|
return true;
|
|
}
|
|
|
|
if (serverlist_maxcachecount <= n)
|
|
{
|
|
serverlist_maxcachecount += 64;
|
|
serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount);
|
|
}
|
|
|
|
entry = &serverlist_cache[n];
|
|
|
|
memset(entry, 0, sizeof(*entry));
|
|
entry->protocol = protocol;
|
|
// store the data the engine cares about (address and ping)
|
|
strlcpy (entry->info.cname, ipstring, sizeof(entry->info.cname));
|
|
|
|
entry->info.isfavorite = isfavorite;
|
|
|
|
// no, then reset the ping right away
|
|
entry->info.ping = -1;
|
|
// we also want to increase the serverlist_cachecount then
|
|
serverlist_cachecount++;
|
|
serverquerycount++;
|
|
|
|
entry->query = SQS_QUERYING;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void NetConn_ClientParsePacket_ServerList_ParseDPList(lhnetaddress_t *senderaddress, const unsigned char *data, int length, qboolean isextended)
|
|
{
|
|
masterreplycount++;
|
|
if (serverlist_consoleoutput)
|
|
Con_Printf("received DarkPlaces %sserver list...\n", isextended ? "extended " : "");
|
|
while (length >= 7)
|
|
{
|
|
char ipstring [128];
|
|
|
|
// IPv4 address
|
|
if (data[0] == '\\')
|
|
{
|
|
unsigned short port = data[5] * 256 + data[6];
|
|
|
|
if (port != 0 && (data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF || data[4] != 0xFF))
|
|
dpsnprintf (ipstring, sizeof (ipstring), "%u.%u.%u.%u:%hu", data[1], data[2], data[3], data[4], port);
|
|
|
|
// move on to next address in packet
|
|
data += 7;
|
|
length -= 7;
|
|
}
|
|
// IPv6 address
|
|
else if (data[0] == '/' && isextended && length >= 19)
|
|
{
|
|
unsigned short port = data[17] * 256 + data[18];
|
|
|
|
if (port != 0)
|
|
{
|
|
#ifdef WHY_JUST_WHY
|
|
const char *ifname;
|
|
char ifnamebuf[16];
|
|
|
|
/// \TODO: make some basic checks of the IP address (broadcast, ...)
|
|
|
|
ifname = LHNETADDRESS_GetInterfaceName(senderaddress, ifnamebuf, sizeof(ifnamebuf));
|
|
if (ifname != NULL)
|
|
{
|
|
dpsnprintf (ipstring, sizeof (ipstring), "[%x:%x:%x:%x:%x:%x:%x:%x%%%s]:%hu",
|
|
(data[1] << 8) | data[2], (data[3] << 8) | data[4], (data[5] << 8) | data[6], (data[7] << 8) | data[8],
|
|
(data[9] << 8) | data[10], (data[11] << 8) | data[12], (data[13] << 8) | data[14], (data[15] << 8) | data[16],
|
|
ifname, port);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
dpsnprintf (ipstring, sizeof (ipstring), "[%x:%x:%x:%x:%x:%x:%x:%x]:%hu",
|
|
(data[1] << 8) | data[2], (data[3] << 8) | data[4], (data[5] << 8) | data[6], (data[7] << 8) | data[8],
|
|
(data[9] << 8) | data[10], (data[11] << 8) | data[12], (data[13] << 8) | data[14], (data[15] << 8) | data[16],
|
|
port);
|
|
}
|
|
}
|
|
|
|
// move on to next address in packet
|
|
data += 19;
|
|
length -= 19;
|
|
}
|
|
else
|
|
{
|
|
Con_Print("Error while parsing the server list\n");
|
|
break;
|
|
}
|
|
|
|
if (serverlist_consoleoutput && developer_networking.integer)
|
|
Con_Printf("Requesting info from DarkPlaces server %s\n", ipstring);
|
|
|
|
if( !NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_DARKPLACES7, ipstring, false ) ) {
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
// begin or resume serverlist queries
|
|
serverlist_querysleep = false;
|
|
serverlist_querywaittime = realtime + 3;
|
|
}
|
|
|
|
static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *data, int length, lhnetaddress_t *peeraddress)
|
|
{
|
|
qboolean fromserver;
|
|
int ret, c;
|
|
const char *s;
|
|
char *string, addressstring2[128], ipstring[32];
|
|
char stringbuf[16384];
|
|
char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE];
|
|
size_t sendlength;
|
|
char infostringvalue[MAX_INPUTLINE];
|
|
char vabuf[1024];
|
|
|
|
// quakeworld ingame packet
|
|
fromserver = cls.netcon && mysocket == cls.netcon->mysocket && !LHNETADDRESS_Compare(&cls.netcon->peeraddress, peeraddress);
|
|
|
|
// convert the address to a string incase we need it
|
|
LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true);
|
|
|
|
if (length >= 5 && data[0] == 255 && data[1] == 255 && data[2] == 255 && data[3] == 255)
|
|
{
|
|
// received a command string - strip off the packaging and put it
|
|
// into our string buffer with NULL termination
|
|
data += 4;
|
|
length -= 4;
|
|
length = min(length, (int)sizeof(stringbuf) - 1);
|
|
memcpy(stringbuf, data, length);
|
|
stringbuf[length] = 0;
|
|
string = stringbuf;
|
|
|
|
if (developer_networking.integer)
|
|
{
|
|
Con_Printf("NetConn_ClientParsePacket: %s sent us a command:\n", addressstring2);
|
|
Com_HexDumpToConsole(data, length);
|
|
}
|
|
|
|
sendlength = sizeof(senddata) - 4;
|
|
switch(Crypto_ClientParsePacket(string, length, senddata+4, &sendlength, peeraddress))
|
|
{
|
|
case CRYPTO_NOMATCH:
|
|
// nothing to do
|
|
break;
|
|
case CRYPTO_MATCH:
|
|
if(sendlength)
|
|
{
|
|
memcpy(senddata, "\377\377\377\377", 4);
|
|
NetConn_Write(mysocket, senddata, sendlength+4, peeraddress);
|
|
}
|
|
break;
|
|
case CRYPTO_DISCARD:
|
|
if(sendlength)
|
|
{
|
|
memcpy(senddata, "\377\377\377\377", 4);
|
|
NetConn_Write(mysocket, senddata, sendlength+4, peeraddress);
|
|
}
|
|
return true;
|
|
break;
|
|
case CRYPTO_REPLACE:
|
|
string = senddata+4;
|
|
length = sendlength;
|
|
break;
|
|
}
|
|
|
|
if (length >= 10 && !memcmp(string, "challenge ", 10) && cls.rcon_trying)
|
|
{
|
|
int i = 0, j;
|
|
for (j = 0;j < MAX_RCONS;j++)
|
|
{
|
|
// note: this value from i is used outside the loop too...
|
|
i = (cls.rcon_ringpos + j) % MAX_RCONS;
|
|
if(cls.rcon_commands[i][0])
|
|
if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[i]))
|
|
break;
|
|
}
|
|
if (j < MAX_RCONS)
|
|
{
|
|
char buf[1500];
|
|
char argbuf[1500];
|
|
const char *e;
|
|
int n;
|
|
dpsnprintf(argbuf, sizeof(argbuf), "%s %s", string + 10, cls.rcon_commands[i]);
|
|
memcpy(buf, "\377\377\377\377srcon HMAC-MD4 CHALLENGE ", 29);
|
|
|
|
e = strchr(rcon_password.string, ' ');
|
|
n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
|
|
|
|
if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 29), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
|
|
{
|
|
int k;
|
|
buf[45] = ' ';
|
|
strlcpy(buf + 46, argbuf, sizeof(buf) - 46);
|
|
NetConn_Write(mysocket, buf, 46 + strlen(buf + 46), peeraddress);
|
|
cls.rcon_commands[i][0] = 0;
|
|
--cls.rcon_trying;
|
|
|
|
for (k = 0;k < MAX_RCONS;k++)
|
|
if(cls.rcon_commands[k][0])
|
|
if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[k]))
|
|
break;
|
|
if(k < MAX_RCONS)
|
|
{
|
|
int l;
|
|
NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", peeraddress);
|
|
// extend the timeout on other requests as we asked for a challenge
|
|
for (l = 0;l < MAX_RCONS;l++)
|
|
if(cls.rcon_commands[l][0])
|
|
if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[l]))
|
|
cls.rcon_timeout[l] = realtime + rcon_secure_challengetimeout.value;
|
|
}
|
|
|
|
return true; // we used up the challenge, so we can't use this oen for connecting now anyway
|
|
}
|
|
}
|
|
}
|
|
if (length >= 10 && !memcmp(string, "challenge ", 10) && cls.connect_trying)
|
|
{
|
|
// darkplaces or quake3
|
|
char protocolnames[1400];
|
|
Protocol_Names(protocolnames, sizeof(protocolnames));
|
|
Con_DPrintf("\"%s\" received, sending connect request back to %s\n", string, addressstring2);
|
|
M_Update_Return_Reason("Got challenge response");
|
|
// update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command)
|
|
InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2);
|
|
// TODO: add userinfo stuff here instead of using NQ commands?
|
|
NetConn_WriteString(mysocket, portable_va(vabuf, sizeof(vabuf), "\377\377\377\377connect\\protocol\\darkplaces 3\\protocols\\%s%s\\challenge\\%s", protocolnames, cls.connect_userinfo, string + 10), peeraddress);
|
|
return true;
|
|
}
|
|
if (length == 6 && !memcmp(string, "accept", 6) && cls.connect_trying)
|
|
{
|
|
// darkplaces or quake3
|
|
M_Update_Return_Reason("Accepted");
|
|
NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_DARKPLACES3);
|
|
return true;
|
|
}
|
|
if (length > 7 && !memcmp(string, "reject ", 7) && cls.connect_trying)
|
|
{
|
|
char rejectreason[128];
|
|
cls.connect_trying = false;
|
|
string += 7;
|
|
length = min(length - 7, (int)sizeof(rejectreason) - 1);
|
|
memcpy(rejectreason, string, length);
|
|
rejectreason[length] = 0;
|
|
M_Update_Return_Reason(rejectreason);
|
|
return true;
|
|
}
|
|
if (length >= 15 && !memcmp(string, "statusResponse\x0A", 15))
|
|
{
|
|
serverlist_info_t *info;
|
|
char *p;
|
|
int n;
|
|
|
|
string += 15;
|
|
// search the cache for this server and update it
|
|
n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2);
|
|
if (n < 0)
|
|
return true;
|
|
|
|
info = &serverlist_cache[n].info;
|
|
info->game[0] = 0;
|
|
info->mod[0] = 0;
|
|
info->map[0] = 0;
|
|
info->name[0] = 0;
|
|
info->qcstatus[0] = 0;
|
|
info->players[0] = 0;
|
|
info->protocol = -1;
|
|
info->numplayers = 0;
|
|
info->numbots = -1;
|
|
info->maxplayers = 0;
|
|
info->gameversion = 0;
|
|
|
|
p = strchr(string, '\n');
|
|
if(p)
|
|
{
|
|
*p = 0; // cut off the string there
|
|
++p;
|
|
}
|
|
else
|
|
Con_Printf("statusResponse without players block?\n");
|
|
|
|
if ((s = InfoString_GetValue(string, "gamename" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->game, s, sizeof (info->game));
|
|
if ((s = InfoString_GetValue(string, "modname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->mod , s, sizeof (info->mod ));
|
|
if ((s = InfoString_GetValue(string, "mapname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->map , s, sizeof (info->map ));
|
|
if ((s = InfoString_GetValue(string, "hostname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->name, s, sizeof (info->name));
|
|
if ((s = InfoString_GetValue(string, "protocol" , infostringvalue, sizeof(infostringvalue))) != NULL) info->protocol = atoi(s);
|
|
if ((s = InfoString_GetValue(string, "clients" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numplayers = atoi(s);
|
|
if ((s = InfoString_GetValue(string, "bots" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numbots = atoi(s);
|
|
if ((s = InfoString_GetValue(string, "sv_maxclients", infostringvalue, sizeof(infostringvalue))) != NULL) info->maxplayers = atoi(s);
|
|
if ((s = InfoString_GetValue(string, "gameversion" , infostringvalue, sizeof(infostringvalue))) != NULL) info->gameversion = atoi(s);
|
|
if ((s = InfoString_GetValue(string, "qcstatus" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->qcstatus, s, sizeof(info->qcstatus));
|
|
if (p != NULL) strlcpy(info->players, p, sizeof(info->players));
|
|
info->numhumans = info->numplayers - max(0, info->numbots);
|
|
info->freeslots = info->maxplayers - info->numplayers;
|
|
|
|
NetConn_ClientParsePacket_ServerList_UpdateCache(n);
|
|
|
|
return true;
|
|
}
|
|
if (length >= 13 && !memcmp(string, "infoResponse\x0A", 13))
|
|
{
|
|
serverlist_info_t *info;
|
|
int n;
|
|
|
|
string += 13;
|
|
// search the cache for this server and update it
|
|
n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2);
|
|
if (n < 0)
|
|
return true;
|
|
|
|
info = &serverlist_cache[n].info;
|
|
info->game[0] = 0;
|
|
info->mod[0] = 0;
|
|
info->map[0] = 0;
|
|
info->name[0] = 0;
|
|
info->qcstatus[0] = 0;
|
|
info->players[0] = 0;
|
|
info->protocol = -1;
|
|
info->numplayers = 0;
|
|
info->numbots = -1;
|
|
info->maxplayers = 0;
|
|
info->gameversion = 0;
|
|
|
|
if ((s = InfoString_GetValue(string, "gamename" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->game, s, sizeof (info->game));
|
|
if ((s = InfoString_GetValue(string, "modname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->mod , s, sizeof (info->mod ));
|
|
if ((s = InfoString_GetValue(string, "mapname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->map , s, sizeof (info->map ));
|
|
if ((s = InfoString_GetValue(string, "hostname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->name, s, sizeof (info->name));
|
|
if ((s = InfoString_GetValue(string, "protocol" , infostringvalue, sizeof(infostringvalue))) != NULL) info->protocol = atoi(s);
|
|
if ((s = InfoString_GetValue(string, "clients" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numplayers = atoi(s);
|
|
if ((s = InfoString_GetValue(string, "bots" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numbots = atoi(s);
|
|
if ((s = InfoString_GetValue(string, "sv_maxclients", infostringvalue, sizeof(infostringvalue))) != NULL) info->maxplayers = atoi(s);
|
|
if ((s = InfoString_GetValue(string, "gameversion" , infostringvalue, sizeof(infostringvalue))) != NULL) info->gameversion = atoi(s);
|
|
if ((s = InfoString_GetValue(string, "qcstatus" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->qcstatus, s, sizeof(info->qcstatus));
|
|
info->numhumans = info->numplayers - max(0, info->numbots);
|
|
info->freeslots = info->maxplayers - info->numplayers;
|
|
|
|
NetConn_ClientParsePacket_ServerList_UpdateCache(n);
|
|
|
|
return true;
|
|
}
|
|
if (!strncmp(string, "getserversResponse\\", 19) && serverlist_cachecount < SERVERLIST_TOTALSIZE)
|
|
{
|
|
// Extract the IP addresses
|
|
data += 18;
|
|
length -= 18;
|
|
NetConn_ClientParsePacket_ServerList_ParseDPList(peeraddress, data, length, false);
|
|
return true;
|
|
}
|
|
if (!strncmp(string, "getserversExtResponse", 21) && serverlist_cachecount < SERVERLIST_TOTALSIZE)
|
|
{
|
|
// Extract the IP addresses
|
|
data += 21;
|
|
length -= 21;
|
|
NetConn_ClientParsePacket_ServerList_ParseDPList(peeraddress, data, length, true);
|
|
return true;
|
|
}
|
|
if (!memcmp(string, "d\n", 2) && serverlist_cachecount < SERVERLIST_TOTALSIZE)
|
|
{
|
|
// Extract the IP addresses
|
|
data += 2;
|
|
length -= 2;
|
|
masterreplycount++;
|
|
if (serverlist_consoleoutput)
|
|
Con_Printf("received QuakeWorld server list from %s...\n", addressstring2);
|
|
while (length >= 6 && (data[0] != 0xFF || data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF) && data[4] * 256 + data[5] != 0)
|
|
{
|
|
dpsnprintf (ipstring, sizeof (ipstring), "%u.%u.%u.%u:%u", data[0], data[1], data[2], data[3], data[4] * 256 + data[5]);
|
|
if (serverlist_consoleoutput && developer_networking.integer)
|
|
Con_Printf("Requesting info from QuakeWorld server %s\n", ipstring);
|
|
|
|
if( !NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_QUAKEWORLD, ipstring, false ) ) {
|
|
break;
|
|
}
|
|
|
|
// move on to next address in packet
|
|
data += 6;
|
|
length -= 6;
|
|
}
|
|
// begin or resume serverlist queries
|
|
serverlist_querysleep = false;
|
|
serverlist_querywaittime = realtime + 3;
|
|
return true;
|
|
}
|
|
if (!strncmp(string, "extResponse ", 12))
|
|
{
|
|
++cl_net_extresponse_count;
|
|
if(cl_net_extresponse_count > NET_EXTRESPONSE_MAX)
|
|
cl_net_extresponse_count = NET_EXTRESPONSE_MAX;
|
|
cl_net_extresponse_last = (cl_net_extresponse_last + 1) % NET_EXTRESPONSE_MAX;
|
|
portable_snprintf(cl_net_extresponse[cl_net_extresponse_last], sizeof(cl_net_extresponse[cl_net_extresponse_last]), "\"%s\" %s", addressstring2, string + 12);
|
|
return true;
|
|
}
|
|
if (!strncmp(string, "ping", 4))
|
|
{
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Received ping from %s, sending ack\n", addressstring2);
|
|
NetConn_WriteString(mysocket, "\377\377\377\377ack", peeraddress);
|
|
return true;
|
|
}
|
|
if (!strncmp(string, "ack", 3))
|
|
return true;
|
|
// QuakeWorld compatibility
|
|
if (length > 1 && string[0] == 'c' && (string[1] == '-' || (string[1] >= '0' && string[1] <= '9')) && cls.connect_trying)
|
|
{
|
|
// challenge message
|
|
Con_Printf("challenge %s received, sending QuakeWorld connect request back to %s\n", string + 1, addressstring2);
|
|
M_Update_Return_Reason("Got QuakeWorld challenge response");
|
|
cls.qw_qport = qport.integer;
|
|
// update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command)
|
|
InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2);
|
|
NetConn_WriteString(mysocket, portable_va(vabuf, sizeof(vabuf), "\377\377\377\377connect %i %i %i \"%s%s\"\n", 28, cls.qw_qport, atoi(string + 1), cls.userinfo, cls.connect_userinfo), peeraddress);
|
|
return true;
|
|
}
|
|
if (length >= 1 && string[0] == 'j' && cls.connect_trying)
|
|
{
|
|
// accept message
|
|
M_Update_Return_Reason("QuakeWorld Accepted");
|
|
NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_QUAKEWORLD);
|
|
return true;
|
|
}
|
|
if (length > 2 && !memcmp(string, "n\\", 2))
|
|
{
|
|
serverlist_info_t *info;
|
|
int n;
|
|
|
|
// qw server status
|
|
if (serverlist_consoleoutput && developer_networking.integer >= 2)
|
|
Con_Printf("QW server status from server at %s:\n%s\n", addressstring2, string + 1);
|
|
|
|
string += 1;
|
|
// search the cache for this server and update it
|
|
n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2);
|
|
if (n < 0)
|
|
return true;
|
|
|
|
info = &serverlist_cache[n].info;
|
|
strlcpy(info->game, "QuakeWorld", sizeof(info->game));
|
|
if ((s = InfoString_GetValue(string, "*gamedir" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->mod , s, sizeof (info->mod ));else info->mod[0] = 0;
|
|
if ((s = InfoString_GetValue(string, "map" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->map , s, sizeof (info->map ));else info->map[0] = 0;
|
|
if ((s = InfoString_GetValue(string, "hostname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->name, s, sizeof (info->name));else info->name[0] = 0;
|
|
info->protocol = 0;
|
|
info->numplayers = 0; // updated below
|
|
info->numhumans = 0; // updated below
|
|
if ((s = InfoString_GetValue(string, "maxclients" , infostringvalue, sizeof(infostringvalue))) != NULL) info->maxplayers = atoi(s);else info->maxplayers = 0;
|
|
if ((s = InfoString_GetValue(string, "gameversion" , infostringvalue, sizeof(infostringvalue))) != NULL) info->gameversion = atoi(s);else info->gameversion = 0;
|
|
|
|
// count active players on server
|
|
// (we could gather more info, but we're just after the number)
|
|
s = strchr(string, '\n');
|
|
if (s)
|
|
{
|
|
s++;
|
|
while (s < string + length)
|
|
{
|
|
for (;s < string + length && *s != '\n';s++)
|
|
;
|
|
if (s >= string + length)
|
|
break;
|
|
info->numplayers++;
|
|
info->numhumans++;
|
|
s++;
|
|
}
|
|
}
|
|
|
|
NetConn_ClientParsePacket_ServerList_UpdateCache(n);
|
|
|
|
return true;
|
|
}
|
|
if (string[0] == 'n')
|
|
{
|
|
// qw print command
|
|
Con_Printf("QW print command from server at %s:\n%s\n", addressstring2, string + 1);
|
|
}
|
|
// we may not have liked the packet, but it was a command packet, so
|
|
// we're done processing this packet now
|
|
return true;
|
|
}
|
|
// quakeworld ingame packet
|
|
if (fromserver && cls.protocol == PROTOCOL_QUAKEWORLD && length >= 8 && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol, net_messagetimeout.value)) == 2)
|
|
{
|
|
ret = 0;
|
|
CL_ParseServerMessage();
|
|
return ret;
|
|
}
|
|
// netquake control packets, supported for compatibility only
|
|
if (length >= 5 && BuffBigLong(data) == ((int)NETFLAG_CTL | length) && !ENCRYPTION_REQUIRED)
|
|
{
|
|
int n;
|
|
serverlist_info_t *info;
|
|
|
|
data += 4;
|
|
length -= 4;
|
|
SZ_Clear(&cl_message);
|
|
SZ_Write(&cl_message, data, length);
|
|
MSG_BeginReading(&cl_message);
|
|
c = MSG_ReadByte(&cl_message);
|
|
switch (c)
|
|
{
|
|
case CCREP_ACCEPT:
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: received CCREP_ACCEPT from %s.\n", addressstring2);
|
|
if (cls.connect_trying)
|
|
{
|
|
lhnetaddress_t clientportaddress;
|
|
clientportaddress = *peeraddress;
|
|
LHNETADDRESS_SetPort(&clientportaddress, MSG_ReadLong(&cl_message));
|
|
// extra ProQuake stuff
|
|
if (length >= 6)
|
|
cls.proquake_servermod = MSG_ReadByte(&cl_message); // MOD_PROQUAKE
|
|
else
|
|
cls.proquake_servermod = 0;
|
|
if (length >= 7)
|
|
cls.proquake_serverversion = MSG_ReadByte(&cl_message); // version * 10
|
|
else
|
|
cls.proquake_serverversion = 0;
|
|
if (length >= 8)
|
|
cls.proquake_serverflags = MSG_ReadByte(&cl_message); // flags (mainly PQF_CHEATFREE)
|
|
else
|
|
cls.proquake_serverflags = 0;
|
|
if (cls.proquake_servermod == 1)
|
|
Con_Printf("Connected to ProQuake %.1f server, enabling precise aim\n", cls.proquake_serverversion / 10.0f);
|
|
// update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command)
|
|
InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2);
|
|
M_Update_Return_Reason("Accepted");
|
|
NetConn_ConnectionEstablished(mysocket, &clientportaddress, PROTOCOL_QUAKE);
|
|
}
|
|
break;
|
|
case CCREP_REJECT:
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: received CCREP_REJECT from %s.\n", addressstring2);
|
|
cls.connect_trying = false;
|
|
M_Update_Return_Reason((char *)MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)));
|
|
break;
|
|
case CCREP_SERVER_INFO:
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: received CCREP_SERVER_INFO from %s.\n", addressstring2);
|
|
// LordHavoc: because the quake server may report weird addresses
|
|
// we just ignore it and keep the real address
|
|
MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring));
|
|
// search the cache for this server and update it
|
|
n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2);
|
|
if (n < 0)
|
|
break;
|
|
|
|
info = &serverlist_cache[n].info;
|
|
strlcpy(info->game, "Quake", sizeof(info->game));
|
|
strlcpy(info->mod , "", sizeof(info->mod)); // mod name is not specified
|
|
strlcpy(info->name, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(info->name));
|
|
strlcpy(info->map , MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(info->map));
|
|
info->numplayers = MSG_ReadByte(&cl_message);
|
|
info->maxplayers = MSG_ReadByte(&cl_message);
|
|
info->protocol = MSG_ReadByte(&cl_message);
|
|
|
|
NetConn_ClientParsePacket_ServerList_UpdateCache(n);
|
|
|
|
break;
|
|
case CCREP_RCON: // RocketGuy: ProQuake rcon support
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: received CCREP_RCON from %s.\n", addressstring2);
|
|
|
|
Con_Printf("%s\n", MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)));
|
|
break;
|
|
case CCREP_PLAYER_INFO:
|
|
// we got a CCREP_PLAYER_INFO??
|
|
//if (developer_extra.integer)
|
|
Con_Printf("Datagram_ParseConnectionless: received CCREP_PLAYER_INFO from %s.\n", addressstring2);
|
|
break;
|
|
case CCREP_RULE_INFO:
|
|
// we got a CCREP_RULE_INFO??
|
|
//if (developer_extra.integer)
|
|
Con_Printf("Datagram_ParseConnectionless: received CCREP_RULE_INFO from %s.\n", addressstring2);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
SZ_Clear(&cl_message);
|
|
// we may not have liked the packet, but it was a valid control
|
|
// packet, so we're done processing this packet now
|
|
return true;
|
|
}
|
|
ret = 0;
|
|
if (fromserver && length >= (int)NET_HEADERSIZE && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol, net_messagetimeout.value)) == 2)
|
|
CL_ParseServerMessage();
|
|
return ret;
|
|
}
|
|
|
|
void NetConn_QueryQueueFrame(void)
|
|
{
|
|
int index;
|
|
int queries;
|
|
int maxqueries;
|
|
double timeouttime;
|
|
static double querycounter = 0;
|
|
|
|
if(!net_slist_pause.integer && serverlist_paused)
|
|
ServerList_RebuildViewList();
|
|
serverlist_paused = net_slist_pause.integer != 0;
|
|
|
|
if (serverlist_querysleep)
|
|
return;
|
|
|
|
// apply a cool down time after master server replies,
|
|
// to avoid messing up the ping times on the servers
|
|
if (serverlist_querywaittime > realtime)
|
|
return;
|
|
|
|
// each time querycounter reaches 1.0 issue a query
|
|
querycounter += cl.realframetime * net_slist_queriespersecond.value;
|
|
maxqueries = (int)querycounter;
|
|
maxqueries = bound(0, maxqueries, net_slist_queriesperframe.integer);
|
|
querycounter -= maxqueries;
|
|
|
|
if( maxqueries == 0 ) {
|
|
return;
|
|
}
|
|
|
|
// scan serverlist and issue queries as needed
|
|
serverlist_querysleep = true;
|
|
|
|
timeouttime = realtime - net_slist_timeout.value;
|
|
for( index = 0, queries = 0 ; index < serverlist_cachecount && queries < maxqueries ; index++ )
|
|
{
|
|
serverlist_entry_t *entry = &serverlist_cache[ index ];
|
|
if( entry->query != SQS_QUERYING && entry->query != SQS_REFRESHING )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
serverlist_querysleep = false;
|
|
if( entry->querycounter != 0 && entry->querytime > timeouttime )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( entry->querycounter != (unsigned) net_slist_maxtries.integer )
|
|
{
|
|
lhnetaddress_t address;
|
|
int socket;
|
|
|
|
LHNETADDRESS_FromString(&address, entry->info.cname, 0);
|
|
if (entry->protocol == PROTOCOL_QUAKEWORLD)
|
|
{
|
|
for (socket = 0; socket < cl_numsockets ; socket++)
|
|
NetConn_WriteString(cl_sockets[socket], "\377\377\377\377status\n", &address);
|
|
}
|
|
else
|
|
{
|
|
for (socket = 0; socket < cl_numsockets ; socket++)
|
|
NetConn_WriteString(cl_sockets[socket], "\377\377\377\377getstatus", &address);
|
|
}
|
|
|
|
// update the entry fields
|
|
entry->querytime = realtime;
|
|
entry->querycounter++;
|
|
|
|
// if not in the slist menu we should print the server to console
|
|
if (serverlist_consoleoutput)
|
|
Con_Printf("querying %25s (%i. try)\n", entry->info.cname, entry->querycounter);
|
|
|
|
queries++;
|
|
}
|
|
else
|
|
{
|
|
// have we tried to refresh this server?
|
|
if( entry->query == SQS_REFRESHING ) {
|
|
// yes, so update the reply count (since its not responding anymore)
|
|
serverreplycount--;
|
|
if(!serverlist_paused)
|
|
ServerList_ViewList_Remove(entry);
|
|
}
|
|
entry->query = SQS_TIMEDOUT;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NetConn_ClientFrame(void)
|
|
{
|
|
int i, length;
|
|
lhnetaddress_t peeraddress;
|
|
unsigned char readbuffer[NET_HEADERSIZE+NET_MAXMESSAGE];
|
|
NetConn_UpdateSockets();
|
|
if (cls.connect_trying && cls.connect_nextsendtime < realtime)
|
|
{
|
|
if (cls.connect_remainingtries == 0)
|
|
M_Update_Return_Reason("Connect: Waiting 10 seconds for reply");
|
|
cls.connect_nextsendtime = realtime + 1;
|
|
cls.connect_remainingtries--;
|
|
if (cls.connect_remainingtries <= -10)
|
|
{
|
|
cls.connect_trying = false;
|
|
M_Update_Return_Reason("Connect: Failed");
|
|
return;
|
|
}
|
|
// try challenge first (newer DP server or QW)
|
|
NetConn_WriteString(cls.connect_mysocket, "\377\377\377\377getchallenge", &cls.connect_address);
|
|
// then try netquake as a fallback (old server, or netquake)
|
|
SZ_Clear(&cl_message);
|
|
// save space for the header, filled in later
|
|
MSG_WriteLong(&cl_message, 0);
|
|
MSG_WriteByte(&cl_message, CCREQ_CONNECT);
|
|
MSG_WriteString(&cl_message, "QUAKE");
|
|
MSG_WriteByte(&cl_message, NET_PROTOCOL_VERSION);
|
|
// extended proquake stuff
|
|
MSG_WriteByte(&cl_message, 1); // mod = MOD_PROQUAKE
|
|
// this version matches ProQuake 3.40, the first version to support
|
|
// the NAT fix, and it only supports the NAT fix for ProQuake 3.40 or
|
|
// higher clients, so we pretend we are that version...
|
|
MSG_WriteByte(&cl_message, 34); // version * 10
|
|
MSG_WriteByte(&cl_message, 0); // flags
|
|
MSG_WriteLong(&cl_message, 0); // password
|
|
// write the packetsize now...
|
|
StoreBigLong(cl_message.data, NETFLAG_CTL | (cl_message.cursize & NETFLAG_LENGTH_MASK));
|
|
NetConn_Write(cls.connect_mysocket, cl_message.data, cl_message.cursize, &cls.connect_address);
|
|
SZ_Clear(&cl_message);
|
|
}
|
|
for (i = 0;i < cl_numsockets;i++)
|
|
{
|
|
while (cl_sockets[i] && (length = NetConn_Read(cl_sockets[i], readbuffer, sizeof(readbuffer), &peeraddress)) > 0)
|
|
{
|
|
// R_TimeReport("clientreadnetwork");
|
|
NetConn_ClientParsePacket(cl_sockets[i], readbuffer, length, &peeraddress);
|
|
// R_TimeReport("clientparsepacket");
|
|
}
|
|
}
|
|
NetConn_QueryQueueFrame();
|
|
if (cls.netcon && realtime > cls.netcon->timeout && !sv.active)
|
|
{
|
|
Con_Print("Connection timed out\n");
|
|
CL_Disconnect();
|
|
SV_LockThreadMutex();
|
|
Host_ShutdownServer ();
|
|
SV_UnlockThreadMutex();
|
|
}
|
|
}
|
|
|
|
static void NetConn_BuildChallengeString(char *buffer, int bufferlength)
|
|
{
|
|
int i;
|
|
char c;
|
|
for (i = 0;i < bufferlength - 1;i++)
|
|
{
|
|
do
|
|
{
|
|
c = rand () % (127 - 33) + 33;
|
|
} while (c == '\\' || c == ';' || c == '"' || c == '%' || c == '/');
|
|
buffer[i] = c;
|
|
}
|
|
buffer[i] = 0;
|
|
}
|
|
|
|
/// (div0) build the full response only if possible; better a getinfo response than no response at all if getstatus won't fit
|
|
static qboolean NetConn_BuildStatusResponse(const char* challenge, char* out_msg, size_t out_size, qboolean fullstatus)
|
|
{
|
|
prvm_prog_t *prog = SVVM_prog;
|
|
char qcstatus[256];
|
|
unsigned int nb_clients = 0, nb_bots = 0, i;
|
|
int length;
|
|
char teambuf[3];
|
|
const char *crypto_idstring;
|
|
const char *str;
|
|
|
|
// How many clients are there?
|
|
for (i = 0;i < (unsigned int)svs.maxclients;i++)
|
|
{
|
|
if (svs.clients[i].active)
|
|
{
|
|
nb_clients++;
|
|
if (!svs.clients[i].netconnection)
|
|
nb_bots++;
|
|
}
|
|
}
|
|
|
|
*qcstatus = 0;
|
|
str = PRVM_GetString(prog, PRVM_serverglobalstring(worldstatus));
|
|
if(str && *str)
|
|
{
|
|
char *p;
|
|
const char *q;
|
|
p = qcstatus;
|
|
for(q = str; *q && (size_t)(p - qcstatus) < (sizeof(qcstatus) - 1); ++q)
|
|
if(*q != '\\' && *q != '\n')
|
|
*p++ = *q;
|
|
*p = 0;
|
|
}
|
|
|
|
/// \TODO: we should add more information for the full status string
|
|
crypto_idstring = Crypto_GetInfoResponseDataString();
|
|
length = portable_snprintf(out_msg, out_size,
|
|
"\377\377\377\377%s\x0A"
|
|
"\\gamename\\%s\\modname\\%s\\gameversion\\%d\\sv_maxclients\\%d"
|
|
"\\clients\\%d\\bots\\%d\\mapname\\%s\\hostname\\%s\\protocol\\%d"
|
|
"%s%s"
|
|
"%s%s"
|
|
"%s%s"
|
|
"%s",
|
|
fullstatus ? "statusResponse" : "infoResponse",
|
|
gamename, com_modname, gameversion.integer, svs.maxclients,
|
|
nb_clients, nb_bots, sv.worldbasename, hostname.string, NET_PROTOCOL_VERSION,
|
|
*qcstatus ? "\\qcstatus\\" : "", qcstatus,
|
|
challenge ? "\\challenge\\" : "", challenge ? challenge : "",
|
|
crypto_idstring ? "\\d0_blind_id\\" : "", crypto_idstring ? crypto_idstring : "",
|
|
fullstatus ? "\n" : "");
|
|
|
|
// Make sure it fits in the buffer
|
|
if (length < 0)
|
|
goto bad;
|
|
|
|
if (fullstatus)
|
|
{
|
|
char *ptr;
|
|
int left;
|
|
int savelength;
|
|
|
|
savelength = length;
|
|
|
|
ptr = out_msg + length;
|
|
left = (int)out_size - length;
|
|
|
|
for (i = 0;i < (unsigned int)svs.maxclients;i++)
|
|
{
|
|
client_t *cl = &svs.clients[i];
|
|
if (cl->active)
|
|
{
|
|
int nameind, cleanind, pingvalue;
|
|
char curchar;
|
|
char cleanname [sizeof(cl->name)];
|
|
const char *str;
|
|
prvm_edict_t *ed;
|
|
|
|
// Remove all characters '"' and '\' in the player name
|
|
nameind = 0;
|
|
cleanind = 0;
|
|
do
|
|
{
|
|
curchar = cl->name[nameind++];
|
|
if (curchar != '"' && curchar != '\\')
|
|
{
|
|
cleanname[cleanind++] = curchar;
|
|
if (cleanind == sizeof(cleanname) - 1)
|
|
break;
|
|
}
|
|
} while (curchar != '\0');
|
|
cleanname[cleanind] = 0; // cleanind is always a valid index even at this point
|
|
|
|
pingvalue = (int)(cl->ping * 1000.0f);
|
|
if(cl->netconnection)
|
|
pingvalue = bound(1, pingvalue, 9999);
|
|
else
|
|
pingvalue = 0;
|
|
|
|
*qcstatus = 0;
|
|
ed = PRVM_EDICT_NUM(i + 1);
|
|
str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
|
|
if(str && *str)
|
|
{
|
|
char *p;
|
|
const char *q;
|
|
p = qcstatus;
|
|
for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
|
|
if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
|
|
*p++ = *q;
|
|
*p = 0;
|
|
}
|
|
|
|
if ((gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) && (teamplay.integer > 0))
|
|
{
|
|
if(cl->frags == -666) // spectator
|
|
strlcpy(teambuf, " 0", sizeof(teambuf));
|
|
else if(cl->colors == 0x44) // red team
|
|
strlcpy(teambuf, " 1", sizeof(teambuf));
|
|
else if(cl->colors == 0xDD) // blue team
|
|
strlcpy(teambuf, " 2", sizeof(teambuf));
|
|
else if(cl->colors == 0xCC) // yellow team
|
|
strlcpy(teambuf, " 3", sizeof(teambuf));
|
|
else if(cl->colors == 0x99) // pink team
|
|
strlcpy(teambuf, " 4", sizeof(teambuf));
|
|
else
|
|
strlcpy(teambuf, " 0", sizeof(teambuf));
|
|
}
|
|
else
|
|
*teambuf = 0;
|
|
|
|
// note: team number is inserted according to SoF2 protocol
|
|
if(*qcstatus)
|
|
length = dpsnprintf(ptr, left, "%s %d%s \"%s\"\n",
|
|
qcstatus,
|
|
pingvalue,
|
|
teambuf,
|
|
cleanname);
|
|
else
|
|
length = dpsnprintf(ptr, left, "%d %d%s \"%s\"\n",
|
|
cl->frags,
|
|
pingvalue,
|
|
teambuf,
|
|
cleanname);
|
|
|
|
if(length < 0)
|
|
{
|
|
// out of space?
|
|
// turn it into an infoResponse!
|
|
out_msg[savelength] = 0;
|
|
memcpy(out_msg + 4, "infoResponse\x0A", 13);
|
|
memmove(out_msg + 17, out_msg + 19, savelength - 19);
|
|
break;
|
|
}
|
|
left -= length;
|
|
ptr += length;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
bad:
|
|
return false;
|
|
}
|
|
|
|
static qboolean NetConn_PreventFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength, double floodtime, qboolean renew)
|
|
{
|
|
size_t floodslotnum, bestfloodslotnum;
|
|
double bestfloodtime;
|
|
lhnetaddress_t noportpeeraddress;
|
|
// see if this is a connect flood
|
|
noportpeeraddress = *peeraddress;
|
|
LHNETADDRESS_SetPort(&noportpeeraddress, 0);
|
|
bestfloodslotnum = 0;
|
|
bestfloodtime = floodlist[bestfloodslotnum].lasttime;
|
|
for (floodslotnum = 0;floodslotnum < floodlength;floodslotnum++)
|
|
{
|
|
if (bestfloodtime >= floodlist[floodslotnum].lasttime)
|
|
{
|
|
bestfloodtime = floodlist[floodslotnum].lasttime;
|
|
bestfloodslotnum = floodslotnum;
|
|
}
|
|
if (floodlist[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &floodlist[floodslotnum].address) == 0)
|
|
{
|
|
// this address matches an ongoing flood address
|
|
if (realtime < floodlist[floodslotnum].lasttime + floodtime)
|
|
{
|
|
if(renew)
|
|
{
|
|
// renew the ban on this address so it does not expire
|
|
// until the flood has subsided
|
|
floodlist[floodslotnum].lasttime = realtime;
|
|
}
|
|
//Con_Printf("Flood detected!\n");
|
|
return true;
|
|
}
|
|
// the flood appears to have subsided, so allow this
|
|
bestfloodslotnum = floodslotnum; // reuse the same slot
|
|
break;
|
|
}
|
|
}
|
|
// begin a new timeout on this address
|
|
floodlist[bestfloodslotnum].address = noportpeeraddress;
|
|
floodlist[bestfloodslotnum].lasttime = realtime;
|
|
//Con_Printf("Flood detection initiated!\n");
|
|
return false;
|
|
}
|
|
|
|
void NetConn_ClearFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength)
|
|
{
|
|
size_t floodslotnum;
|
|
lhnetaddress_t noportpeeraddress;
|
|
// see if this is a connect flood
|
|
noportpeeraddress = *peeraddress;
|
|
LHNETADDRESS_SetPort(&noportpeeraddress, 0);
|
|
for (floodslotnum = 0;floodslotnum < floodlength;floodslotnum++)
|
|
{
|
|
if (floodlist[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &floodlist[floodslotnum].address) == 0)
|
|
{
|
|
// this address matches an ongoing flood address
|
|
// remove the ban
|
|
floodlist[floodslotnum].address.addresstype = LHNETADDRESSTYPE_NONE;
|
|
floodlist[floodslotnum].lasttime = 0;
|
|
//Con_Printf("Flood cleared!\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef qboolean (*rcon_matchfunc_t) (lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen);
|
|
|
|
static qboolean hmac_mdfour_time_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen)
|
|
{
|
|
char mdfourbuf[16];
|
|
long t1, t2;
|
|
|
|
t1 = (long) time(NULL);
|
|
t2 = strtol(s, NULL, 0);
|
|
if(abs(t1 - t2) > rcon_secure_maxdiff.integer)
|
|
return false;
|
|
|
|
if(!HMAC_MDFOUR_16BYTES((unsigned char *) mdfourbuf, (unsigned char *) s, slen, (unsigned char *) password, strlen(password)))
|
|
return false;
|
|
|
|
return !memcmp(mdfourbuf, hash, 16);
|
|
}
|
|
|
|
static qboolean hmac_mdfour_challenge_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen)
|
|
{
|
|
char mdfourbuf[16];
|
|
int i;
|
|
|
|
if(slen < (int)(sizeof(challenge[0].string)) - 1)
|
|
return false;
|
|
|
|
// validate the challenge
|
|
for (i = 0;i < MAX_CHALLENGES;i++)
|
|
if(challenge[i].time > 0)
|
|
if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strncmp(challenge[i].string, s, sizeof(challenge[0].string) - 1))
|
|
break;
|
|
// if the challenge is not recognized, drop the packet
|
|
if (i == MAX_CHALLENGES)
|
|
return false;
|
|
|
|
if(!HMAC_MDFOUR_16BYTES((unsigned char *) mdfourbuf, (unsigned char *) s, slen, (unsigned char *) password, strlen(password)))
|
|
return false;
|
|
|
|
if(memcmp(mdfourbuf, hash, 16))
|
|
return false;
|
|
|
|
// unmark challenge to prevent replay attacks
|
|
challenge[i].time = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
static qboolean plaintext_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen)
|
|
{
|
|
return !strcmp(password, hash);
|
|
}
|
|
|
|
/// returns a string describing the user level, or NULL for auth failure
|
|
static const char *RCon_Authenticate(lhnetaddress_t *peeraddress, const char *password, const char *s, const char *endpos, rcon_matchfunc_t comparator, const char *cs, int cslen)
|
|
{
|
|
const char *text, *userpass_start, *userpass_end, *userpass_startpass;
|
|
static char buf[MAX_INPUTLINE];
|
|
qboolean hasquotes;
|
|
qboolean restricted = false;
|
|
qboolean have_usernames = false;
|
|
char vabuf[1024];
|
|
|
|
userpass_start = rcon_password.string;
|
|
while((userpass_end = strchr(userpass_start, ' ')))
|
|
{
|
|
have_usernames = true;
|
|
strlcpy(buf, userpass_start, ((size_t)(userpass_end-userpass_start) >= sizeof(buf)) ? (int)(sizeof(buf)) : (int)(userpass_end-userpass_start+1));
|
|
if(buf[0])
|
|
if(comparator(peeraddress, buf, password, cs, cslen))
|
|
goto allow;
|
|
userpass_start = userpass_end + 1;
|
|
}
|
|
if(userpass_start[0])
|
|
{
|
|
userpass_end = userpass_start + strlen(userpass_start);
|
|
if(comparator(peeraddress, userpass_start, password, cs, cslen))
|
|
goto allow;
|
|
}
|
|
|
|
restricted = true;
|
|
have_usernames = false;
|
|
userpass_start = rcon_restricted_password.string;
|
|
while((userpass_end = strchr(userpass_start, ' ')))
|
|
{
|
|
have_usernames = true;
|
|
strlcpy(buf, userpass_start, ((size_t)(userpass_end-userpass_start) >= sizeof(buf)) ? (int)(sizeof(buf)) : (int)(userpass_end-userpass_start+1));
|
|
if(buf[0])
|
|
if(comparator(peeraddress, buf, password, cs, cslen))
|
|
goto check;
|
|
userpass_start = userpass_end + 1;
|
|
}
|
|
if(userpass_start[0])
|
|
{
|
|
userpass_end = userpass_start + strlen(userpass_start);
|
|
if(comparator(peeraddress, userpass_start, password, cs, cslen))
|
|
goto check;
|
|
}
|
|
|
|
return NULL; // DENIED
|
|
|
|
check:
|
|
for(text = s; text != endpos; ++text)
|
|
if((signed char) *text > 0 && ((signed char) *text < (signed char) ' ' || *text == ';'))
|
|
return NULL; // block possible exploits against the parser/alias expansion
|
|
|
|
while(s != endpos)
|
|
{
|
|
size_t l = strlen(s);
|
|
if(l)
|
|
{
|
|
hasquotes = (strchr(s, '"') != NULL);
|
|
// sorry, we can't allow these substrings in wildcard expressions,
|
|
// as they can mess with the argument counts
|
|
text = rcon_restricted_commands.string;
|
|
while(COM_ParseToken_Console(&text))
|
|
{
|
|
// com_token now contains a pattern to check for...
|
|
if(strchr(com_token, '*') || strchr(com_token, '?')) // wildcard expression, * can only match a SINGLE argument
|
|
{
|
|
if(!hasquotes)
|
|
if(matchpattern_with_separator(s, com_token, true, " ", true)) // note how we excluded tab, newline etc. above
|
|
goto match;
|
|
}
|
|
else if(strchr(com_token, ' ')) // multi-arg expression? must match in whole
|
|
{
|
|
if(!strcmp(com_token, s))
|
|
goto match;
|
|
}
|
|
else // single-arg expression? must match the beginning of the command
|
|
{
|
|
if(!strcmp(com_token, s))
|
|
goto match;
|
|
if(!memcmp(va(vabuf, sizeof(vabuf), "%s ", com_token), s, strlen(com_token) + 1))
|
|
goto match;
|
|
}
|
|
}
|
|
// if we got here, nothing matched!
|
|
return NULL;
|
|
}
|
|
match:
|
|
s += l + 1;
|
|
}
|
|
|
|
allow:
|
|
userpass_startpass = strchr(userpass_start, ':');
|
|
if(have_usernames && userpass_startpass && userpass_startpass < userpass_end)
|
|
return va(vabuf, sizeof(vabuf), "%srcon (username %.*s)", restricted ? "restricted " : "", (int)(userpass_startpass-userpass_start), userpass_start);
|
|
|
|
return va(vabuf, sizeof(vabuf), "%srcon", restricted ? "restricted " : "");
|
|
}
|
|
|
|
static void RCon_Execute(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, const char *addressstring2, const char *userlevel, const char *s, const char *endpos, qboolean proquakeprotocol)
|
|
{
|
|
if(userlevel)
|
|
{
|
|
// looks like a legitimate rcon command with the correct password
|
|
const char *s_ptr = s;
|
|
Con_Printf("server received %s command from %s: ", userlevel, host_client ? host_client->name : addressstring2);
|
|
while(s_ptr != endpos)
|
|
{
|
|
size_t l = strlen(s_ptr);
|
|
if(l)
|
|
Con_Printf(" %s;", s_ptr);
|
|
s_ptr += l + 1;
|
|
}
|
|
Con_Printf("\n");
|
|
|
|
if (!host_client || !host_client->netconnection || LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP)
|
|
Con_Rcon_Redirect_Init(mysocket, peeraddress, proquakeprotocol);
|
|
while(s != endpos)
|
|
{
|
|
size_t l = strlen(s);
|
|
if(l)
|
|
{
|
|
client_t *host_client_save = host_client;
|
|
Cmd_ExecuteString(s, src_command, true);
|
|
host_client = host_client_save;
|
|
// in case it is a command that changes host_client (like restart)
|
|
}
|
|
s += l + 1;
|
|
}
|
|
Con_Rcon_Redirect_End();
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("server denied rcon access to %s\n", host_client ? host_client->name : addressstring2);
|
|
}
|
|
}
|
|
|
|
static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *data, int length, lhnetaddress_t *peeraddress)
|
|
{
|
|
int i, ret, clientnum, best;
|
|
double besttime;
|
|
client_t *client;
|
|
char *s, *string, response[1400], addressstring2[128];
|
|
static char stringbuf[16384]; // server only
|
|
qboolean islocal = (LHNETADDRESS_GetAddressType(peeraddress) == LHNETADDRESSTYPE_LOOP);
|
|
char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE];
|
|
size_t sendlength, response_len;
|
|
char infostringvalue[MAX_INPUTLINE];
|
|
char vabuf[1024];
|
|
|
|
if (!sv.active)
|
|
return false;
|
|
|
|
// convert the address to a string incase we need it
|
|
LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true);
|
|
|
|
// see if we can identify the sender as a local player
|
|
// (this is necessary for rcon to send a reliable reply if the client is
|
|
// actually on the server, not sending remotely)
|
|
for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
|
|
if (host_client->netconnection && host_client->netconnection->mysocket == mysocket && !LHNETADDRESS_Compare(&host_client->netconnection->peeraddress, peeraddress))
|
|
break;
|
|
if (i == svs.maxclients)
|
|
host_client = NULL;
|
|
|
|
if (length >= 5 && data[0] == 255 && data[1] == 255 && data[2] == 255 && data[3] == 255)
|
|
{
|
|
// received a command string - strip off the packaging and put it
|
|
// into our string buffer with NULL termination
|
|
data += 4;
|
|
length -= 4;
|
|
length = min(length, (int)sizeof(stringbuf) - 1);
|
|
memcpy(stringbuf, data, length);
|
|
stringbuf[length] = 0;
|
|
string = stringbuf;
|
|
|
|
if (developer_extra.integer)
|
|
{
|
|
Con_Printf("NetConn_ServerParsePacket: %s sent us a command:\n", addressstring2);
|
|
Com_HexDumpToConsole(data, length);
|
|
}
|
|
|
|
sendlength = sizeof(senddata) - 4;
|
|
switch(Crypto_ServerParsePacket(string, length, senddata+4, &sendlength, peeraddress))
|
|
{
|
|
case CRYPTO_NOMATCH:
|
|
// nothing to do
|
|
break;
|
|
case CRYPTO_MATCH:
|
|
if(sendlength)
|
|
{
|
|
memcpy(senddata, "\377\377\377\377", 4);
|
|
NetConn_Write(mysocket, senddata, sendlength+4, peeraddress);
|
|
}
|
|
break;
|
|
case CRYPTO_DISCARD:
|
|
if(sendlength)
|
|
{
|
|
memcpy(senddata, "\377\377\377\377", 4);
|
|
NetConn_Write(mysocket, senddata, sendlength+4, peeraddress);
|
|
}
|
|
return true;
|
|
break;
|
|
case CRYPTO_REPLACE:
|
|
string = senddata+4;
|
|
length = sendlength;
|
|
break;
|
|
}
|
|
|
|
if (length >= 12 && !memcmp(string, "getchallenge", 12) && (islocal || sv_public.integer > -3))
|
|
{
|
|
for (i = 0, best = 0, besttime = realtime;i < MAX_CHALLENGES;i++)
|
|
{
|
|
if(challenge[i].time > 0)
|
|
if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address))
|
|
break;
|
|
if (besttime > challenge[i].time)
|
|
besttime = challenge[best = i].time;
|
|
}
|
|
// if we did not find an exact match, choose the oldest and
|
|
// update address and string
|
|
if (i == MAX_CHALLENGES)
|
|
{
|
|
i = best;
|
|
challenge[i].address = *peeraddress;
|
|
NetConn_BuildChallengeString(challenge[i].string, sizeof(challenge[i].string));
|
|
}
|
|
else
|
|
{
|
|
// flood control: drop if requesting challenge too often
|
|
if(challenge[i].time > realtime - net_challengefloodblockingtimeout.value)
|
|
return true;
|
|
}
|
|
challenge[i].time = realtime;
|
|
// send the challenge
|
|
portable_snprintf(response, sizeof(response), "\377\377\377\377challenge %s", challenge[i].string);
|
|
response_len = strlen(response) + 1;
|
|
Crypto_ServerAppendToChallenge(string, length, response, &response_len, sizeof(response));
|
|
NetConn_Write(mysocket, response, response_len, peeraddress);
|
|
return true;
|
|
}
|
|
if (length > 8 && !memcmp(string, "connect\\", 8))
|
|
{
|
|
crypto_t *crypto = Crypto_ServerGetInstance(peeraddress);
|
|
string += 7;
|
|
length -= 7;
|
|
|
|
if(crypto && crypto->authenticated)
|
|
{
|
|
// no need to check challenge
|
|
if(crypto_developer.integer)
|
|
{
|
|
Con_Printf("%s connection to %s is being established: client is %s@%.*s, I am %.*s@%.*s\n",
|
|
crypto->use_aes ? "Encrypted" : "Authenticated",
|
|
addressstring2,
|
|
crypto->client_idfp[0] ? crypto->client_idfp : "-",
|
|
crypto_keyfp_recommended_length, crypto->client_keyfp[0] ? crypto->client_keyfp : "-",
|
|
crypto_keyfp_recommended_length, crypto->server_idfp[0] ? crypto->server_idfp : "-",
|
|
crypto_keyfp_recommended_length, crypto->server_keyfp[0] ? crypto->server_keyfp : "-"
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((s = InfoString_GetValue(string, "challenge", infostringvalue, sizeof(infostringvalue))))
|
|
{
|
|
// validate the challenge
|
|
for (i = 0;i < MAX_CHALLENGES;i++)
|
|
if(challenge[i].time > 0)
|
|
if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strcmp(challenge[i].string, s))
|
|
break;
|
|
// if the challenge is not recognized, drop the packet
|
|
if (i == MAX_CHALLENGES)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if((s = InfoString_GetValue(string, "message", infostringvalue, sizeof(infostringvalue))))
|
|
Con_DPrintf("Connecting client %s sent us the message: %s\n", addressstring2, s);
|
|
|
|
if(!(islocal || sv_public.integer > -2))
|
|
{
|
|
if (developer_extra.integer)
|
|
Con_Printf("Datagram_ParseConnectionless: sending \"reject %s\" to %s.\n", sv_public_rejectreason.string, addressstring2);
|
|
NetConn_WriteString(mysocket, portable_va(vabuf, sizeof(vabuf), "\377\377\377\377reject %s", sv_public_rejectreason.string), peeraddress);
|
|
return true;
|
|
}
|
|
|
|
// check engine protocol
|
|
if(!(s = InfoString_GetValue(string, "protocol", infostringvalue, sizeof(infostringvalue))) || strcmp(s, "darkplaces 3"))
|
|
{
|
|
if (developer_extra.integer)
|
|
Con_Printf("Datagram_ParseConnectionless: sending \"reject Wrong game protocol.\" to %s.\n", addressstring2);
|
|
NetConn_WriteString(mysocket, "\377\377\377\377reject Wrong game protocol.", peeraddress);
|
|
return true;
|
|
}
|
|
|
|
// see if this is a duplicate connection request or a disconnected
|
|
// client who is rejoining to the same client slot
|
|
for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++)
|
|
{
|
|
if (client->netconnection && LHNETADDRESS_Compare(peeraddress, &client->netconnection->peeraddress) == 0)
|
|
{
|
|
// this is a known client...
|
|
if(crypto && crypto->authenticated)
|
|
{
|
|
// reject if changing key!
|
|
if(client->netconnection->crypto.authenticated)
|
|
{
|
|
if(
|
|
strcmp(client->netconnection->crypto.client_idfp, crypto->client_idfp)
|
|
||
|
|
strcmp(client->netconnection->crypto.server_idfp, crypto->server_idfp)
|
|
||
|
|
strcmp(client->netconnection->crypto.client_keyfp, crypto->client_keyfp)
|
|
||
|
|
strcmp(client->netconnection->crypto.server_keyfp, crypto->server_keyfp)
|
|
)
|
|
{
|
|
if (developer_extra.integer)
|
|
Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to change key of crypto.\" to %s.\n", addressstring2);
|
|
NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to change key of crypto.", peeraddress);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// reject if downgrading!
|
|
if(client->netconnection->crypto.authenticated)
|
|
{
|
|
if (developer_extra.integer)
|
|
Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to downgrade crypto.\" to %s.\n", addressstring2);
|
|
NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to downgrade crypto.", peeraddress);
|
|
return true;
|
|
}
|
|
}
|
|
if (client->begun)
|
|
{
|
|
// client crashed and is coming back,
|
|
// keep their stuff intact
|
|
if (developer_extra.integer)
|
|
Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", addressstring2);
|
|
NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress);
|
|
if(crypto && crypto->authenticated)
|
|
Crypto_ServerFinishInstance(&client->netconnection->crypto, crypto);
|
|
SV_SendServerinfo(client);
|
|
}
|
|
else
|
|
{
|
|
// client is still trying to connect,
|
|
// so we send a duplicate reply
|
|
if (developer_extra.integer)
|
|
Con_Printf("Datagram_ParseConnectionless: sending duplicate accept to %s.\n", addressstring2);
|
|
if(crypto && crypto->authenticated)
|
|
Crypto_ServerFinishInstance(&client->netconnection->crypto, crypto);
|
|
NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (NetConn_PreventFlood(peeraddress, sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0]), net_connectfloodblockingtimeout.value, true))
|
|
return true;
|
|
|
|
// find an empty client slot for this new client
|
|
for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++)
|
|
{
|
|
netconn_t *conn;
|
|
if (!client->active && (conn = NetConn_Open(mysocket, peeraddress)))
|
|
{
|
|
// allocated connection
|
|
if (developer_extra.integer)
|
|
Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", conn->address);
|
|
NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress);
|
|
// now set up the client
|
|
if(crypto && crypto->authenticated)
|
|
Crypto_ServerFinishInstance(&conn->crypto, crypto);
|
|
SV_ConnectClient(clientnum, conn);
|
|
NetConn_Heartbeat(1);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// no empty slots found - server is full
|
|
if (developer_extra.integer)
|
|
Con_Printf("Datagram_ParseConnectionless: sending \"reject Server is full.\" to %s.\n", addressstring2);
|
|
NetConn_WriteString(mysocket, "\377\377\377\377reject Server is full.", peeraddress);
|
|
|
|
return true;
|
|
}
|
|
if (length >= 7 && !memcmp(string, "getinfo", 7) && (islocal || sv_public.integer > -1))
|
|
{
|
|
const char *challenge = NULL;
|
|
|
|
if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false))
|
|
return true;
|
|
|
|
// If there was a challenge in the getinfo message
|
|
if (length > 8 && string[7] == ' ')
|
|
challenge = string + 8;
|
|
|
|
if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), false))
|
|
{
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Sending reply to master %s - %s\n", addressstring2, response);
|
|
NetConn_WriteString(mysocket, response, peeraddress);
|
|
}
|
|
return true;
|
|
}
|
|
if (length >= 9 && !memcmp(string, "getstatus", 9) && (islocal || sv_public.integer > -1))
|
|
{
|
|
const char *challenge = NULL;
|
|
|
|
if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false))
|
|
return true;
|
|
|
|
// If there was a challenge in the getinfo message
|
|
if (length > 10 && string[9] == ' ')
|
|
challenge = string + 10;
|
|
|
|
if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), true))
|
|
{
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Sending reply to client %s - %s\n", addressstring2, response);
|
|
NetConn_WriteString(mysocket, response, peeraddress);
|
|
}
|
|
return true;
|
|
}
|
|
if (length >= 37 && !memcmp(string, "srcon HMAC-MD4 TIME ", 20))
|
|
{
|
|
char *password = string + 20;
|
|
char *timeval = string + 37;
|
|
char *s = strchr(timeval, ' ');
|
|
char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it
|
|
const char *userlevel;
|
|
|
|
if(rcon_secure.integer > 1)
|
|
return true;
|
|
|
|
if(!s)
|
|
return true; // invalid packet
|
|
++s;
|
|
|
|
userlevel = RCon_Authenticate(peeraddress, password, s, endpos, hmac_mdfour_time_matching, timeval, endpos - timeval - 1); // not including the appended \0 into the HMAC
|
|
RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false);
|
|
return true;
|
|
}
|
|
if (length >= 42 && !memcmp(string, "srcon HMAC-MD4 CHALLENGE ", 25))
|
|
{
|
|
char *password = string + 25;
|
|
char *challenge = string + 42;
|
|
char *s = strchr(challenge, ' ');
|
|
char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it
|
|
const char *userlevel;
|
|
if(!s)
|
|
return true; // invalid packet
|
|
++s;
|
|
|
|
userlevel = RCon_Authenticate(peeraddress, password, s, endpos, hmac_mdfour_challenge_matching, challenge, endpos - challenge - 1); // not including the appended \0 into the HMAC
|
|
RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false);
|
|
return true;
|
|
}
|
|
if (length >= 5 && !memcmp(string, "rcon ", 5))
|
|
{
|
|
int i;
|
|
char *s = string + 5;
|
|
char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it
|
|
char password[64];
|
|
|
|
if(rcon_secure.integer > 0)
|
|
return true;
|
|
|
|
for (i = 0;!ISWHITESPACE(*s);s++)
|
|
if (i < (int)sizeof(password) - 1)
|
|
password[i++] = *s;
|
|
if(ISWHITESPACE(*s) && s != endpos) // skip leading ugly space
|
|
++s;
|
|
password[i] = 0;
|
|
if (!ISWHITESPACE(password[0]))
|
|
{
|
|
const char *userlevel = RCon_Authenticate(peeraddress, password, s, endpos, plaintext_matching, NULL, 0);
|
|
RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false);
|
|
}
|
|
return true;
|
|
}
|
|
if (!strncmp(string, "extResponse ", 12))
|
|
{
|
|
++sv_net_extresponse_count;
|
|
if(sv_net_extresponse_count > NET_EXTRESPONSE_MAX)
|
|
sv_net_extresponse_count = NET_EXTRESPONSE_MAX;
|
|
sv_net_extresponse_last = (sv_net_extresponse_last + 1) % NET_EXTRESPONSE_MAX;
|
|
portable_snprintf(sv_net_extresponse[sv_net_extresponse_last], sizeof(sv_net_extresponse[sv_net_extresponse_last]), "'%s' %s", addressstring2, string + 12);
|
|
return true;
|
|
}
|
|
if (!strncmp(string, "ping", 4))
|
|
{
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Received ping from %s, sending ack\n", addressstring2);
|
|
NetConn_WriteString(mysocket, "\377\377\377\377ack", peeraddress);
|
|
return true;
|
|
}
|
|
if (!strncmp(string, "ack", 3))
|
|
return true;
|
|
// we may not have liked the packet, but it was a command packet, so
|
|
// we're done processing this packet now
|
|
return true;
|
|
}
|
|
// netquake control packets, supported for compatibility only, and only
|
|
// when running game protocols that are normally served via this connection
|
|
// protocol
|
|
// (this protects more modern protocols against being used for
|
|
// Quake packet flood Denial Of Service attacks)
|
|
if (length >= 5 && (i = BuffBigLong(data)) && (i & (~NETFLAG_LENGTH_MASK)) == (int)NETFLAG_CTL && (i & NETFLAG_LENGTH_MASK) == length && (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3) && !ENCRYPTION_REQUIRED)
|
|
{
|
|
int c;
|
|
int protocolnumber;
|
|
const char *protocolname;
|
|
data += 4;
|
|
length -= 4;
|
|
SZ_Clear(&sv_message);
|
|
SZ_Write(&sv_message, data, length);
|
|
MSG_BeginReading(&sv_message);
|
|
c = MSG_ReadByte(&sv_message);
|
|
switch (c)
|
|
{
|
|
case CCREQ_CONNECT:
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_CONNECT from %s.\n", addressstring2);
|
|
if(!(islocal || sv_public.integer > -2))
|
|
{
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"%s\" to %s.\n", sv_public_rejectreason.string, addressstring2);
|
|
SZ_Clear(&sv_message);
|
|
// save space for the header, filled in later
|
|
MSG_WriteLong(&sv_message, 0);
|
|
MSG_WriteByte(&sv_message, CCREP_REJECT);
|
|
MSG_WriteString(&sv_message, va(vabuf, sizeof(vabuf), "%s\n", sv_public_rejectreason.string));
|
|
StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
|
|
NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
|
|
SZ_Clear(&sv_message);
|
|
break;
|
|
}
|
|
|
|
protocolname = MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring));
|
|
protocolnumber = MSG_ReadByte(&sv_message);
|
|
if (strcmp(protocolname, "QUAKE") || protocolnumber != NET_PROTOCOL_VERSION)
|
|
{
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Incompatible version.\" to %s.\n", addressstring2);
|
|
SZ_Clear(&sv_message);
|
|
// save space for the header, filled in later
|
|
MSG_WriteLong(&sv_message, 0);
|
|
MSG_WriteByte(&sv_message, CCREP_REJECT);
|
|
MSG_WriteString(&sv_message, "Incompatible version.\n");
|
|
StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
|
|
NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
|
|
SZ_Clear(&sv_message);
|
|
break;
|
|
}
|
|
|
|
// see if this connect request comes from a known client
|
|
for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++)
|
|
{
|
|
if (client->netconnection && LHNETADDRESS_Compare(peeraddress, &client->netconnection->peeraddress) == 0)
|
|
{
|
|
// this is either a duplicate connection request
|
|
// or coming back from a timeout
|
|
// (if so, keep their stuff intact)
|
|
|
|
crypto_t *crypto = Crypto_ServerGetInstance(peeraddress);
|
|
if((crypto && crypto->authenticated) || client->netconnection->crypto.authenticated)
|
|
{
|
|
if (developer_extra.integer)
|
|
Con_Printf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Attempt to downgrade crypto.\" to %s.\n", addressstring2);
|
|
SZ_Clear(&sv_message);
|
|
// save space for the header, filled in later
|
|
MSG_WriteLong(&sv_message, 0);
|
|
MSG_WriteByte(&sv_message, CCREP_REJECT);
|
|
MSG_WriteString(&sv_message, "Attempt to downgrade crypto.\n");
|
|
StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
|
|
NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
|
|
SZ_Clear(&sv_message);
|
|
return true;
|
|
}
|
|
|
|
// send a reply
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: sending duplicate CCREP_ACCEPT to %s.\n", addressstring2);
|
|
SZ_Clear(&sv_message);
|
|
// save space for the header, filled in later
|
|
MSG_WriteLong(&sv_message, 0);
|
|
MSG_WriteByte(&sv_message, CCREP_ACCEPT);
|
|
MSG_WriteLong(&sv_message, LHNETADDRESS_GetPort(LHNET_AddressFromSocket(client->netconnection->mysocket)));
|
|
StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
|
|
NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
|
|
SZ_Clear(&sv_message);
|
|
|
|
// if client is already spawned, re-send the
|
|
// serverinfo message as they'll need it to play
|
|
if (client->begun)
|
|
SV_SendServerinfo(client);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// this is a new client, check for connection flood
|
|
if (NetConn_PreventFlood(peeraddress, sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0]), net_connectfloodblockingtimeout.value, true))
|
|
break;
|
|
|
|
// find a slot for the new client
|
|
for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++)
|
|
{
|
|
netconn_t *conn;
|
|
if (!client->active && (client->netconnection = conn = NetConn_Open(mysocket, peeraddress)) != NULL)
|
|
{
|
|
// connect to the client
|
|
// everything is allocated, just fill in the details
|
|
strlcpy (conn->address, addressstring2, sizeof (conn->address));
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_ACCEPT to %s.\n", addressstring2);
|
|
// send back the info about the server connection
|
|
SZ_Clear(&sv_message);
|
|
// save space for the header, filled in later
|
|
MSG_WriteLong(&sv_message, 0);
|
|
MSG_WriteByte(&sv_message, CCREP_ACCEPT);
|
|
MSG_WriteLong(&sv_message, LHNETADDRESS_GetPort(LHNET_AddressFromSocket(conn->mysocket)));
|
|
StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
|
|
NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
|
|
SZ_Clear(&sv_message);
|
|
// now set up the client struct
|
|
SV_ConnectClient(clientnum, conn);
|
|
NetConn_Heartbeat(1);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Server is full.\" to %s.\n", addressstring2);
|
|
// no room; try to let player know
|
|
SZ_Clear(&sv_message);
|
|
// save space for the header, filled in later
|
|
MSG_WriteLong(&sv_message, 0);
|
|
MSG_WriteByte(&sv_message, CCREP_REJECT);
|
|
MSG_WriteString(&sv_message, "Server is full.\n");
|
|
StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
|
|
NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
|
|
SZ_Clear(&sv_message);
|
|
break;
|
|
case CCREQ_SERVER_INFO:
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_SERVER_INFO from %s.\n", addressstring2);
|
|
if(!(islocal || sv_public.integer > -1))
|
|
break;
|
|
|
|
if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false))
|
|
break;
|
|
|
|
if (sv.active && !strcmp(MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)), "QUAKE"))
|
|
{
|
|
int numclients;
|
|
char myaddressstring[128];
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_SERVER_INFO to %s.\n", addressstring2);
|
|
SZ_Clear(&sv_message);
|
|
// save space for the header, filled in later
|
|
MSG_WriteLong(&sv_message, 0);
|
|
MSG_WriteByte(&sv_message, CCREP_SERVER_INFO);
|
|
LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), myaddressstring, sizeof(myaddressstring), true);
|
|
MSG_WriteString(&sv_message, myaddressstring);
|
|
MSG_WriteString(&sv_message, hostname.string);
|
|
MSG_WriteString(&sv_message, sv.name);
|
|
// How many clients are there?
|
|
for (i = 0, numclients = 0;i < svs.maxclients;i++)
|
|
if (svs.clients[i].active)
|
|
numclients++;
|
|
MSG_WriteByte(&sv_message, numclients);
|
|
MSG_WriteByte(&sv_message, svs.maxclients);
|
|
MSG_WriteByte(&sv_message, NET_PROTOCOL_VERSION);
|
|
StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
|
|
NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
|
|
SZ_Clear(&sv_message);
|
|
}
|
|
break;
|
|
case CCREQ_PLAYER_INFO:
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_PLAYER_INFO from %s.\n", addressstring2);
|
|
if(!(islocal || sv_public.integer > -1))
|
|
break;
|
|
|
|
if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false))
|
|
break;
|
|
|
|
if (sv.active)
|
|
{
|
|
int playerNumber, activeNumber, clientNumber;
|
|
client_t *client;
|
|
|
|
playerNumber = MSG_ReadByte(&sv_message);
|
|
activeNumber = -1;
|
|
for (clientNumber = 0, client = svs.clients; clientNumber < svs.maxclients; clientNumber++, client++)
|
|
if (client->active && ++activeNumber == playerNumber)
|
|
break;
|
|
if (clientNumber != svs.maxclients)
|
|
{
|
|
SZ_Clear(&sv_message);
|
|
// save space for the header, filled in later
|
|
MSG_WriteLong(&sv_message, 0);
|
|
MSG_WriteByte(&sv_message, CCREP_PLAYER_INFO);
|
|
MSG_WriteByte(&sv_message, playerNumber);
|
|
MSG_WriteString(&sv_message, client->name);
|
|
MSG_WriteLong(&sv_message, client->colors);
|
|
MSG_WriteLong(&sv_message, client->frags);
|
|
MSG_WriteLong(&sv_message, (int)(realtime - client->connecttime));
|
|
if(sv_status_privacy.integer)
|
|
MSG_WriteString(&sv_message, client->netconnection ? "hidden" : "botclient");
|
|
else
|
|
MSG_WriteString(&sv_message, client->netconnection ? client->netconnection->address : "botclient");
|
|
StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
|
|
NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
|
|
SZ_Clear(&sv_message);
|
|
}
|
|
}
|
|
break;
|
|
case CCREQ_RULE_INFO:
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_RULE_INFO from %s.\n", addressstring2);
|
|
if(!(islocal || sv_public.integer > -1))
|
|
break;
|
|
|
|
// no flood check here, as it only returns one cvar for one cvar and clients may iterate quickly
|
|
|
|
if (sv.active)
|
|
{
|
|
char *prevCvarName;
|
|
cvar_t *var;
|
|
|
|
// find the search start location
|
|
prevCvarName = MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring));
|
|
var = Cvar_FindVarAfter(prevCvarName, CVAR_NOTIFY);
|
|
|
|
// send the response
|
|
SZ_Clear(&sv_message);
|
|
// save space for the header, filled in later
|
|
MSG_WriteLong(&sv_message, 0);
|
|
MSG_WriteByte(&sv_message, CCREP_RULE_INFO);
|
|
if (var)
|
|
{
|
|
MSG_WriteString(&sv_message, var->name);
|
|
MSG_WriteString(&sv_message, var->string);
|
|
}
|
|
StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
|
|
NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
|
|
SZ_Clear(&sv_message);
|
|
}
|
|
break;
|
|
case CCREQ_RCON:
|
|
if (developer_extra.integer)
|
|
Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_RCON from %s.\n", addressstring2);
|
|
if (sv.active && !rcon_secure.integer)
|
|
{
|
|
char password[2048];
|
|
char cmd[2048];
|
|
char *s;
|
|
char *endpos;
|
|
const char *userlevel;
|
|
strlcpy(password, MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)), sizeof(password));
|
|
strlcpy(cmd, MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)), sizeof(cmd));
|
|
s = cmd;
|
|
endpos = cmd + strlen(cmd) + 1; // one behind the NUL, so adding strlen+1 will eventually reach it
|
|
userlevel = RCon_Authenticate(peeraddress, password, s, endpos, plaintext_matching, NULL, 0);
|
|
RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, true);
|
|
return true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
SZ_Clear(&sv_message);
|
|
// we may not have liked the packet, but it was a valid control
|
|
// packet, so we're done processing this packet now
|
|
return true;
|
|
}
|
|
if (host_client)
|
|
{
|
|
if ((ret = NetConn_ReceivedMessage(host_client->netconnection, data, length, sv.protocol, host_client->begun ? net_messagetimeout.value : net_connecttimeout.value)) == 2)
|
|
{
|
|
SV_ReadClientMessage();
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void NetConn_ServerFrame(void)
|
|
{
|
|
int i, length;
|
|
lhnetaddress_t peeraddress;
|
|
unsigned char readbuffer[NET_HEADERSIZE+NET_MAXMESSAGE];
|
|
for (i = 0;i < sv_numsockets;i++)
|
|
while (sv_sockets[i] && (length = NetConn_Read(sv_sockets[i], readbuffer, sizeof(readbuffer), &peeraddress)) > 0)
|
|
NetConn_ServerParsePacket(sv_sockets[i], readbuffer, length, &peeraddress);
|
|
for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
|
|
{
|
|
// never timeout loopback connections
|
|
if (host_client->netconnection && realtime > host_client->netconnection->timeout && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP)
|
|
{
|
|
Con_Printf("Client \"%s\" connection timed out\n", host_client->name);
|
|
SV_DropClient(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NetConn_SleepMicroseconds(int microseconds)
|
|
{
|
|
LHNET_SleepUntilPacket_Microseconds(microseconds);
|
|
}
|
|
|
|
void NetConn_QueryMasters(qboolean querydp, qboolean queryqw)
|
|
{
|
|
int i, j;
|
|
int masternum;
|
|
lhnetaddress_t masteraddress;
|
|
lhnetaddress_t broadcastaddress;
|
|
char request[256];
|
|
|
|
if (serverlist_cachecount >= SERVERLIST_TOTALSIZE)
|
|
return;
|
|
|
|
// 26000 is the default quake server port, servers on other ports will not
|
|
// be found
|
|
// note this is IPv4-only, I doubt there are IPv6-only LANs out there
|
|
LHNETADDRESS_FromString(&broadcastaddress, "255.255.255.255", 26000);
|
|
|
|
if (querydp)
|
|
{
|
|
for (i = 0;i < cl_numsockets;i++)
|
|
{
|
|
if (cl_sockets[i])
|
|
{
|
|
const char *cmdname, *extraoptions;
|
|
int af = LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i]));
|
|
|
|
if(LHNETADDRESS_GetAddressType(&broadcastaddress) == af)
|
|
{
|
|
// search LAN for Quake servers
|
|
SZ_Clear(&cl_message);
|
|
// save space for the header, filled in later
|
|
MSG_WriteLong(&cl_message, 0);
|
|
MSG_WriteByte(&cl_message, CCREQ_SERVER_INFO);
|
|
MSG_WriteString(&cl_message, "QUAKE");
|
|
MSG_WriteByte(&cl_message, NET_PROTOCOL_VERSION);
|
|
StoreBigLong(cl_message.data, NETFLAG_CTL | (cl_message.cursize & NETFLAG_LENGTH_MASK));
|
|
NetConn_Write(cl_sockets[i], cl_message.data, cl_message.cursize, &broadcastaddress);
|
|
SZ_Clear(&cl_message);
|
|
|
|
// search LAN for DarkPlaces servers
|
|
NetConn_WriteString(cl_sockets[i], "\377\377\377\377getstatus", &broadcastaddress);
|
|
}
|
|
|
|
// build the getservers message to send to the dpmaster master servers
|
|
if (LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])) == LHNETADDRESSTYPE_INET6)
|
|
{
|
|
cmdname = "getserversExt";
|
|
extraoptions = " ipv4 ipv6"; // ask for IPv4 and IPv6 servers
|
|
}
|
|
else
|
|
{
|
|
cmdname = "getservers";
|
|
extraoptions = "";
|
|
}
|
|
portable_snprintf(request, sizeof(request), "\377\377\377\377%s %s %u empty full%s", cmdname, gamename, NET_PROTOCOL_VERSION, extraoptions);
|
|
|
|
// search internet
|
|
for (masternum = 0;sv_masters[masternum].name;masternum++)
|
|
{
|
|
if (sv_masters[masternum].string && sv_masters[masternum].string[0] && LHNETADDRESS_FromString(&masteraddress, sv_masters[masternum].string, DPMASTER_PORT) && LHNETADDRESS_GetAddressType(&masteraddress) == af)
|
|
{
|
|
masterquerycount++;
|
|
NetConn_WriteString(cl_sockets[i], request, &masteraddress);
|
|
}
|
|
}
|
|
|
|
// search favorite servers
|
|
for(j = 0; j < nFavorites; ++j)
|
|
{
|
|
if(LHNETADDRESS_GetAddressType(&favorites[j]) == af)
|
|
{
|
|
if(LHNETADDRESS_ToString(&favorites[j], request, sizeof(request), true))
|
|
NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_DARKPLACES7, request, true );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// only query QuakeWorld servers when the user wants to
|
|
if (queryqw)
|
|
{
|
|
for (i = 0;i < cl_numsockets;i++)
|
|
{
|
|
if (cl_sockets[i])
|
|
{
|
|
int af = LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i]));
|
|
|
|
if(LHNETADDRESS_GetAddressType(&broadcastaddress) == af)
|
|
{
|
|
// search LAN for QuakeWorld servers
|
|
NetConn_WriteString(cl_sockets[i], "\377\377\377\377status\n", &broadcastaddress);
|
|
|
|
// build the getservers message to send to the qwmaster master servers
|
|
// note this has no -1 prefix, and the trailing nul byte is sent
|
|
portable_snprintf(request, sizeof(request), "c\n");
|
|
}
|
|
|
|
// search internet
|
|
for (masternum = 0;sv_qwmasters[masternum].name;masternum++)
|
|
{
|
|
if (sv_qwmasters[masternum].string && LHNETADDRESS_FromString(&masteraddress, sv_qwmasters[masternum].string, QWMASTER_PORT) && LHNETADDRESS_GetAddressType(&masteraddress) == LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])))
|
|
{
|
|
if (m_state != m_slist)
|
|
{
|
|
char lookupstring[128];
|
|
LHNETADDRESS_ToString(&masteraddress, lookupstring, sizeof(lookupstring), true);
|
|
Con_Printf("Querying master %s (resolved from %s)\n", lookupstring, sv_qwmasters[masternum].string);
|
|
}
|
|
masterquerycount++;
|
|
NetConn_Write(cl_sockets[i], request, (int)strlen(request) + 1, &masteraddress);
|
|
}
|
|
}
|
|
|
|
// search favorite servers
|
|
for(j = 0; j < nFavorites; ++j)
|
|
{
|
|
if(LHNETADDRESS_GetAddressType(&favorites[j]) == af)
|
|
{
|
|
if(LHNETADDRESS_ToString(&favorites[j], request, sizeof(request), true))
|
|
{
|
|
NetConn_WriteString(cl_sockets[i], "\377\377\377\377status\n", &favorites[j]);
|
|
NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_QUAKEWORLD, request, true );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!masterquerycount)
|
|
{
|
|
Con_Print("Unable to query master servers, no suitable network sockets active.\n");
|
|
M_Update_Return_Reason("No network");
|
|
}
|
|
}
|
|
|
|
void NetConn_Heartbeat(int priority)
|
|
{
|
|
lhnetaddress_t masteraddress;
|
|
int masternum;
|
|
lhnetsocket_t *mysocket;
|
|
|
|
// if it's a state change (client connected), limit next heartbeat to no
|
|
// more than 30 sec in the future
|
|
if (priority == 1 && nextheartbeattime > realtime + 30.0)
|
|
nextheartbeattime = realtime + 30.0;
|
|
|
|
// limit heartbeatperiod to 30 to 270 second range,
|
|
// lower limit is to avoid abusing master servers with excess traffic,
|
|
// upper limit is to avoid timing out on the master server (which uses
|
|
// 300 sec timeout)
|
|
if (sv_heartbeatperiod.value < 30)
|
|
Cvar_SetValueQuick(&sv_heartbeatperiod, 30);
|
|
if (sv_heartbeatperiod.value > 270)
|
|
Cvar_SetValueQuick(&sv_heartbeatperiod, 270);
|
|
|
|
// make advertising optional and don't advertise singleplayer games, and
|
|
// only send a heartbeat as often as the admin wants
|
|
if (sv.active && sv_public.integer > 0 && svs.maxclients >= 2 && (priority > 1 || realtime > nextheartbeattime))
|
|
{
|
|
nextheartbeattime = realtime + sv_heartbeatperiod.value;
|
|
for (masternum = 0;sv_masters[masternum].name;masternum++)
|
|
if (sv_masters[masternum].string && sv_masters[masternum].string[0] && LHNETADDRESS_FromString(&masteraddress, sv_masters[masternum].string, DPMASTER_PORT) && (mysocket = NetConn_ChooseServerSocketForAddress(&masteraddress)))
|
|
NetConn_WriteString(mysocket, "\377\377\377\377heartbeat DarkPlaces\x0A", &masteraddress);
|
|
}
|
|
}
|
|
|
|
static void Net_Heartbeat_f(void)
|
|
{
|
|
if (sv.active)
|
|
NetConn_Heartbeat(2);
|
|
else
|
|
Con_Print("No server running, can not heartbeat to master server.\n");
|
|
}
|
|
|
|
static void PrintStats(netconn_t *conn)
|
|
{
|
|
if ((cls.state == ca_connected && cls.protocol == PROTOCOL_QUAKEWORLD) || (sv.active && sv.protocol == PROTOCOL_QUAKEWORLD))
|
|
Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->outgoing_unreliable_sequence, conn->qw.incoming_sequence);
|
|
else
|
|
Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->nq.sendSequence, conn->nq.receiveSequence);
|
|
Con_Printf("unreliable messages sent = %i\n", conn->unreliableMessagesSent);
|
|
Con_Printf("unreliable messages recv = %i\n", conn->unreliableMessagesReceived);
|
|
Con_Printf("reliable messages sent = %i\n", conn->reliableMessagesSent);
|
|
Con_Printf("reliable messages received = %i\n", conn->reliableMessagesReceived);
|
|
Con_Printf("packetsSent = %i\n", conn->packetsSent);
|
|
Con_Printf("packetsReSent = %i\n", conn->packetsReSent);
|
|
Con_Printf("packetsReceived = %i\n", conn->packetsReceived);
|
|
Con_Printf("receivedDuplicateCount = %i\n", conn->receivedDuplicateCount);
|
|
Con_Printf("droppedDatagrams = %i\n", conn->droppedDatagrams);
|
|
}
|
|
|
|
void Net_Stats_f(void)
|
|
{
|
|
netconn_t *conn;
|
|
Con_Print("connections =\n");
|
|
for (conn = netconn_list;conn;conn = conn->next)
|
|
PrintStats(conn);
|
|
}
|
|
|
|
void Net_Refresh_f(void)
|
|
{
|
|
if (m_state != m_slist) {
|
|
Con_Print("Sending new requests to master servers\n");
|
|
ServerList_QueryList(false, true, false, true);
|
|
Con_Print("Listening for replies...\n");
|
|
} else
|
|
ServerList_QueryList(false, true, false, false);
|
|
}
|
|
|
|
void Net_Slist_f(void)
|
|
{
|
|
ServerList_ResetMasks();
|
|
serverlist_sortbyfield = SLIF_PING;
|
|
serverlist_sortflags = 0;
|
|
if (m_state != m_slist) {
|
|
Con_Print("Sending requests to master servers\n");
|
|
ServerList_QueryList(true, true, false, true);
|
|
Con_Print("Listening for replies...\n");
|
|
} else
|
|
ServerList_QueryList(true, true, false, false);
|
|
}
|
|
|
|
void Net_SlistQW_f(void)
|
|
{
|
|
ServerList_ResetMasks();
|
|
serverlist_sortbyfield = SLIF_PING;
|
|
serverlist_sortflags = 0;
|
|
if (m_state != m_slist) {
|
|
Con_Print("Sending requests to master servers\n");
|
|
ServerList_QueryList(true, false, true, true);
|
|
serverlist_consoleoutput = true;
|
|
Con_Print("Listening for replies...\n");
|
|
} else
|
|
ServerList_QueryList(true, false, true, false);
|
|
}
|
|
|
|
void NetConn_Init(void)
|
|
{
|
|
int i;
|
|
lhnetaddress_t tempaddress;
|
|
netconn_mempool = Mem_AllocPool("network connections", 0, NULL);
|
|
Cmd_AddCommand("net_stats", Net_Stats_f, "print network statistics");
|
|
Cmd_AddCommand("net_slist", Net_Slist_f, "query dp master servers and print all server information");
|
|
Cmd_AddCommand("net_slistqw", Net_SlistQW_f, "query qw master servers and print all server information");
|
|
Cmd_AddCommand("net_refresh", Net_Refresh_f, "query dp master servers and refresh all server information");
|
|
Cmd_AddCommand("heartbeat", Net_Heartbeat_f, "send a heartbeat to the master server (updates your server information)");
|
|
Cvar_RegisterVariable(&rcon_restricted_password);
|
|
Cvar_RegisterVariable(&rcon_restricted_commands);
|
|
Cvar_RegisterVariable(&rcon_secure_maxdiff);
|
|
Cvar_RegisterVariable(&net_slist_queriespersecond);
|
|
Cvar_RegisterVariable(&net_slist_queriesperframe);
|
|
Cvar_RegisterVariable(&net_slist_timeout);
|
|
Cvar_RegisterVariable(&net_slist_maxtries);
|
|
Cvar_RegisterVariable(&net_slist_favorites);
|
|
Cvar_RegisterVariable(&net_slist_pause);
|
|
if(LHNET_DefaultDSCP(-1) >= 0) // register cvar only if supported
|
|
Cvar_RegisterVariable(&net_tos_dscp);
|
|
Cvar_RegisterVariable(&net_messagetimeout);
|
|
Cvar_RegisterVariable(&net_connecttimeout);
|
|
Cvar_RegisterVariable(&net_connectfloodblockingtimeout);
|
|
Cvar_RegisterVariable(&net_challengefloodblockingtimeout);
|
|
Cvar_RegisterVariable(&net_getstatusfloodblockingtimeout);
|
|
Cvar_RegisterVariable(&cl_netlocalping);
|
|
Cvar_RegisterVariable(&cl_netpacketloss_send);
|
|
Cvar_RegisterVariable(&cl_netpacketloss_receive);
|
|
Cvar_RegisterVariable(&hostname);
|
|
Cvar_RegisterVariable(&developer_networking);
|
|
Cvar_RegisterVariable(&cl_netport);
|
|
Cvar_RegisterVariable(&sv_netport);
|
|
Cvar_RegisterVariable(&net_address);
|
|
Cvar_RegisterVariable(&net_address_ipv6);
|
|
Cvar_RegisterVariable(&sv_public);
|
|
Cvar_RegisterVariable(&sv_public_rejectreason);
|
|
Cvar_RegisterVariable(&sv_heartbeatperiod);
|
|
for (i = 0;sv_masters[i].name;i++)
|
|
Cvar_RegisterVariable(&sv_masters[i]);
|
|
Cvar_RegisterVariable(&gameversion);
|
|
Cvar_RegisterVariable(&gameversion_min);
|
|
Cvar_RegisterVariable(&gameversion_max);
|
|
// COMMANDLINEOPTION: Server: -ip <ipaddress> sets the ip address of this machine for purposes of networking (default 0.0.0.0 also known as INADDR_ANY), use only if you have multiple network adapters and need to choose one specifically.
|
|
if ((i = COM_CheckParm("-ip")) && i + 1 < com_argc)
|
|
{
|
|
if (LHNETADDRESS_FromString(&tempaddress, com_argv[i + 1], 0) == 1)
|
|
{
|
|
Con_Printf("-ip option used, setting net_address to \"%s\"\n", com_argv[i + 1]);
|
|
Cvar_SetQuick(&net_address, com_argv[i + 1]);
|
|
}
|
|
else
|
|
Con_Printf("-ip option used, but unable to parse the address \"%s\"\n", com_argv[i + 1]);
|
|
}
|
|
// COMMANDLINEOPTION: Server: -port <portnumber> sets the port to use for a server (default 26000, the same port as QUAKE itself), useful if you host multiple servers on your machine
|
|
if (((i = COM_CheckParm("-port")) || (i = COM_CheckParm("-ipport")) || (i = COM_CheckParm("-udpport"))) && i + 1 < com_argc)
|
|
{
|
|
i = atoi(com_argv[i + 1]);
|
|
if (i >= 0 && i < 65536)
|
|
{
|
|
Con_Printf("-port option used, setting port cvar to %i\n", i);
|
|
Cvar_SetValueQuick(&sv_netport, i);
|
|
}
|
|
else
|
|
Con_Printf("-port option used, but %i is not a valid port number\n", i);
|
|
}
|
|
cl_numsockets = 0;
|
|
sv_numsockets = 0;
|
|
cl_message.data = cl_message_buf;
|
|
cl_message.maxsize = sizeof(cl_message_buf);
|
|
cl_message.cursize = 0;
|
|
sv_message.data = sv_message_buf;
|
|
sv_message.maxsize = sizeof(sv_message_buf);
|
|
sv_message.cursize = 0;
|
|
LHNET_Init();
|
|
if (Thread_HasThreads())
|
|
netconn_mutex = Thread_CreateMutex();
|
|
}
|
|
|
|
void NetConn_Shutdown(void)
|
|
{
|
|
NetConn_CloseClientPorts();
|
|
NetConn_CloseServerPorts();
|
|
LHNET_Shutdown();
|
|
if (netconn_mutex)
|
|
Thread_DestroyMutex(netconn_mutex);
|
|
netconn_mutex = NULL;
|
|
}
|
|
|