2 Copyright (C) 1996-1997 Id Software, Inc.
3 Copyright (C) 2002 Mathieu Olivier
4 Copyright (C) 2003 Ashley Rose Hale (LadyHavoc)
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 See the GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 // for secure rcon authentication
32 #define QWMASTER_PORT 27000
33 #define DPMASTER_PORT 27950
35 // note this defaults on for dedicated servers, off for listen servers
36 cvar_t sv_public = {CF_SERVER, "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"};
37 cvar_t sv_public_rejectreason = {CF_SERVER, "sv_public_rejectreason", "The server is closing.", "Rejection reason for connects when sv_public is -2"};
38 static cvar_t sv_heartbeatperiod = {CF_SERVER | CF_ARCHIVE, "sv_heartbeatperiod", "120", "how often to send heartbeat in seconds (only used if sv_public is 1)"};
39 extern cvar_t sv_status_privacy;
41 static cvar_t sv_masters [] =
43 {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "sv_master1", "", "user-chosen master server 1"},
44 {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "sv_master2", "", "user-chosen master server 2"},
45 {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "sv_master3", "", "user-chosen master server 3"},
46 {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "sv_master4", "", "user-chosen master server 4"},
47 {CF_CLIENT | CF_SERVER, "sv_masterextra1", "dpmaster.deathmask.net", "dpmaster.deathmask.net - default master server 1 (admin: Willis)"},
48 {CF_CLIENT | CF_SERVER, "sv_masterextra2", "dpmaster.tchr.no", "dpmaster.tchr.no - default master server 2 (admin: tChr)"},
49 {CF_CLIENT | CF_SERVER, "sv_masterextra3", "dpm.dpmaster.org:27777", "dpm.dpmaster.org - default master server 3 (admin: gazby/soylent_cow)"},
53 static cvar_t sv_qwmasters [] =
55 {CF_CLIENT | CF_ARCHIVE, "sv_qwmaster1", "", "user-chosen qwmaster server 1"},
56 {CF_CLIENT | CF_ARCHIVE, "sv_qwmaster2", "", "user-chosen qwmaster server 2"},
57 {CF_CLIENT | CF_ARCHIVE, "sv_qwmaster3", "", "user-chosen qwmaster server 3"},
58 {CF_CLIENT | CF_ARCHIVE, "sv_qwmaster4", "", "user-chosen qwmaster server 4"},
59 {CF_CLIENT, "sv_qwmasterextra1", "master.quakeservers.net:27000", "Global master server. (admin: unknown)"},
60 {CF_CLIENT, "sv_qwmasterextra2", "asgaard.morphos-team.net:27000", "Global master server. (admin: unknown)"},
61 {CF_CLIENT, "sv_qwmasterextra3", "qwmaster.ocrana.de:27000", "German master server. (admin: unknown)"},
62 {CF_CLIENT, "sv_qwmasterextra4", "qwmaster.fodquake.net:27000", "Global master server. (admin: unknown)"},
66 static double nextheartbeattime = 0;
70 static unsigned char cl_message_buf[NET_MAXMESSAGE];
71 static unsigned char sv_message_buf[NET_MAXMESSAGE];
72 char cl_readstring[MAX_INPUTLINE];
73 char sv_readstring[MAX_INPUTLINE];
75 cvar_t net_test = {CF_CLIENT | CF_SERVER, "net_test", "0", "internal development use only, leave it alone (usually does nothing anyway)"};
76 cvar_t net_usesizelimit = {CF_SERVER, "net_usesizelimit", "2", "use packet size limiting (0: never, 1: in non-CSQC mode, 2: always)"};
77 cvar_t net_burstreserve = {CF_SERVER, "net_burstreserve", "0.3", "how much of the burst time to reserve for packet size spikes"};
78 cvar_t net_messagetimeout = {CF_CLIENT | CF_SERVER, "net_messagetimeout","300", "drops players who have not sent any packets for this many seconds"};
79 cvar_t net_connecttimeout = {CF_CLIENT | CF_SERVER, "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."};
80 cvar_t net_connectfloodblockingtimeout = {CF_SERVER, "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."};
81 cvar_t net_challengefloodblockingtimeout = {CF_SERVER, "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."};
82 cvar_t net_getstatusfloodblockingtimeout = {CF_SERVER, "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 net_slist_timeout 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."};
83 cvar_t net_sourceaddresscheck = {CF_CLIENT, "net_sourceaddresscheck", "1", "compare the source IP address for replies (more secure, may break some bad multihoming setups"};
84 cvar_t hostname = {CF_SERVER | CF_ARCHIVE, "hostname", "UNNAMED", "server message to show in server browser"};
85 cvar_t developer_networking = {CF_CLIENT | CF_SERVER, "developer_networking", "0", "prints all received and sent packets (recommended only for debugging)"};
87 cvar_t net_fakelag = {CF_CLIENT, "net_fakelag","0", "lags local loopback connection by this much ping time (useful to play more fairly on your own server with people with higher pings)"};
88 static cvar_t net_fakeloss_send = {CF_CLIENT, "net_fakeloss_send","0", "drops this percentage of outgoing packets, useful for testing network protocol robustness (jerky movement, prediction errors, etc)"};
89 static cvar_t net_fakeloss_receive = {CF_CLIENT, "net_fakeloss_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)"};
92 static cvar_t net_slist_debug = {CF_CLIENT, "net_slist_debug", "0", "enables verbose messages for master server queries"};
93 static cvar_t net_slist_favorites = {CF_CLIENT | CF_ARCHIVE, "net_slist_favorites", "", "contains a list of IP addresses and ports to always query explicitly"};
94 static cvar_t net_slist_interval = {CF_CLIENT, "net_slist_interval", "1", "minimum number of seconds to wait between getstatus queries to the same DP server, must be >= server's net_getstatusfloodblockingtimeout"};
95 static cvar_t net_slist_maxping = {CF_CLIENT | CF_ARCHIVE, "net_slist_maxping", "420", "server query responses are ignored if their ping in milliseconds is higher than this"};
96 static cvar_t net_slist_maxtries = {CF_CLIENT, "net_slist_maxtries", "3", "how many times to ask the same server for information (more times gives better ping reports but takes longer)"};
97 static cvar_t net_slist_pause = {CF_CLIENT, "net_slist_pause", "0", "when set to 1, the server list sorting in the menu won't update until it is set back to 0"};
98 static cvar_t net_slist_queriespersecond = {CF_CLIENT, "net_slist_queriespersecond", "128", "how many server information requests to send per second"};
99 static cvar_t net_slist_queriesperframe = {CF_CLIENT, "net_slist_queriesperframe", "2", "maximum number of server information requests to send each rendered frame (guards against low framerates causing problems)"};
100 static cvar_t net_slist_timeout = {CF_CLIENT, "net_slist_timeout", "4", "minimum number of seconds to wait between status queries to the same QW server, determines which response belongs to which query so low values will cause impossible pings; also a warning is printed if a dpmaster query fails to complete within this time"};
103 static cvar_t net_tos_dscp = {CF_CLIENT | CF_ARCHIVE, "net_tos_dscp", "32", "DiffServ Codepoint for network sockets (may need game restart to apply)"};
104 static cvar_t gameversion = {CF_SERVER, "gameversion", "0", "version of game data (mod-specific) to be sent to querying clients"};
105 static cvar_t gameversion_min = {CF_CLIENT | CF_SERVER, "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"};
106 static cvar_t gameversion_max = {CF_CLIENT | CF_SERVER, "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"};
107 static cvar_t rcon_restricted_password = {CF_SERVER | CF_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"};
108 static cvar_t rcon_restricted_commands = {CF_SERVER, "rcon_restricted_commands", "", "allowed commands for rcon when the restricted mode password was used"};
109 static cvar_t rcon_secure_maxdiff = {CF_SERVER, "rcon_secure_maxdiff", "5", "maximum time difference between rcon request and server system clock (to protect against replay attack)"};
110 extern cvar_t rcon_secure;
111 extern cvar_t rcon_secure_challengetimeout;
113 double masterquerytime = -1000;
114 unsigned masterquerycount = 0;
115 unsigned masterreplycount = 0;
116 unsigned serverquerycount = 0;
117 unsigned serverreplycount = 0;
119 challenge_t challenges[MAX_CHALLENGES];
121 #define DPMASTER_COUNT sizeof(sv_masters) / sizeof(cvar_t)
122 #define QWMASTER_COUNT sizeof(sv_qwmasters) / sizeof(cvar_t)
124 /// bitfield because in theory we could be doing QW & DP simultaneously
125 uint8_t serverlist_querystage = 0;
128 #define SLIST_QUERYSTAGE_DPMASTERS 1
129 #define SLIST_QUERYSTAGE_QWMASTERS 2
130 #define SLIST_QUERYSTAGE_SERVERS 4
132 static uint8_t dpmasterstatus[DPMASTER_COUNT] = {0};
133 static uint8_t qwmasterstatus[QWMASTER_COUNT] = {0};
134 #define MASTER_TX_QUERY 1 // we sent the query
135 #define MASTER_RX_RESPONSE 2 // we got at least 1 packet of the response
136 #define MASTER_RX_COMPLETE 3 // we saw the EOT marker (assumes dpmaster >= 2.0, see dpmaster/doc/techinfo.txt)
138 /// the hash password for timestamp verification
139 char serverlist_dpserverquerykey[12]; // challenge_t uses [12]
142 static unsigned cl_numsockets;
143 static lhnetsocket_t *cl_sockets[16];
144 static unsigned sv_numsockets;
145 static lhnetsocket_t *sv_sockets[16];
147 netconn_t *netconn_list = NULL;
148 mempool_t *netconn_mempool = NULL;
149 void *netconn_mutex = NULL;
151 cvar_t cl_netport = {CF_CLIENT, "cl_port", "0", "forces client to use chosen port number if not 0"};
152 cvar_t sv_netport = {CF_SERVER, "port", "26000", "server port for players to connect to"};
153 cvar_t net_address = {CF_CLIENT | CF_SERVER, "net_address", "", "network address to open ipv4 ports on (if empty, use default interfaces)"};
154 cvar_t net_address_ipv6 = {CF_CLIENT | CF_SERVER, "net_address_ipv6", "", "network address to open ipv6 ports on (if empty, use default interfaces)"};
156 char cl_net_extresponse[NET_EXTRESPONSE_MAX][1400];
157 unsigned cl_net_extresponse_count = 0;
158 unsigned cl_net_extresponse_last = 0;
160 char sv_net_extresponse[NET_EXTRESPONSE_MAX][1400];
161 unsigned sv_net_extresponse_count = 0;
162 unsigned sv_net_extresponse_last = 0;
165 // ServerList interface
166 serverlist_mask_t serverlist_andmasks[SERVERLIST_ANDMASKCOUNT];
167 serverlist_mask_t serverlist_ormasks[SERVERLIST_ORMASKCOUNT];
169 serverlist_infofield_t serverlist_sortbyfield;
170 unsigned serverlist_sortflags;
172 unsigned serverlist_viewcount = 0;
173 uint16_t serverlist_viewlist[SERVERLIST_VIEWLISTSIZE];
175 unsigned serverlist_maxcachecount = 0;
176 unsigned serverlist_cachecount = 0;
177 serverlist_entry_t *serverlist_cache = NULL;
179 static qbool serverlist_consoleoutput;
181 static unsigned nFavorites = 0;
182 static lhnetaddress_t favorites[MAX_FAVORITESERVERS];
183 static unsigned nFavorites_idfp = 0;
184 static char favorites_idfp[MAX_FAVORITESERVERS][FP64_SIZE+1];
186 void NetConn_UpdateFavorites_c(cvar_t *var)
192 while((size_t) nFavorites < sizeof(favorites) / sizeof(*favorites) && COM_ParseToken_Console(&p))
194 if(com_token[0] != '[' && strlen(com_token) == FP64_SIZE && !strchr(com_token, '.'))
195 // currently 44 bytes, longest possible IPv6 address: 39 bytes, so this works
196 // (if v6 address contains port, it must start with '[')
198 dp_strlcpy(favorites_idfp[nFavorites_idfp], com_token, sizeof(favorites_idfp[nFavorites_idfp]));
203 if(LHNETADDRESS_FromString(&favorites[nFavorites], com_token, 26000))
209 /// helper function to insert a value into the viewset
210 /// spare entries will be removed
211 static void _ServerList_ViewList_Helper_InsertBefore(unsigned index, serverlist_entry_t *entry)
215 if( serverlist_viewcount < SERVERLIST_VIEWLISTSIZE ) {
216 i = serverlist_viewcount++;
218 i = SERVERLIST_VIEWLISTSIZE - 1;
221 for( ; i > index ; i-- )
222 serverlist_viewlist[ i ] = serverlist_viewlist[ i - 1 ];
224 serverlist_viewlist[index] = (int)(entry - serverlist_cache);
227 /// we suppose serverlist_viewcount to be valid, ie > 0
228 static inline void _ServerList_ViewList_Helper_Remove(unsigned index)
230 serverlist_viewcount--;
231 for( ; index < serverlist_viewcount ; index++ )
232 serverlist_viewlist[index] = serverlist_viewlist[index + 1];
235 /// \returns true if A should be inserted before B
236 static qbool _ServerList_Entry_Compare( serverlist_entry_t *A, serverlist_entry_t *B )
238 int result = 0; // > 0 if for numbers A > B and for text if A < B
240 if( serverlist_sortflags & SLSF_CATEGORIES )
242 result = A->info.category - B->info.category;
247 if( serverlist_sortflags & SLSF_FAVORITES )
249 if(A->info.isfavorite != B->info.isfavorite)
250 return A->info.isfavorite;
253 switch( serverlist_sortbyfield ) {
255 result = A->info.ping - B->info.ping;
257 case SLIF_MAXPLAYERS:
258 result = A->info.maxplayers - B->info.maxplayers;
260 case SLIF_NUMPLAYERS:
261 result = A->info.numplayers - B->info.numplayers;
264 result = A->info.numbots - B->info.numbots;
267 result = A->info.numhumans - B->info.numhumans;
270 result = A->info.freeslots - B->info.freeslots;
273 result = A->info.protocol - B->info.protocol;
276 result = strcmp( B->info.cname, A->info.cname );
279 result = strcasecmp( B->info.game, A->info.game );
282 result = strcasecmp( B->info.map, A->info.map );
285 result = strcasecmp( B->info.mod, A->info.mod );
288 result = strcasecmp( B->info.name, A->info.name );
291 result = strcasecmp( B->info.qcstatus, A->info.qcstatus ); // not really THAT useful, though
294 result = A->info.category - B->info.category;
296 case SLIF_ISFAVORITE:
297 result = !!B->info.isfavorite - !!A->info.isfavorite;
300 Con_DPrint( "_ServerList_Entry_Compare: Bad serverlist_sortbyfield!\n" );
306 if( serverlist_sortflags & SLSF_DESCENDING )
312 // if the chosen sort key is identical, sort by index
313 // (makes this a stable sort, so that later replies from servers won't
314 // shuffle the servers around when they have the same ping)
318 static qbool _ServerList_CompareInt( int A, serverlist_maskop_t op, int B )
320 // This should actually be done with some intermediate and end-of-function return
332 case SLMO_GREATEREQUAL:
334 case SLMO_NOTCONTAIN:
335 case SLMO_STARTSWITH:
336 case SLMO_NOTSTARTSWITH:
339 Con_DPrint( "_ServerList_CompareInt: Bad op!\n" );
344 static qbool _ServerList_CompareStr( const char *A, serverlist_maskop_t op, const char *B )
347 char bufferA[ 1400 ], bufferB[ 1400 ]; // should be more than enough
348 COM_StringDecolorize(A, 0, bufferA, sizeof(bufferA), false);
349 for (i = 0;i < (int)sizeof(bufferA)-1 && bufferA[i];i++)
350 bufferA[i] = (bufferA[i] >= 'A' && bufferA[i] <= 'Z') ? (bufferA[i] + 'a' - 'A') : bufferA[i];
352 for (i = 0;i < (int)sizeof(bufferB)-1 && B[i];i++)
353 bufferB[i] = (B[i] >= 'A' && B[i] <= 'Z') ? (B[i] + 'a' - 'A') : B[i];
356 // Same here, also using an intermediate & final return would be more appropriate
360 return *bufferB && !!strstr( bufferA, bufferB ); // we want a real bool
361 case SLMO_NOTCONTAIN:
362 return !*bufferB || !strstr( bufferA, bufferB );
363 case SLMO_STARTSWITH:
364 //Con_Printf("startsWith: %s %s\n", bufferA, bufferB);
365 return *bufferB && !memcmp(bufferA, bufferB, strlen(bufferB));
366 case SLMO_NOTSTARTSWITH:
367 return !*bufferB || memcmp(bufferA, bufferB, strlen(bufferB));
369 return strcmp( bufferA, bufferB ) < 0;
371 return strcmp( bufferA, bufferB ) <= 0;
373 return strcmp( bufferA, bufferB ) == 0;
375 return strcmp( bufferA, bufferB ) > 0;
377 return strcmp( bufferA, bufferB ) != 0;
378 case SLMO_GREATEREQUAL:
379 return strcmp( bufferA, bufferB ) >= 0;
381 Con_DPrint( "_ServerList_CompareStr: Bad op!\n" );
386 static qbool _ServerList_Entry_Mask( serverlist_mask_t *mask, serverlist_info_t *info )
388 if( !_ServerList_CompareInt( info->ping, mask->tests[SLIF_PING], mask->info.ping ) )
390 if( !_ServerList_CompareInt( info->maxplayers, mask->tests[SLIF_MAXPLAYERS], mask->info.maxplayers ) )
392 if( !_ServerList_CompareInt( info->numplayers, mask->tests[SLIF_NUMPLAYERS], mask->info.numplayers ) )
394 if( !_ServerList_CompareInt( info->numbots, mask->tests[SLIF_NUMBOTS], mask->info.numbots ) )
396 if( !_ServerList_CompareInt( info->numhumans, mask->tests[SLIF_NUMHUMANS], mask->info.numhumans ) )
398 if( !_ServerList_CompareInt( info->freeslots, mask->tests[SLIF_FREESLOTS], mask->info.freeslots ) )
400 if( !_ServerList_CompareInt( info->protocol, mask->tests[SLIF_PROTOCOL], mask->info.protocol ))
402 if( *mask->info.cname
403 && !_ServerList_CompareStr( info->cname, mask->tests[SLIF_CNAME], mask->info.cname ) )
406 && !_ServerList_CompareStr( info->game, mask->tests[SLIF_GAME], mask->info.game ) )
409 && !_ServerList_CompareStr( info->mod, mask->tests[SLIF_MOD], mask->info.mod ) )
412 && !_ServerList_CompareStr( info->map, mask->tests[SLIF_MAP], mask->info.map ) )
415 && !_ServerList_CompareStr( info->name, mask->tests[SLIF_NAME], mask->info.name ) )
417 if( *mask->info.qcstatus
418 && !_ServerList_CompareStr( info->qcstatus, mask->tests[SLIF_QCSTATUS], mask->info.qcstatus ) )
420 if( *mask->info.players
421 && !_ServerList_CompareStr( info->players, mask->tests[SLIF_PLAYERS], mask->info.players ) )
423 if( !_ServerList_CompareInt( info->category, mask->tests[SLIF_CATEGORY], mask->info.category ) )
425 if( !_ServerList_CompareInt( info->isfavorite, mask->tests[SLIF_ISFAVORITE], mask->info.isfavorite ))
430 static void ServerList_ViewList_Insert( serverlist_entry_t *entry )
432 unsigned start, end, mid, i;
435 // reject incompatible servers
437 entry->info.gameversion != gameversion.integer
440 gameversion_min.integer >= 0 // min/max range set by user/mod?
441 && gameversion_max.integer >= 0
442 && gameversion_min.integer <= entry->info.gameversion // version of server in min/max range?
443 && gameversion_max.integer >= entry->info.gameversion
448 // also display entries that are currently being refreshed [11/8/2007 Black]
449 // bones_was_here: if their previous ping was acceptable (unset if timeout occurs)
450 if (!entry->info.ping)
453 // refresh the "favorite" status
454 entry->info.isfavorite = false;
455 if(LHNETADDRESS_FromString(&addr, entry->info.cname, 26000))
457 char idfp[FP64_SIZE+1];
458 for(i = 0; i < nFavorites; ++i)
460 if(LHNETADDRESS_Compare(&addr, &favorites[i]) == 0)
462 entry->info.isfavorite = true;
466 if(Crypto_RetrieveHostKey(&addr, 0, NULL, 0, idfp, sizeof(idfp), NULL, NULL))
468 for(i = 0; i < nFavorites_idfp; ++i)
470 if(!strcmp(idfp, favorites_idfp[i]))
472 entry->info.isfavorite = true;
479 // refresh the "category"
480 entry->info.category = MR_GetServerListEntryCategory(entry);
482 // FIXME: change this to be more readable (...)
483 // now check whether it passes through the masks
484 for( start = 0 ; start < SERVERLIST_ANDMASKCOUNT && serverlist_andmasks[start].active; start++ )
485 if( !_ServerList_Entry_Mask( &serverlist_andmasks[start], &entry->info ) )
488 for( start = 0 ; start < SERVERLIST_ORMASKCOUNT && serverlist_ormasks[start].active ; start++ )
489 if( _ServerList_Entry_Mask( &serverlist_ormasks[start], &entry->info ) )
491 if( start == SERVERLIST_ORMASKCOUNT || (start > 0 && !serverlist_ormasks[start].active) )
494 if( !serverlist_viewcount ) {
495 _ServerList_ViewList_Helper_InsertBefore( 0, entry );
498 // ok, insert it, we just need to find out where exactly:
501 // check whether to insert it as new first item
502 if( _ServerList_Entry_Compare( entry, ServerList_GetViewEntry(0) ) ) {
503 _ServerList_ViewList_Helper_InsertBefore( 0, entry );
505 } // check whether to insert it as new last item
506 else if( !_ServerList_Entry_Compare( entry, ServerList_GetViewEntry(serverlist_viewcount - 1) ) ) {
507 _ServerList_ViewList_Helper_InsertBefore( serverlist_viewcount, entry );
511 end = serverlist_viewcount - 1;
512 while( end > start + 1 )
514 mid = (start + end) / 2;
515 // test the item that lies in the middle between start and end
516 if( _ServerList_Entry_Compare( entry, ServerList_GetViewEntry(mid) ) )
517 // the item has to be in the upper half
520 // the item has to be in the lower half
523 _ServerList_ViewList_Helper_InsertBefore( start + 1, entry );
526 static void ServerList_ViewList_Remove( serverlist_entry_t *entry )
530 for( i = 0; i < serverlist_viewcount; i++ )
532 if (ServerList_GetViewEntry(i) == entry)
534 _ServerList_ViewList_Helper_Remove(i);
540 void ServerList_RebuildViewList(cvar_t *var)
544 if (net_slist_pause.integer)
547 serverlist_viewcount = 0;
548 for (i = 0; i < serverlist_cachecount; ++i)
549 ServerList_ViewList_Insert(&serverlist_cache[i]);
552 void ServerList_ResetMasks(void)
556 memset( &serverlist_andmasks, 0, sizeof( serverlist_andmasks ) );
557 memset( &serverlist_ormasks, 0, sizeof( serverlist_ormasks ) );
558 // numbots needs to be compared to -1 to always succeed
559 for(i = 0; i < SERVERLIST_ANDMASKCOUNT; ++i)
560 serverlist_andmasks[i].info.numbots = -1;
561 for(i = 0; i < SERVERLIST_ORMASKCOUNT; ++i)
562 serverlist_ormasks[i].info.numbots = -1;
565 void ServerList_GetPlayerStatistics(unsigned *numplayerspointer, unsigned *maxplayerspointer)
568 unsigned numplayers = 0, maxplayers = 0;
570 for (i = 0;i < serverlist_cachecount;i++)
572 if (serverlist_cache[i].info.ping)
574 numplayers += serverlist_cache[i].info.numhumans;
575 maxplayers += serverlist_cache[i].info.maxplayers;
578 *numplayerspointer = numplayers;
579 *maxplayerspointer = maxplayers;
583 static void _ServerList_Test(void)
586 if (serverlist_maxcachecount <= 1024)
588 serverlist_maxcachecount = 1024;
589 serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount);
591 for( i = 0 ; i < 1024 ; i++ ) {
592 memset( &serverlist_cache[serverlist_cachecount], 0, sizeof( serverlist_entry_t ) );
593 serverlist_cache[serverlist_cachecount].info.ping = 1000 + 1024 - i;
594 dpsnprintf( serverlist_cache[serverlist_cachecount].info.name, sizeof(serverlist_cache[serverlist_cachecount].info.name), "Black's ServerList Test %i", i );
595 serverlist_cache[serverlist_cachecount].finished = true;
596 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 );
597 ServerList_ViewList_Insert( &serverlist_cache[serverlist_cachecount] );
598 serverlist_cachecount++;
605 ServerList_BuildDPServerQuery
607 Generates the string for pinging DP servers with a hash-verified timestamp
608 to provide reliable pings while preventing ping cheating,
609 and discard spurious getstatus/getinfo packets.
611 14 bytes of header including the mandatory space,
612 22 bytes of base64-encoded hash,
613 up to 16 bytes of unsigned hexadecimal milliseconds (typically 4-5 bytes sent),
616 The maximum challenge length (after the space) for existing DP7 servers is 49.
619 static inline void ServerList_BuildDPServerQuery(char *buffer, size_t buffersize, double querytime)
621 unsigned char hash[24]; // 4*(16/3) rounded up to 4 byte multiple
622 uint64_t timestamp = querytime * 1000.0; // no rounding up as that could make small pings go <= 0
624 HMAC_MDFOUR_16BYTES(hash,
625 (unsigned char *)×tamp, sizeof(timestamp),
626 (unsigned char *)serverlist_dpserverquerykey, sizeof(serverlist_dpserverquerykey));
627 base64_encode(hash, 16, sizeof(hash));
628 dpsnprintf(buffer, buffersize, "\377\377\377\377getstatus %.22s%" PRIx64, hash, timestamp);
631 static void NetConn_BuildChallengeString(char *buffer, int bufferlength);
632 void ServerList_QueryList(qbool resetcache, qbool querydp, qbool queryqw, qbool consoleoutput)
635 lhnetaddress_t broadcastaddress;
636 char dpquery[53]; // theoretical max: 14+22+16+1
638 if (net_slist_debug.integer)
639 Con_Printf("^2Querying master, favourite and LAN servers, reset=%u\n", resetcache);
640 serverlist_querystage = (querydp ? SLIST_QUERYSTAGE_DPMASTERS : 0) | (queryqw ? SLIST_QUERYSTAGE_QWMASTERS : 0);
641 masterquerycount = 0;
642 masterreplycount = 0;
645 serverquerycount = 0;
646 serverreplycount = 0;
647 serverlist_cachecount = 0;
648 serverlist_viewcount = 0;
649 serverlist_maxcachecount = 0;
650 serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount);
654 // refresh all entries
655 for (i = 0; i < serverlist_cachecount; ++i)
657 serverlist_entry_t *entry = &serverlist_cache[i];
658 entry->responded = false;
661 serverlist_consoleoutput = consoleoutput;
663 //_ServerList_Test();
665 NetConn_QueryMasters(querydp, queryqw);
667 // Generate new DP server query key string
668 // Used to prevent ping cheating and discard spurious getstatus/getinfo packets
669 if (!serverlist_querystage) // don't change key while updating
670 NetConn_BuildChallengeString(serverlist_dpserverquerykey, sizeof(serverlist_dpserverquerykey));
674 // Master and and/or favourite queries were likely delayed by DNS lag,
675 // for correct pings we need to know what host.realtime would be if it were updated now.
676 masterquerytime = host.realtime + (Sys_DirtyTime() - host.dirtytime);
677 ServerList_BuildDPServerQuery(dpquery, sizeof(dpquery), masterquerytime);
679 // 26000 is the default quake server port, servers on other ports will not be found
680 // note this is IPv4-only, I doubt there are IPv6-only LANs out there
681 LHNETADDRESS_FromString(&broadcastaddress, "255.255.255.255", 26000);
683 for (i = 0; i < cl_numsockets; ++i)
687 if (LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])) != LHNETADDRESS_GetAddressType(&broadcastaddress))
692 // search LAN for Quake servers
693 SZ_Clear(&cl_message);
694 // save space for the header, filled in later
695 MSG_WriteLong(&cl_message, 0);
696 MSG_WriteByte(&cl_message, CCREQ_SERVER_INFO);
697 MSG_WriteString(&cl_message, "QUAKE");
698 MSG_WriteByte(&cl_message, NET_PROTOCOL_VERSION);
699 StoreBigLong(cl_message.data, NETFLAG_CTL | (cl_message.cursize & NETFLAG_LENGTH_MASK));
700 NetConn_Write(cl_sockets[i], cl_message.data, cl_message.cursize, &broadcastaddress);
701 SZ_Clear(&cl_message);
703 // search LAN for DarkPlaces servers
704 NetConn_WriteString(cl_sockets[i], dpquery, &broadcastaddress);
708 // search LAN for QuakeWorld servers
709 NetConn_WriteString(cl_sockets[i], "\377\377\377\377status\n", &broadcastaddress);
716 int NetConn_Read(lhnetsocket_t *mysocket, void *data, int maxlength, lhnetaddress_t *peeraddress)
721 if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex)
722 Thread_LockMutex(netconn_mutex);
723 length = LHNET_Read(mysocket, data, maxlength, peeraddress);
724 if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex)
725 Thread_UnlockMutex(netconn_mutex);
728 if (net_fakeloss_receive.integer)
729 for (i = 0;i < cl_numsockets;i++)
730 if (cl_sockets[i] == mysocket && (rand() % 100) < net_fakeloss_receive.integer)
732 if (developer_networking.integer)
734 char addressstring[128], addressstring2[128];
735 LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), addressstring, sizeof(addressstring), true);
738 LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true);
739 Con_Printf("LHNET_Read(%p (%s), %p, %i, %p) = %i from %s:\n", (void *)mysocket, addressstring, (void *)data, maxlength, (void *)peeraddress, length, addressstring2);
740 Com_HexDumpToConsole((unsigned char *)data, length);
743 Con_Printf("LHNET_Read(%p (%s), %p, %i, %p) = %i\n", (void *)mysocket, addressstring, (void *)data, maxlength, (void *)peeraddress, length);
748 int NetConn_Write(lhnetsocket_t *mysocket, const void *data, int length, const lhnetaddress_t *peeraddress)
753 if (net_fakeloss_send.integer)
754 for (i = 0;i < cl_numsockets;i++)
755 if (cl_sockets[i] == mysocket && (rand() % 100) < net_fakeloss_send.integer)
757 if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex)
758 Thread_LockMutex(netconn_mutex);
759 ret = LHNET_Write(mysocket, data, length, peeraddress);
760 if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex)
761 Thread_UnlockMutex(netconn_mutex);
762 if (developer_networking.integer)
764 char addressstring[128], addressstring2[128];
765 LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), addressstring, sizeof(addressstring), true);
766 LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true);
767 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)");
768 Com_HexDumpToConsole((unsigned char *)data, length);
773 int NetConn_WriteString(lhnetsocket_t *mysocket, const char *string, const lhnetaddress_t *peeraddress)
775 // note this does not include the trailing NULL because we add that in the parser
776 return NetConn_Write(mysocket, string, (int)strlen(string), peeraddress);
779 qbool NetConn_CanSend(netconn_t *conn)
781 conn->outgoing_packetcounter = (conn->outgoing_packetcounter + 1) % NETGRAPH_PACKETS;
782 conn->outgoing_netgraph[conn->outgoing_packetcounter].time = host.realtime;
783 conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_NOPACKET;
784 conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes = NETGRAPH_NOPACKET;
785 conn->outgoing_netgraph[conn->outgoing_packetcounter].ackbytes = NETGRAPH_NOPACKET;
786 conn->outgoing_netgraph[conn->outgoing_packetcounter].cleartime = conn->cleartime;
787 if (host.realtime > conn->cleartime)
791 conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_CHOKEDPACKET;
796 static void NetConn_UpdateCleartime(double *cleartime, int rate, int burstsize, int len)
798 double bursttime = burstsize / (double)rate;
800 // delay later packets to obey rate limit
801 if (*cleartime < host.realtime - bursttime)
802 *cleartime = host.realtime - bursttime;
803 *cleartime = *cleartime + len / (double)rate;
805 // limit bursts to one packet in size ("dialup mode" emulating old behaviour)
806 if (net_test.integer)
808 if (*cleartime < host.realtime)
809 *cleartime = host.realtime;
813 static int NetConn_AddCryptoFlag(crypto_t *crypto)
815 // HACK: if an encrypted connection is used, randomly set some unused
816 // flags. When AES encryption is enabled, that will make resends differ
817 // from the original, so that e.g. substring filters in a router/IPS
818 // are unlikely to match a second time. See also "startkeylogger".
820 if (crypto->authenticated)
822 // Let's always set at least one of the bits.
823 int r = rand() % 7 + 1;
825 flag |= NETFLAG_CRYPTO0;
827 flag |= NETFLAG_CRYPTO1;
829 flag |= NETFLAG_CRYPTO2;
834 int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol, int rate, int burstsize, qbool quakesignon_suppressreliables)
837 unsigned char sendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE];
838 unsigned char cryptosendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE];
840 // if this packet was supposedly choked, but we find ourselves sending one
841 // anyway, make sure the size counting starts at zero
842 // (this mostly happens on level changes and disconnects and such)
843 if (conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes == NETGRAPH_CHOKEDPACKET)
844 conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_NOPACKET;
846 conn->outgoing_netgraph[conn->outgoing_packetcounter].cleartime = conn->cleartime;
848 if (protocol == PROTOCOL_QUAKEWORLD)
853 // note that it is ok to send empty messages to the qw server,
854 // otherwise it won't respond to us at all
856 sendreliable = false;
857 // if the remote side dropped the last reliable message, resend it
858 if (conn->qw.incoming_acknowledged > conn->qw.last_reliable_sequence && conn->qw.incoming_reliable_acknowledged != conn->qw.reliable_sequence)
860 // if the reliable transmit buffer is empty, copy the current message out
861 if (!conn->sendMessageLength && conn->message.cursize)
863 memcpy (conn->sendMessage, conn->message.data, conn->message.cursize);
864 conn->sendMessageLength = conn->message.cursize;
865 SZ_Clear(&conn->message); // clear the message buffer
866 conn->qw.reliable_sequence ^= 1;
869 // outgoing unreliable packet number, and outgoing reliable packet number (0 or 1)
870 StoreLittleLong(sendbuffer, conn->outgoing_unreliable_sequence | (((unsigned int)sendreliable)<<31));
871 // last received unreliable packet number, and last received reliable packet number (0 or 1)
872 StoreLittleLong(sendbuffer + 4, conn->qw.incoming_sequence | (((unsigned int)conn->qw.incoming_reliable_sequence)<<31));
874 conn->outgoing_unreliable_sequence++;
875 // client sends qport in every packet
876 if (conn == cls.netcon)
878 *((short *)(sendbuffer + 8)) = LittleShort(cls.qw_qport);
880 // also update cls.qw_outgoing_sequence
881 cls.qw_outgoing_sequence = conn->outgoing_unreliable_sequence;
883 if (packetLen + (sendreliable ? conn->sendMessageLength : 0) > 1400)
885 Con_Printf ("NetConn_SendUnreliableMessage: reliable message too big %u\n", data->cursize);
889 conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += packetLen + 28;
891 // add the reliable message if there is one
894 conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += conn->sendMessageLength + 28;
895 memcpy(sendbuffer + packetLen, conn->sendMessage, conn->sendMessageLength);
896 packetLen += conn->sendMessageLength;
897 conn->qw.last_reliable_sequence = conn->outgoing_unreliable_sequence;
900 // add the unreliable message if possible
901 if (packetLen + data->cursize <= 1400)
903 conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += data->cursize + 28;
904 memcpy(sendbuffer + packetLen, data->data, data->cursize);
905 packetLen += data->cursize;
908 NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress);
911 conn->unreliableMessagesSent++;
913 totallen += packetLen + 28;
917 unsigned int packetLen;
918 unsigned int dataLen;
923 // if a reliable message fragment has been lost, send it again
924 if (conn->sendMessageLength && (host.realtime - conn->lastSendTime) > 1.0)
926 if (conn->sendMessageLength <= MAX_PACKETFRAGMENT)
928 dataLen = conn->sendMessageLength;
933 dataLen = MAX_PACKETFRAGMENT;
937 packetLen = NET_HEADERSIZE + dataLen;
939 StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom | NetConn_AddCryptoFlag(&conn->crypto)));
940 StoreBigLong(sendbuffer + 4, conn->nq.sendSequence - 1);
941 memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen);
943 conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += packetLen + 28;
945 sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer));
946 if (sendme && NetConn_Write(conn->mysocket, sendme, (int)sendmelen, &conn->peeraddress) == (int)sendmelen)
948 conn->lastSendTime = host.realtime;
949 conn->packetsReSent++;
952 totallen += (int)sendmelen + 28;
955 // if we have a new reliable message to send, do so
956 if (!conn->sendMessageLength && conn->message.cursize && !quakesignon_suppressreliables)
958 if (conn->message.cursize > (int)sizeof(conn->sendMessage))
960 Con_Printf("NetConn_SendUnreliableMessage: reliable message too big (%u > %u)\n", conn->message.cursize, (int)sizeof(conn->sendMessage));
961 conn->message.overflowed = true;
965 if (developer_networking.integer && conn == cls.netcon)
967 Con_Print("client sending reliable message to server:\n");
968 SZ_HexDumpToConsole(&conn->message);
971 memcpy(conn->sendMessage, conn->message.data, conn->message.cursize);
972 conn->sendMessageLength = conn->message.cursize;
973 SZ_Clear(&conn->message);
975 if (conn->sendMessageLength <= MAX_PACKETFRAGMENT)
977 dataLen = conn->sendMessageLength;
982 dataLen = MAX_PACKETFRAGMENT;
986 packetLen = NET_HEADERSIZE + dataLen;
988 StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom | NetConn_AddCryptoFlag(&conn->crypto)));
989 StoreBigLong(sendbuffer + 4, conn->nq.sendSequence);
990 memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen);
992 conn->nq.sendSequence++;
994 conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += packetLen + 28;
996 sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer));
998 NetConn_Write(conn->mysocket, sendme, (int)sendmelen, &conn->peeraddress);
1000 conn->lastSendTime = host.realtime;
1001 conn->packetsSent++;
1002 conn->reliableMessagesSent++;
1004 totallen += (int)sendmelen + 28;
1007 // if we have an unreliable message to send, do so
1010 packetLen = NET_HEADERSIZE + data->cursize;
1012 if (packetLen > (int)sizeof(sendbuffer))
1014 Con_Printf("NetConn_SendUnreliableMessage: message too big %u\n", data->cursize);
1018 StoreBigLong(sendbuffer, packetLen | NETFLAG_UNRELIABLE | NetConn_AddCryptoFlag(&conn->crypto));
1019 StoreBigLong(sendbuffer + 4, conn->outgoing_unreliable_sequence);
1020 memcpy(sendbuffer + NET_HEADERSIZE, data->data, data->cursize);
1022 conn->outgoing_unreliable_sequence++;
1024 conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += packetLen + 28;
1026 sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer));
1028 NetConn_Write(conn->mysocket, sendme, (int)sendmelen, &conn->peeraddress);
1030 conn->packetsSent++;
1031 conn->unreliableMessagesSent++;
1033 totallen += (int)sendmelen + 28;
1037 NetConn_UpdateCleartime(&conn->cleartime, rate, burstsize, totallen);
1042 qbool NetConn_HaveClientPorts(void)
1044 return !!cl_numsockets;
1047 qbool NetConn_HaveServerPorts(void)
1049 return !!sv_numsockets;
1052 void NetConn_CloseClientPorts(void)
1054 for (;cl_numsockets > 0;cl_numsockets--)
1055 if (cl_sockets[cl_numsockets - 1])
1056 LHNET_CloseSocket(cl_sockets[cl_numsockets - 1]);
1059 static void NetConn_OpenClientPort(const char *addressstring, lhnetaddresstype_t addresstype, int defaultport)
1061 lhnetaddress_t address;
1064 char addressstring2[1024];
1065 if (addressstring && addressstring[0])
1066 success = LHNETADDRESS_FromString(&address, addressstring, defaultport);
1068 success = LHNETADDRESS_FromPort(&address, addresstype, defaultport);
1071 if ((s = LHNET_OpenSocket_Connectionless(&address)))
1073 cl_sockets[cl_numsockets++] = s;
1074 LHNETADDRESS_ToString(LHNET_AddressFromSocket(s), addressstring2, sizeof(addressstring2), true);
1075 if (addresstype != LHNETADDRESSTYPE_LOOP)
1076 Con_Printf("Client opened a socket on address %s\n", addressstring2);
1080 LHNETADDRESS_ToString(&address, addressstring2, sizeof(addressstring2), true);
1081 Con_Printf(CON_ERROR "Client failed to open a socket on address %s\n", addressstring2);
1085 Con_Printf(CON_ERROR "Client unable to parse address %s\n", addressstring);
1088 void NetConn_OpenClientPorts(void)
1091 NetConn_CloseClientPorts();
1093 SV_LockThreadMutex(); // FIXME recursive?
1094 Crypto_LoadKeys(); // client sockets
1095 SV_UnlockThreadMutex();
1097 port = bound(0, cl_netport.integer, 65535);
1098 if (cl_netport.integer != port)
1099 Cvar_SetValueQuick(&cl_netport, port);
1101 Con_Printf("Client using an automatically assigned port\n");
1103 Con_Printf("Client using port %i\n", port);
1104 NetConn_OpenClientPort(NULL, LHNETADDRESSTYPE_LOOP, 2);
1105 NetConn_OpenClientPort(net_address.string, LHNETADDRESSTYPE_INET4, port);
1106 #ifndef NOSUPPORTIPV6
1107 NetConn_OpenClientPort(net_address_ipv6.string, LHNETADDRESSTYPE_INET6, port);
1111 void NetConn_CloseServerPorts(void)
1113 for (;sv_numsockets > 0;sv_numsockets--)
1114 if (sv_sockets[sv_numsockets - 1])
1115 LHNET_CloseSocket(sv_sockets[sv_numsockets - 1]);
1118 static qbool NetConn_OpenServerPort(const char *addressstring, lhnetaddresstype_t addresstype, int defaultport, int range)
1120 lhnetaddress_t address;
1123 char addressstring2[1024];
1126 for (port = defaultport; port <= defaultport + range; port++)
1128 if (addressstring && addressstring[0])
1129 success = LHNETADDRESS_FromString(&address, addressstring, port);
1131 success = LHNETADDRESS_FromPort(&address, addresstype, port);
1134 if ((s = LHNET_OpenSocket_Connectionless(&address)))
1136 sv_sockets[sv_numsockets++] = s;
1137 LHNETADDRESS_ToString(LHNET_AddressFromSocket(s), addressstring2, sizeof(addressstring2), true);
1138 if (addresstype != LHNETADDRESSTYPE_LOOP)
1139 Con_Printf("Server listening on address %s\n", addressstring2);
1144 LHNETADDRESS_ToString(&address, addressstring2, sizeof(addressstring2), true);
1145 Con_Printf(CON_ERROR "Server failed to open socket on address %s\n", addressstring2);
1150 Con_Printf(CON_ERROR "Server unable to parse address %s\n", addressstring);
1151 // if it cant parse one address, it wont be able to parse another for sure
1158 void NetConn_OpenServerPorts(int opennetports)
1161 NetConn_CloseServerPorts();
1163 SV_LockThreadMutex(); // FIXME recursive?
1164 Crypto_LoadKeys(); // server sockets
1165 SV_UnlockThreadMutex();
1167 NetConn_UpdateSockets();
1168 port = bound(0, sv_netport.integer, 65535);
1171 if (sv_netport.integer != port)
1172 Cvar_SetValueQuick(&sv_netport, port);
1173 if (cls.state != ca_dedicated)
1174 NetConn_OpenServerPort(NULL, LHNETADDRESSTYPE_LOOP, 1, 1);
1177 #ifndef NOSUPPORTIPV6
1178 qbool ip4success = NetConn_OpenServerPort(net_address.string, LHNETADDRESSTYPE_INET4, port, 100);
1179 NetConn_OpenServerPort(net_address_ipv6.string, LHNETADDRESSTYPE_INET6, port, ip4success ? 1 : 100);
1181 NetConn_OpenServerPort(net_address.string, LHNETADDRESSTYPE_INET4, port, 100);
1184 if (sv_numsockets == 0)
1185 Host_Error("NetConn_OpenServerPorts: unable to open any ports!");
1188 lhnetsocket_t *NetConn_ChooseClientSocketForAddress(lhnetaddress_t *address)
1191 lhnetaddresstype_t a = LHNETADDRESS_GetAddressType(address);
1193 for (i = 0;i < cl_numsockets;i++)
1194 if (cl_sockets[i] && LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])) == a)
1195 return cl_sockets[i];
1199 lhnetsocket_t *NetConn_ChooseServerSocketForAddress(lhnetaddress_t *address)
1202 lhnetaddresstype_t a = LHNETADDRESS_GetAddressType(address);
1204 for (i = 0;i < sv_numsockets;i++)
1205 if (sv_sockets[i] && LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(sv_sockets[i])) == a)
1206 return sv_sockets[i];
1210 netconn_t *NetConn_Open(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress)
1213 conn = (netconn_t *)Mem_Alloc(netconn_mempool, sizeof(*conn));
1214 conn->mysocket = mysocket;
1215 conn->peeraddress = *peeraddress;
1216 conn->lastMessageTime = host.realtime;
1217 conn->message.data = conn->messagedata;
1218 conn->message.maxsize = sizeof(conn->messagedata);
1219 conn->message.cursize = 0;
1220 // LadyHavoc: (inspired by ProQuake) use a short connect timeout to
1221 // reduce effectiveness of connection request floods
1222 conn->timeout = host.realtime + net_connecttimeout.value;
1223 LHNETADDRESS_ToString(&conn->peeraddress, conn->address, sizeof(conn->address), true);
1224 conn->next = netconn_list;
1225 netconn_list = conn;
1229 void NetConn_ClearFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength);
1230 void NetConn_Close(netconn_t *conn)
1233 // remove connection from list
1235 // allow the client to reconnect immediately
1236 NetConn_ClearFlood(&(conn->peeraddress), sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0]));
1238 if (conn == netconn_list)
1239 netconn_list = conn->next;
1242 for (c = netconn_list;c;c = c->next)
1244 if (c->next == conn)
1246 c->next = conn->next;
1250 // not found in list, we'll avoid crashing here...
1258 static int clientport = -1;
1259 static int clientport2 = -1;
1261 // Call on disconnect, during startup, or if cl_port/cl_netport is changed
1262 static void NetConn_CL_UpdateSockets_Callback(cvar_t *var)
1264 if(cls.state != ca_dedicated)
1266 if (clientport2 != var->integer)
1268 clientport2 = var->integer;
1269 if (cls.state == ca_connected)
1270 Con_Print("Changing \"cl_port\" will not take effect until you reconnect.\n");
1273 if (cls.state == ca_disconnected && clientport != clientport2)
1275 clientport = clientport2;
1276 NetConn_CloseClientPorts();
1278 if (cl_numsockets == 0)
1279 NetConn_OpenClientPorts();
1283 static int hostport = -1;
1285 // Call when port/sv_netport is changed
1286 static void NetConn_sv_netport_Callback(cvar_t *var)
1288 if (hostport != var->integer)
1290 hostport = var->integer;
1292 Con_Print("Changing \"port\" will not take effect until \"map\" command is executed.\n");
1296 void NetConn_UpdateSockets(void)
1300 // TODO add logic to automatically close sockets if needed
1301 LHNET_DefaultDSCP(net_tos_dscp.integer);
1303 for (j = 0;j < MAX_RCONS;j++)
1305 i = (cls.rcon_ringpos + j + 1) % MAX_RCONS;
1306 if(cls.rcon_commands[i][0])
1308 if(host.realtime > cls.rcon_timeout[i])
1311 LHNETADDRESS_ToString(&cls.rcon_addresses[i], s, sizeof(s), true);
1312 Con_Printf("rcon to %s (for command %s) failed: challenge request timed out\n", s, cls.rcon_commands[i]);
1313 cls.rcon_commands[i][0] = 0;
1321 static int NetConn_ReceivedMessage(netconn_t *conn, const unsigned char *data, size_t length, protocolversion_t protocol, double newtimeout)
1323 int originallength = (int)length;
1324 unsigned char sendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE];
1325 unsigned char cryptosendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE];
1326 unsigned char cryptoreadbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE];
1330 if (protocol == PROTOCOL_QUAKEWORLD)
1332 unsigned int sequence, sequence_ack;
1333 qbool reliable_ack, reliable_message;
1337 sequence = LittleLong(*((int *)(data + 0)));
1338 sequence_ack = LittleLong(*((int *)(data + 4)));
1342 if (conn != cls.netcon)
1347 // 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?)
1348 //qport = LittleShort(*((int *)(data + 8)));
1353 conn->packetsReceived++;
1354 reliable_message = (sequence >> 31) != 0;
1355 reliable_ack = (sequence_ack >> 31) != 0;
1356 sequence &= ~(1<<31);
1357 sequence_ack &= ~(1<<31);
1358 if (sequence <= conn->qw.incoming_sequence)
1360 //Con_DPrint("Got a stale datagram\n");
1363 count = sequence - (conn->qw.incoming_sequence + 1);
1366 conn->droppedDatagrams += count;
1367 //Con_DPrintf("Dropped %u datagram(s)\n", count);
1368 // If too may packets have been dropped, only write the
1369 // last NETGRAPH_PACKETS ones to the netgraph. Why?
1370 // Because there's no point in writing more than
1371 // these as the netgraph is going to be full anyway.
1372 if (count > NETGRAPH_PACKETS)
1373 count = NETGRAPH_PACKETS;
1376 conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS;
1377 conn->incoming_netgraph[conn->incoming_packetcounter].time = host.realtime;
1378 conn->incoming_netgraph[conn->incoming_packetcounter].cleartime = conn->incoming_cleartime;
1379 conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = NETGRAPH_LOSTPACKET;
1380 conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET;
1381 conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET;
1384 conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS;
1385 conn->incoming_netgraph[conn->incoming_packetcounter].time = host.realtime;
1386 conn->incoming_netgraph[conn->incoming_packetcounter].cleartime = conn->incoming_cleartime;
1387 conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = originallength + 28;
1388 conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET;
1389 conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET;
1390 NetConn_UpdateCleartime(&conn->incoming_cleartime, cl_rate.integer, cl_rate_burstsize.integer, originallength + 28);
1392 // limit bursts to one packet in size ("dialup mode" emulating old behaviour)
1393 if (net_test.integer)
1395 if (conn->cleartime < host.realtime)
1396 conn->cleartime = host.realtime;
1399 if (reliable_ack == conn->qw.reliable_sequence)
1401 // received, now we will be able to send another reliable message
1402 conn->sendMessageLength = 0;
1403 conn->reliableMessagesReceived++;
1405 conn->qw.incoming_sequence = sequence;
1406 if (conn == cls.netcon)
1407 cls.qw_incoming_sequence = conn->qw.incoming_sequence;
1408 conn->qw.incoming_acknowledged = sequence_ack;
1409 conn->qw.incoming_reliable_acknowledged = reliable_ack;
1410 if (reliable_message)
1411 conn->qw.incoming_reliable_sequence ^= 1;
1412 conn->lastMessageTime = host.realtime;
1413 conn->timeout = host.realtime + newtimeout;
1414 conn->unreliableMessagesReceived++;
1415 if (conn == cls.netcon)
1417 SZ_Clear(&cl_message);
1418 SZ_Write(&cl_message, data, (int)length);
1419 MSG_BeginReading(&cl_message);
1423 SZ_Clear(&sv_message);
1424 SZ_Write(&sv_message, data, (int)length);
1425 MSG_BeginReading(&sv_message);
1433 unsigned int sequence;
1438 originallength = (int)length;
1439 data = (const unsigned char *) Crypto_DecryptPacket(&conn->crypto, data, length, cryptoreadbuffer, &length, sizeof(cryptoreadbuffer));
1445 qlength = (unsigned int)BuffBigLong(data);
1446 flags = qlength & ~NETFLAG_LENGTH_MASK;
1447 qlength &= NETFLAG_LENGTH_MASK;
1448 // control packets were already handled
1449 if (!(flags & NETFLAG_CTL) && qlength == length)
1451 sequence = BuffBigLong(data + 4);
1452 conn->packetsReceived++;
1455 if (flags & NETFLAG_UNRELIABLE)
1457 if (sequence >= conn->nq.unreliableReceiveSequence)
1459 if (sequence > conn->nq.unreliableReceiveSequence)
1461 count = sequence - conn->nq.unreliableReceiveSequence;
1462 conn->droppedDatagrams += count;
1463 //Con_DPrintf("Dropped %u datagram(s)\n", count);
1464 // If too may packets have been dropped, only write the
1465 // last NETGRAPH_PACKETS ones to the netgraph. Why?
1466 // Because there's no point in writing more than
1467 // these as the netgraph is going to be full anyway.
1468 if (count > NETGRAPH_PACKETS)
1469 count = NETGRAPH_PACKETS;
1472 conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS;
1473 conn->incoming_netgraph[conn->incoming_packetcounter].time = host.realtime;
1474 conn->incoming_netgraph[conn->incoming_packetcounter].cleartime = conn->incoming_cleartime;
1475 conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = NETGRAPH_LOSTPACKET;
1476 conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET;
1477 conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET;
1480 conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS;
1481 conn->incoming_netgraph[conn->incoming_packetcounter].time = host.realtime;
1482 conn->incoming_netgraph[conn->incoming_packetcounter].cleartime = conn->incoming_cleartime;
1483 conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = originallength + 28;
1484 conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET;
1485 conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET;
1486 NetConn_UpdateCleartime(&conn->incoming_cleartime, cl_rate.integer, cl_rate_burstsize.integer, originallength + 28);
1488 conn->nq.unreliableReceiveSequence = sequence + 1;
1489 conn->lastMessageTime = host.realtime;
1490 conn->timeout = host.realtime + newtimeout;
1491 conn->unreliableMessagesReceived++;
1494 if (conn == cls.netcon)
1496 SZ_Clear(&cl_message);
1497 SZ_Write(&cl_message, data, (int)length);
1498 MSG_BeginReading(&cl_message);
1502 SZ_Clear(&sv_message);
1503 SZ_Write(&sv_message, data, (int)length);
1504 MSG_BeginReading(&sv_message);
1510 // Con_DPrint("Got a stale datagram\n");
1513 else if (flags & NETFLAG_ACK)
1515 conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes += originallength + 28;
1516 NetConn_UpdateCleartime(&conn->incoming_cleartime, cl_rate.integer, cl_rate_burstsize.integer, originallength + 28);
1518 if (sequence == (conn->nq.sendSequence - 1))
1520 if (sequence == conn->nq.ackSequence)
1522 conn->nq.ackSequence++;
1523 if (conn->nq.ackSequence != conn->nq.sendSequence)
1524 Con_DPrint("ack sequencing error\n");
1525 conn->lastMessageTime = host.realtime;
1526 conn->timeout = host.realtime + newtimeout;
1527 if (conn->sendMessageLength > MAX_PACKETFRAGMENT)
1529 unsigned int packetLen;
1530 unsigned int dataLen;
1533 conn->sendMessageLength -= MAX_PACKETFRAGMENT;
1534 memmove(conn->sendMessage, conn->sendMessage+MAX_PACKETFRAGMENT, conn->sendMessageLength);
1536 if (conn->sendMessageLength <= MAX_PACKETFRAGMENT)
1538 dataLen = conn->sendMessageLength;
1543 dataLen = MAX_PACKETFRAGMENT;
1547 packetLen = NET_HEADERSIZE + dataLen;
1549 StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom | NetConn_AddCryptoFlag(&conn->crypto)));
1550 StoreBigLong(sendbuffer + 4, conn->nq.sendSequence);
1551 memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen);
1553 conn->nq.sendSequence++;
1555 sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer));
1556 if (sendme && NetConn_Write(conn->mysocket, sendme, (int)sendmelen, &conn->peeraddress) == (int)sendmelen)
1558 conn->lastSendTime = host.realtime;
1559 conn->packetsSent++;
1563 conn->sendMessageLength = 0;
1566 // Con_DPrint("Duplicate ACK received\n");
1569 // Con_DPrint("Stale ACK received\n");
1572 else if (flags & NETFLAG_DATA)
1574 unsigned char temppacket[8];
1575 conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes += originallength + 28;
1576 NetConn_UpdateCleartime(&conn->incoming_cleartime, cl_rate.integer, cl_rate_burstsize.integer, originallength + 28);
1578 conn->outgoing_netgraph[conn->outgoing_packetcounter].ackbytes += 8 + 28;
1580 StoreBigLong(temppacket, 8 | NETFLAG_ACK | NetConn_AddCryptoFlag(&conn->crypto));
1581 StoreBigLong(temppacket + 4, sequence);
1582 sendme = Crypto_EncryptPacket(&conn->crypto, temppacket, 8, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer));
1584 NetConn_Write(conn->mysocket, sendme, (int)sendmelen, &conn->peeraddress);
1585 if (sequence == conn->nq.receiveSequence)
1587 conn->lastMessageTime = host.realtime;
1588 conn->timeout = host.realtime + newtimeout;
1589 conn->nq.receiveSequence++;
1590 if( conn->receiveMessageLength + length <= (int)sizeof( conn->receiveMessage ) ) {
1591 memcpy(conn->receiveMessage + conn->receiveMessageLength, data, length);
1592 conn->receiveMessageLength += (int)length;
1594 Con_Printf( "Reliable message (seq: %i) too big for message buffer!\n"
1595 "Dropping the message!\n", sequence );
1596 conn->receiveMessageLength = 0;
1599 if (flags & NETFLAG_EOM)
1601 conn->reliableMessagesReceived++;
1602 length = conn->receiveMessageLength;
1603 conn->receiveMessageLength = 0;
1606 if (conn == cls.netcon)
1608 SZ_Clear(&cl_message);
1609 SZ_Write(&cl_message, conn->receiveMessage, (int)length);
1610 MSG_BeginReading(&cl_message);
1614 SZ_Clear(&sv_message);
1615 SZ_Write(&sv_message, conn->receiveMessage, (int)length);
1616 MSG_BeginReading(&sv_message);
1623 conn->receivedDuplicateCount++;
1631 static void NetConn_ConnectionEstablished(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, protocolversion_t initialprotocol)
1635 cls.connect_trying = false;
1636 // Disconnect from the current server or stop demo playback
1637 if(cls.state == ca_connected || cls.demoplayback)
1639 // allocate a net connection to keep track of things
1640 cls.netcon = NetConn_Open(mysocket, peeraddress);
1641 dp_strlcpy(cl_connect_status, "Connection established", sizeof(cl_connect_status));
1642 crypto = &cls.netcon->crypto;
1643 if(cls.crypto.authenticated)
1645 Crypto_FinishInstance(crypto, &cls.crypto);
1646 Con_Printf("%s connection to %s has been established: server is %s@%s%.*s, I am %.*s@%s%.*s\n",
1647 crypto->use_aes ? "Encrypted" : "Authenticated",
1648 cls.netcon->address,
1649 crypto->server_idfp[0] ? crypto->server_idfp : "-",
1650 (crypto->server_issigned || !crypto->server_keyfp[0]) ? "" : "~",
1651 crypto_keyfp_recommended_length, crypto->server_keyfp[0] ? crypto->server_keyfp : "-",
1652 crypto_keyfp_recommended_length, crypto->client_idfp[0] ? crypto->client_idfp : "-",
1653 (crypto->client_issigned || !crypto->client_keyfp[0]) ? "" : "~",
1654 crypto_keyfp_recommended_length, crypto->client_keyfp[0] ? crypto->client_keyfp : "-"
1658 Con_Printf("%s to %s\n", cl_connect_status, cls.netcon->address);
1660 key_dest = key_game;
1664 cls.demonum = -1; // not in the demo loop now
1665 cls.state = ca_connected;
1666 cls.signon = 0; // need all the signon messages before playing
1667 cls.protocol = initialprotocol;
1668 // reset move sequence numbering on this new connection
1669 cls.servermovesequence = 0;
1670 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1671 CL_ForwardToServer("new");
1672 if (cls.protocol == PROTOCOL_QUAKE)
1674 // write a keepalive (clc_nop) as it seems to greatly improve the
1675 // chances of connecting to a netquake server
1677 unsigned char buf[4];
1678 memset(&msg, 0, sizeof(msg));
1680 msg.maxsize = sizeof(buf);
1681 MSG_WriteChar(&msg, clc_nop);
1682 NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol, 10000, 0, false);
1686 int NetConn_IsLocalGame(void)
1688 if (cls.state == ca_connected && sv.active && cl.maxclients == 1)
1694 static qbool hmac_mdfour_time_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen);
1695 static int NetConn_ClientParsePacket_ServerList_ProcessReply(const char *addressstring, const char *challenge)
1699 double currentrealtime;
1700 serverlist_entry_t *entry;
1702 // search the cache for this server and update it
1703 for (n = 0; n < serverlist_cachecount; ++n)
1705 entry = &serverlist_cache[n];
1706 if (!strcmp(addressstring, entry->info.cname))
1710 if (n == serverlist_cachecount)
1712 if (net_slist_debug.integer)
1713 Con_Printf("^6Received LAN broadcast response from %s\n", addressstring);
1716 if (serverlist_cachecount == SERVERLIST_TOTALSIZE)
1719 if (serverlist_maxcachecount <= serverlist_cachecount)
1721 serverlist_maxcachecount += 64;
1722 serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount);
1724 ++serverlist_cachecount;
1725 entry = &serverlist_cache[n];
1727 memset(entry, 0, sizeof(*entry));
1728 entry->info.cname_len = dp_strlcpy(entry->info.cname, addressstring, sizeof(entry->info.cname));
1730 // use the broadcast as the first query, NetConn_QueryQueueFrame() will send more
1731 entry->querytime = masterquerytime;
1732 // protocol is one of these at all
1733 // NetConn_ClientParsePacket_ServerList_PrepareQuery() callsites
1734 entry->protocol = challenge ? PROTOCOL_DARKPLACES7 : PROTOCOL_QUAKEWORLD;
1736 if (serverlist_consoleoutput)
1737 Con_Printf("querying %s\n", addressstring);
1740 // If the client stalls partway through a frame (test command: `alias a a;a`)
1741 // for correct pings we need to know what host.realtime would be if it were updated now.
1742 currentrealtime = host.realtime + (Sys_DirtyTime() - host.dirtytime);
1746 unsigned char hash[24]; // 4*(16/3) rounded up to 4 byte multiple
1747 uint64_t timestamp = strtoull(&challenge[22], NULL, 16);
1749 HMAC_MDFOUR_16BYTES(hash,
1750 (unsigned char *)×tamp, sizeof(timestamp),
1751 (unsigned char *)serverlist_dpserverquerykey, sizeof(serverlist_dpserverquerykey));
1752 base64_encode(hash, 16, sizeof(hash));
1753 if (memcmp(hash, challenge, 22) != 0)
1756 ping = currentrealtime * 1000.0 - timestamp;
1759 ping = 1000 * (currentrealtime - entry->querytime);
1761 if (ping <= 0 || ping > net_slist_maxping.value
1762 || (entry->info.ping && ping > entry->info.ping + 100)) // server loading map, client stall, etc
1765 // never round down to 0, 0 latency is impossible, 0 means no data available
1769 if (entry->info.ping)
1770 entry->info.ping = (entry->info.ping + ping) * 0.5 + 0.5; // "average" biased toward most recent results
1773 entry->info.ping = ping + 0.5;
1776 entry->responded = true;
1778 // other server info is updated by the caller
1782 static void NetConn_ClientParsePacket_ServerList_UpdateCache(int n)
1784 serverlist_entry_t *entry = &serverlist_cache[n];
1785 serverlist_info_t *info = &entry->info;
1787 // update description strings for engine menu and console output
1788 entry->line1_len = dpsnprintf(entry->line1, sizeof(serverlist_cache[n].line1), "^%c%5.0f^7 ^%c%3u^7/%3u %-65.65s",
1789 info->ping >= 300 ? '1' : (info->ping >= 200 ? '3' : '7'),
1790 info->ping ?: INFINITY, // display inf when a listed server times out and net_slist_pause blocks its removal
1791 ((info->numhumans > 0 && info->numhumans < info->maxplayers) ? (info->numhumans >= 4 ? '7' : '3') : '1'),
1795 entry->line2_len = dpsnprintf(entry->line2, sizeof(serverlist_cache[n].line2), "^4%-21.21s %-19.19s ^%c%-17.17s^4 %-20.20s", info->cname, info->game,
1797 info->gameversion != gameversion.integer
1800 gameversion_min.integer >= 0 // min/max range set by user/mod?
1801 && gameversion_max.integer >= 0
1802 && gameversion_min.integer <= info->gameversion // version of server in min/max range?
1803 && gameversion_max.integer >= info->gameversion
1806 info->mod, info->map);
1808 if(!net_slist_pause.integer)
1810 ServerList_ViewList_Remove(entry);
1811 ServerList_ViewList_Insert(entry);
1814 if (serverlist_consoleoutput)
1815 Con_Printf("%s\n%s\n", entry->line1, entry->line2);
1818 // returns true, if it's sensible to continue the processing
1819 static qbool NetConn_ClientParsePacket_ServerList_PrepareQuery(int protocol, const char *ipstring, qbool isfavorite)
1822 serverlist_entry_t *entry;
1824 // ignore the rest of the message if the serverlist is full
1825 if (serverlist_cachecount == SERVERLIST_TOTALSIZE)
1828 for (n = 0; n < serverlist_cachecount; ++n)
1829 if (!strcmp(ipstring, serverlist_cache[n].info.cname))
1832 // also ignore it if we have already queried it (other master server response)
1833 if (n < serverlist_cachecount)
1836 if (serverlist_maxcachecount <= n)
1838 serverlist_maxcachecount += 64;
1839 serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount);
1842 entry = &serverlist_cache[n];
1843 memset(entry, 0, sizeof(*entry));
1844 entry->protocol = protocol;
1845 entry->info.cname_len = dp_strlcpy(entry->info.cname, ipstring, sizeof(entry->info.cname));
1846 entry->info.isfavorite = isfavorite;
1848 serverlist_cachecount++;
1854 static void NetConn_ClientParsePacket_ServerList_ParseDPList(lhnetaddress_t *masteraddress, const char *masteraddressstring, const unsigned char *data, int length, qbool isextended)
1857 lhnetaddress_t testaddress;
1859 for (masternum = 0; masternum < DPMASTER_COUNT; ++masternum)
1860 if (sv_masters[masternum].string[0]
1861 && LHNETADDRESS_FromString(&testaddress, sv_masters[masternum].string, DPMASTER_PORT)
1862 && LHNETADDRESS_Compare(&testaddress, masteraddress) == 0)
1864 if (net_sourceaddresscheck.integer && masternum >= DPMASTER_COUNT)
1866 Con_Printf(CON_WARN "ignoring DarkPlaces %sserver list from unrecognised master %s\n", isextended ? "extended " : "", masteraddressstring);
1871 dpmasterstatus[masternum] = MASTER_RX_RESPONSE;
1872 if (serverlist_consoleoutput || net_slist_debug.integer)
1873 Con_Printf("^5Received DarkPlaces server list %sfrom %s\n", isextended ? "(extended) " : "", sv_masters[masternum].string);
1877 char ipstring [128];
1880 if (data[0] == '\\')
1882 unsigned short port = data[5] * 256 + data[6];
1884 if (port != 0 && (data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF || data[4] != 0xFF))
1885 dpsnprintf (ipstring, sizeof (ipstring), "%u.%u.%u.%u:%hu", data[1], data[2], data[3], data[4], port);
1886 else if (port == 0 && data[1] == 'E' && data[2] == 'O' && data[3] == 'T' && data[4] == '\0')
1888 dpmasterstatus[masternum] = MASTER_RX_COMPLETE;
1889 if (net_slist_debug.integer)
1890 Con_Printf("^4End Of Transmission %sfrom %s\n", isextended ? "(extended) " : "", sv_masters[masternum].string);
1894 // move on to next address in packet
1899 else if (data[0] == '/' && isextended && length >= 19)
1901 unsigned short port = data[17] * 256 + data[18];
1909 /// \TODO: make some basic checks of the IP address (broadcast, ...)
1911 ifname = LHNETADDRESS_GetInterfaceName(senderaddress, ifnamebuf, sizeof(ifnamebuf));
1914 dpsnprintf (ipstring, sizeof (ipstring), "[%x:%x:%x:%x:%x:%x:%x:%x%%%s]:%hu",
1915 (data[1] << 8) | data[2], (data[3] << 8) | data[4], (data[5] << 8) | data[6], (data[7] << 8) | data[8],
1916 (data[9] << 8) | data[10], (data[11] << 8) | data[12], (data[13] << 8) | data[14], (data[15] << 8) | data[16],
1922 dpsnprintf (ipstring, sizeof (ipstring), "[%x:%x:%x:%x:%x:%x:%x:%x]:%hu",
1923 (data[1] << 8) | data[2], (data[3] << 8) | data[4], (data[5] << 8) | data[6], (data[7] << 8) | data[8],
1924 (data[9] << 8) | data[10], (data[11] << 8) | data[12], (data[13] << 8) | data[14], (data[15] << 8) | data[16],
1929 // move on to next address in packet
1935 Con_Print(CON_WARN "Error while parsing the server list\n");
1939 if (serverlist_consoleoutput && developer_networking.integer)
1940 Con_Printf("Requesting info from DarkPlaces server %s\n", ipstring);
1942 if (!NetConn_ClientParsePacket_ServerList_PrepareQuery(PROTOCOL_DARKPLACES7, ipstring, false))
1946 if (serverlist_querystage & SLIST_QUERYSTAGE_QWMASTERS)
1947 return; // we must wait if we're also querying QW as it has no EOT marker
1948 // begin or resume serverlist queries
1949 for (masternum = 0; masternum < DPMASTER_COUNT; ++masternum)
1950 if (dpmasterstatus[masternum] && dpmasterstatus[masternum] < MASTER_RX_COMPLETE)
1951 break; // was queried but no EOT marker received yet
1952 if (masternum >= DPMASTER_COUNT)
1954 serverlist_querystage = SLIST_QUERYSTAGE_SERVERS;
1955 if (net_slist_debug.integer)
1956 Con_Print("^2Starting to query servers early (got EOT from all masters)\n");
1960 static void NetConn_ClientParsePacket_ServerList_ParseQWList(lhnetaddress_t *masteraddress, const char *masteraddressstring, const unsigned char *data, int length)
1963 lhnetaddress_t testaddress;
1965 for (masternum = 0; masternum < QWMASTER_COUNT; ++masternum)
1966 if (sv_qwmasters[masternum].string[0]
1967 && LHNETADDRESS_FromString(&testaddress, sv_qwmasters[masternum].string, QWMASTER_PORT)
1968 && LHNETADDRESS_Compare(&testaddress, masteraddress) == 0)
1970 if (net_sourceaddresscheck.integer && masternum >= QWMASTER_COUNT)
1972 Con_Printf(CON_WARN "ignoring QuakeWorld server list from unrecognised master %s\n", masteraddressstring);
1977 qwmasterstatus[masternum] = MASTER_RX_RESPONSE;
1978 if (serverlist_consoleoutput || net_slist_debug.integer)
1979 Con_Printf("^5Received QuakeWorld server list from %s\n", sv_qwmasters[masternum].string);
1981 while (length >= 6 && (data[0] != 0xFF || data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF) && data[4] * 256 + data[5] != 0)
1985 dpsnprintf (ipstring, sizeof (ipstring), "%u.%u.%u.%u:%u", data[0], data[1], data[2], data[3], data[4] * 256 + data[5]);
1986 if (serverlist_consoleoutput && developer_networking.integer)
1987 Con_Printf("Requesting info from QuakeWorld server %s\n", ipstring);
1989 if (!NetConn_ClientParsePacket_ServerList_PrepareQuery(PROTOCOL_QUAKEWORLD, ipstring, false))
1992 // move on to next address in packet
1997 // Unlike in NetConn_ClientParsePacket_ServerList_ParseDPList()
1998 // we can't start to query servers early here because QW has no EOT marker.
2002 static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *data, int length, lhnetaddress_t *peeraddress)
2006 char *string, addressstring2[128];
2007 char stringbuf[16384];
2008 char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE];
2011 char infostringvalue[MAX_INPUTLINE];
2015 // quakeworld ingame packet
2016 fromserver = cls.netcon && mysocket == cls.netcon->mysocket && !LHNETADDRESS_Compare(&cls.netcon->peeraddress, peeraddress);
2018 // convert the address to a string incase we need it
2019 LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true);
2021 if (length >= 5 && data[0] == 255 && data[1] == 255 && data[2] == 255 && data[3] == 255)
2023 // received a command string - strip off the packaging and put it
2024 // into our string buffer with NULL termination
2027 length = min(length, (int)sizeof(stringbuf) - 1);
2028 memcpy(stringbuf, data, length);
2029 stringbuf[length] = 0;
2032 if (developer_networking.integer)
2034 Con_Printf("NetConn_ClientParsePacket: %s sent us a command:\n", addressstring2);
2035 Com_HexDumpToConsole(data, length);
2038 sendlength = sizeof(senddata) - 4;
2039 switch(Crypto_ClientParsePacket(string, length, senddata+4, &sendlength, peeraddress, addressstring2))
2041 case CRYPTO_NOMATCH:
2047 memcpy(senddata, "\377\377\377\377", 4);
2048 NetConn_Write(mysocket, senddata, (int)sendlength+4, peeraddress);
2051 case CRYPTO_DISCARD:
2054 memcpy(senddata, "\377\377\377\377", 4);
2055 NetConn_Write(mysocket, senddata, (int)sendlength+4, peeraddress);
2059 case CRYPTO_REPLACE:
2060 string = senddata+4;
2061 length = (int)sendlength;
2065 if (length >= 10 && !memcmp(string, "challenge ", 10) && cls.rcon_trying)
2068 for (j = 0;j < MAX_RCONS;j++)
2070 // note: this value from i is used outside the loop too...
2071 i = (cls.rcon_ringpos + j) % MAX_RCONS;
2072 if(cls.rcon_commands[i][0])
2073 if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[i]))
2082 dpsnprintf(argbuf, sizeof(argbuf), "%s %s", string + 10, cls.rcon_commands[i]);
2083 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 CHALLENGE ", 29);
2085 e = strchr(rcon_password.string, ' ');
2086 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2088 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 29), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n))
2092 dp_strlcpy(buf + 46, argbuf, sizeof(buf) - 46);
2093 NetConn_Write(mysocket, buf, 46 + (int)strlen(buf + 46), peeraddress);
2094 cls.rcon_commands[i][0] = 0;
2097 for (k = 0;k < MAX_RCONS;k++)
2098 if(cls.rcon_commands[k][0])
2099 if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[k]))
2104 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", peeraddress);
2105 // extend the timeout on other requests as we asked for a challenge
2106 for (l = 0;l < MAX_RCONS;l++)
2107 if(cls.rcon_commands[l][0])
2108 if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[l]))
2109 cls.rcon_timeout[l] = host.realtime + rcon_secure_challengetimeout.value;
2112 return true; // we used up the challenge, so we can't use this oen for connecting now anyway
2116 if (length >= 10 && !memcmp(string, "challenge ", 10) && cls.connect_trying)
2118 // darkplaces or quake3
2119 char protocolnames[1400];
2121 if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
2122 Con_Printf(CON_WARN "ignoring challenge message from wrong server %s\n", addressstring2);
2125 Con_DPrintf("\"%s\" received, sending connect request back to %s\n", string, addressstring2);
2126 dp_strlcpy(cl_connect_status, "Connect: replying to challenge...", sizeof(cl_connect_status));
2128 Protocol_Names(protocolnames, sizeof(protocolnames));
2129 // update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command)
2130 InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2);
2131 // TODO: add userinfo stuff here instead of using NQ commands?
2132 memcpy(senddata, "\377\377\377\377", 4);
2133 dpsnprintf(senddata+4, sizeof(senddata)-4, "connect\\protocol\\darkplaces 3\\protocols\\%s%s\\challenge\\%s", protocolnames, cls.connect_userinfo, string + 10);
2134 NetConn_WriteString(mysocket, senddata, peeraddress);
2137 if (length == 6 && !memcmp(string, "accept", 6) && cls.connect_trying)
2139 // darkplaces or quake3
2140 if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
2141 Con_Printf(CON_WARN "ignoring accept message from wrong server %s\n", addressstring2);
2144 NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_DARKPLACES3);
2147 if (length > 7 && !memcmp(string, "reject ", 7) && cls.connect_trying)
2149 if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
2150 Con_Printf(CON_WARN "ignoring reject message from wrong server %s\n", addressstring2);
2153 cls.connect_trying = false;
2155 length = min(length - 7, (int)sizeof(cl_connect_status) - 1);
2156 dpsnprintf(cl_connect_status, sizeof(cl_connect_status), "Connect: rejected, %.*s", length, string);
2157 Con_Printf(CON_ERROR "Connect: rejected by %s\n" CON_ERROR "%.*s\n", addressstring2, length, string);
2161 if(key_dest != key_game)
2163 if (length >= 15 && !memcmp(string, "statusResponse\x0A", 15))
2165 serverlist_info_t *info;
2170 // search the cache for this server and update it
2171 // the challenge is (ab)used to return the query time
2172 s = InfoString_GetValue(string, "challenge", infostringvalue, sizeof(infostringvalue));
2173 n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2, s);
2177 info = &serverlist_cache[n].info;
2178 info->game[0] = '\0'; info->game_len = 0;
2179 info->mod[0] = '\0'; info->mod_len = 0;
2180 info->map[0] = '\0'; info->map_len = 0;
2181 info->name[0] = '\0'; info->name_len = 0;
2182 info->qcstatus[0] = '\0'; info->qcstatus_len = 0;
2183 info->players[0] = '\0'; info->players_len = 0;
2184 info->protocol = -1;
2185 info->numplayers = 0;
2187 info->maxplayers = 0;
2188 info->gameversion = 0;
2190 p = strchr(string, '\n');
2193 *p = 0; // cut off the string there
2197 Con_Printf("statusResponse without players block?\n");
2199 if ((s = InfoString_GetValue(string, "gamename" , infostringvalue, sizeof(infostringvalue))) != NULL) info->game_len = dp_strlcpy(info->game, s, sizeof(info->game));
2200 if ((s = InfoString_GetValue(string, "modname" , infostringvalue, sizeof(infostringvalue))) != NULL) info->mod_len = dp_strlcpy(info->mod, s, sizeof(info->mod));
2201 if ((s = InfoString_GetValue(string, "mapname" , infostringvalue, sizeof(infostringvalue))) != NULL) info->map_len = dp_strlcpy(info->map, s, sizeof(info->map));
2202 if ((s = InfoString_GetValue(string, "hostname" , infostringvalue, sizeof(infostringvalue))) != NULL) info->name_len = dp_strlcpy(info->name, s, sizeof(info->name));
2203 if ((s = InfoString_GetValue(string, "protocol" , infostringvalue, sizeof(infostringvalue))) != NULL) info->protocol = atoi(s);
2204 if ((s = InfoString_GetValue(string, "clients" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numplayers = atoi(s);
2205 if ((s = InfoString_GetValue(string, "bots" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numbots = atoi(s);
2206 if ((s = InfoString_GetValue(string, "sv_maxclients", infostringvalue, sizeof(infostringvalue))) != NULL) info->maxplayers = atoi(s);
2207 if ((s = InfoString_GetValue(string, "gameversion" , infostringvalue, sizeof(infostringvalue))) != NULL) info->gameversion = atoi(s);
2208 if ((s = InfoString_GetValue(string, "qcstatus" , infostringvalue, sizeof(infostringvalue))) != NULL) info->qcstatus_len = dp_strlcpy(info->qcstatus, s, sizeof(info->qcstatus));
2209 if (p != NULL) info->players_len = dp_strlcpy(info->players, p, sizeof(info->players));
2210 info->numhumans = info->numplayers - max(0, info->numbots);
2211 info->freeslots = info->maxplayers - info->numplayers;
2213 NetConn_ClientParsePacket_ServerList_UpdateCache(n);
2217 if (length >= 13 && !memcmp(string, "infoResponse\x0A", 13))
2219 serverlist_info_t *info;
2223 // search the cache for this server and update it
2224 // the challenge is (ab)used to return the query time
2225 s = InfoString_GetValue(string, "challenge", infostringvalue, sizeof(infostringvalue));
2226 n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2, s);
2230 info = &serverlist_cache[n].info;
2231 info->game[0] = '\0'; info->game_len = 0;
2232 info->mod[0] = '\0'; info->mod_len = 0;
2233 info->map[0] = '\0'; info->map_len = 0;
2234 info->name[0] = '\0'; info->name_len = 0;
2235 info->qcstatus[0] = '\0'; info->qcstatus_len = 0;
2236 info->players[0] = '\0'; info->players_len = 0;
2237 info->protocol = -1;
2238 info->numplayers = 0;
2240 info->maxplayers = 0;
2241 info->gameversion = 0;
2243 if ((s = InfoString_GetValue(string, "gamename" , infostringvalue, sizeof(infostringvalue))) != NULL) info->game_len = dp_strlcpy(info->game, s, sizeof(info->game));
2244 if ((s = InfoString_GetValue(string, "modname" , infostringvalue, sizeof(infostringvalue))) != NULL) info->mod_len = dp_strlcpy(info->mod, s, sizeof(info->mod));
2245 if ((s = InfoString_GetValue(string, "mapname" , infostringvalue, sizeof(infostringvalue))) != NULL) info->map_len = dp_strlcpy(info->map, s, sizeof(info->map));
2246 if ((s = InfoString_GetValue(string, "hostname" , infostringvalue, sizeof(infostringvalue))) != NULL) info->name_len = dp_strlcpy(info->name, s, sizeof(info->name));
2247 if ((s = InfoString_GetValue(string, "protocol" , infostringvalue, sizeof(infostringvalue))) != NULL) info->protocol = atoi(s);
2248 if ((s = InfoString_GetValue(string, "clients" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numplayers = atoi(s);
2249 if ((s = InfoString_GetValue(string, "bots" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numbots = atoi(s);
2250 if ((s = InfoString_GetValue(string, "sv_maxclients", infostringvalue, sizeof(infostringvalue))) != NULL) info->maxplayers = atoi(s);
2251 if ((s = InfoString_GetValue(string, "gameversion" , infostringvalue, sizeof(infostringvalue))) != NULL) info->gameversion = atoi(s);
2252 if ((s = InfoString_GetValue(string, "qcstatus" , infostringvalue, sizeof(infostringvalue))) != NULL) info->qcstatus_len = dp_strlcpy(info->qcstatus, s, sizeof(info->qcstatus));
2253 info->numhumans = info->numplayers - max(0, info->numbots);
2254 info->freeslots = info->maxplayers - info->numplayers;
2256 NetConn_ClientParsePacket_ServerList_UpdateCache(n);
2260 if (!strncmp(string, "getserversResponse\\", 19) && serverlist_cachecount < SERVERLIST_TOTALSIZE)
2262 // Extract the IP addresses
2265 NetConn_ClientParsePacket_ServerList_ParseDPList(peeraddress, addressstring2, data, length, false);
2268 if (!strncmp(string, "getserversExtResponse", 21) && serverlist_cachecount < SERVERLIST_TOTALSIZE)
2270 // Extract the IP addresses
2273 NetConn_ClientParsePacket_ServerList_ParseDPList(peeraddress, addressstring2, data, length, true);
2276 if (!memcmp(string, "d\n", 2) && serverlist_cachecount < SERVERLIST_TOTALSIZE)
2278 // Extract the IP addresses
2281 NetConn_ClientParsePacket_ServerList_ParseQWList(peeraddress, addressstring2, data, length);
2286 if (!strncmp(string, "extResponse ", 12))
2288 ++cl_net_extresponse_count;
2289 if(cl_net_extresponse_count > NET_EXTRESPONSE_MAX)
2290 cl_net_extresponse_count = NET_EXTRESPONSE_MAX;
2291 cl_net_extresponse_last = (cl_net_extresponse_last + 1) % NET_EXTRESPONSE_MAX;
2292 dpsnprintf(cl_net_extresponse[cl_net_extresponse_last], sizeof(cl_net_extresponse[cl_net_extresponse_last]), "\"%s\" %s", addressstring2, string + 12);
2295 if (!strncmp(string, "ping", 4))
2297 if (developer_extra.integer)
2298 Con_DPrintf("Received ping from %s, sending ack\n", addressstring2);
2299 NetConn_WriteString(mysocket, "\377\377\377\377ack", peeraddress);
2302 if (!strncmp(string, "ack", 3))
2304 // QuakeWorld compatibility
2305 if (length > 1 && string[0] == 'c' && (string[1] == '-' || (string[1] >= '0' && string[1] <= '9')) && cls.connect_trying)
2307 // challenge message
2308 if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
2309 Con_Printf(CON_WARN "ignoring c message from wrong server %s\n", addressstring2);
2312 Con_DPrintf("challenge %s received, sending QuakeWorld connect request back to %s\n", string + 1, addressstring2);
2313 dp_strlcpy(cl_connect_status, "Connect: replying to challenge...", sizeof(cl_connect_status));
2315 cls.qw_qport = qport.integer;
2316 // update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command)
2317 InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2);
2318 memcpy(senddata, "\377\377\377\377", 4);
2319 dpsnprintf(senddata+4, sizeof(senddata)-4, "connect %i %i %i \"%s%s\"\n", 28, cls.qw_qport, atoi(string + 1), cls.userinfo, cls.connect_userinfo);
2320 NetConn_WriteString(mysocket, senddata, peeraddress);
2323 if (length >= 1 && string[0] == 'j' && cls.connect_trying)
2326 if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
2327 Con_Printf(CON_WARN "ignoring j message from wrong server %s\n", addressstring2);
2330 NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_QUAKEWORLD);
2333 if (length > 2 && !memcmp(string, "n\\", 2))
2336 serverlist_info_t *info;
2340 if (serverlist_consoleoutput && developer_networking.integer >= 2)
2341 Con_Printf("QW server status from server at %s:\n%s\n", addressstring2, string + 1);
2344 // search the cache for this server and update it
2345 n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2, NULL);
2349 info = &serverlist_cache[n].info;
2350 dp_strlcpy(info->game, "QuakeWorld", sizeof(info->game));
2351 if ((s = InfoString_GetValue(string, "*gamedir" , infostringvalue, sizeof(infostringvalue))) != NULL) info->mod_len = dp_strlcpy(info->mod, s, sizeof(info->mod));
2352 else { info->mod[0] = '\0'; info->mod_len = 0; }
2353 if ((s = InfoString_GetValue(string, "map" , infostringvalue, sizeof(infostringvalue))) != NULL) info->map_len = dp_strlcpy(info->map, s, sizeof(info->map));
2354 else { info->map[0] = '\0'; info->map_len = 0; }
2355 if ((s = InfoString_GetValue(string, "hostname" , infostringvalue, sizeof(infostringvalue))) != NULL) info->name_len = dp_strlcpy(info->name, s, sizeof(info->name));
2356 else { info->name[0] = '\0'; info->name_len = 0; }
2358 info->numplayers = 0; // updated below
2359 info->numhumans = 0; // updated below
2360 if ((s = InfoString_GetValue(string, "maxclients" , infostringvalue, sizeof(infostringvalue))) != NULL) info->maxplayers = atoi(s);else info->maxplayers = 0;
2361 if ((s = InfoString_GetValue(string, "gameversion" , infostringvalue, sizeof(infostringvalue))) != NULL) info->gameversion = atoi(s);else info->gameversion = 0;
2363 // count active players on server
2364 // (we could gather more info, but we're just after the number)
2365 s = strchr(string, '\n');
2369 while (s < string + length)
2371 for (;s < string + length && *s != '\n';s++)
2373 if (s >= string + length)
2381 NetConn_ClientParsePacket_ServerList_UpdateCache(n);
2385 if (string[0] == 'n')
2387 // qw print command, used by rcon replies too
2388 if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address) && LHNETADDRESS_Compare(peeraddress, &cls.rcon_address)) {
2389 Con_Printf(CON_WARN "ignoring n message from wrong server %s\n", addressstring2);
2392 Con_Printf("QW print command from server at %s:\n%s\n", addressstring2, string + 1);
2394 // we may not have liked the packet, but it was a command packet, so
2395 // we're done processing this packet now
2398 // quakeworld ingame packet
2399 if (fromserver && cls.protocol == PROTOCOL_QUAKEWORLD && length >= 8 && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol, net_messagetimeout.value)) == 2)
2402 CL_ParseServerMessage();
2405 // netquake control packets, supported for compatibility only
2406 if (length >= 5 && BuffBigLong(data) == ((int)NETFLAG_CTL | length) && !ENCRYPTION_REQUIRED)
2410 serverlist_info_t *info;
2415 SZ_Clear(&cl_message);
2416 SZ_Write(&cl_message, data, length);
2417 MSG_BeginReading(&cl_message);
2418 c = MSG_ReadByte(&cl_message);
2422 if (developer_extra.integer)
2423 Con_DPrintf("Datagram_ParseConnectionless: received CCREP_ACCEPT from %s.\n", addressstring2);
2424 if (cls.connect_trying)
2426 lhnetaddress_t clientportaddress;
2427 if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
2428 Con_Printf(CON_WARN "ignoring CCREP_ACCEPT message from wrong server %s\n", addressstring2);
2431 clientportaddress = *peeraddress;
2432 LHNETADDRESS_SetPort(&clientportaddress, MSG_ReadLong(&cl_message));
2433 // extra ProQuake stuff
2435 cls.proquake_servermod = MSG_ReadByte(&cl_message); // MOD_PROQUAKE
2437 cls.proquake_servermod = 0;
2439 cls.proquake_serverversion = MSG_ReadByte(&cl_message); // version * 10
2441 cls.proquake_serverversion = 0;
2443 cls.proquake_serverflags = MSG_ReadByte(&cl_message); // flags (mainly PQF_CHEATFREE)
2445 cls.proquake_serverflags = 0;
2446 if (cls.proquake_servermod == 1)
2447 Con_Printf("Connected to ProQuake %.1f server, enabling precise aim\n", cls.proquake_serverversion / 10.0f);
2448 // update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command)
2449 InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2);
2450 NetConn_ConnectionEstablished(mysocket, &clientportaddress, PROTOCOL_QUAKE);
2454 if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address))
2456 Con_Printf(CON_WARN "ignoring CCREP_REJECT message from wrong server %s\n", addressstring2);
2459 cls.connect_trying = false;
2460 dpsnprintf(cl_connect_status, sizeof(cl_connect_status), "Connect: rejected, %s", MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)));
2461 Con_Printf(CON_ERROR "Connect: rejected by %s\n%s\n", addressstring2, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)));
2463 case CCREP_SERVER_INFO:
2464 if (developer_extra.integer)
2465 Con_DPrintf("Datagram_ParseConnectionless: received CCREP_SERVER_INFO from %s.\n", addressstring2);
2467 // LadyHavoc: because the quake server may report weird addresses
2468 // we just ignore it and keep the real address
2469 MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring));
2470 // search the cache for this server and update it
2471 n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2, NULL);
2475 info = &serverlist_cache[n].info;
2476 info->game_len = dp_strlcpy(info->game, "Quake", sizeof(info->game));
2477 info->mod_len = dp_strlcpy(info->mod, "", sizeof(info->mod)); // mod name is not specified
2478 info->name_len = dp_strlcpy(info->name, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(info->name));
2479 info->map_len = dp_strlcpy(info->map, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(info->map));
2480 info->numplayers = MSG_ReadByte(&cl_message);
2481 info->maxplayers = MSG_ReadByte(&cl_message);
2482 info->protocol = MSG_ReadByte(&cl_message);
2484 NetConn_ClientParsePacket_ServerList_UpdateCache(n);
2487 case CCREP_RCON: // RocketGuy: ProQuake rcon support
2488 if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.rcon_address)) {
2489 Con_Printf(CON_WARN "ignoring CCREP_RCON message from wrong server %s\n", addressstring2);
2492 if (developer_extra.integer)
2493 Con_DPrintf("Datagram_ParseConnectionless: received CCREP_RCON from %s.\n", addressstring2);
2495 Con_Printf("%s\n", MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)));
2497 case CCREP_PLAYER_INFO:
2498 // we got a CCREP_PLAYER_INFO??
2499 //if (developer_extra.integer)
2500 Con_Printf("Datagram_ParseConnectionless: received CCREP_PLAYER_INFO from %s.\n", addressstring2);
2502 case CCREP_RULE_INFO:
2503 // we got a CCREP_RULE_INFO??
2504 //if (developer_extra.integer)
2505 Con_Printf("Datagram_ParseConnectionless: received CCREP_RULE_INFO from %s.\n", addressstring2);
2510 SZ_Clear(&cl_message);
2511 // we may not have liked the packet, but it was a valid control
2512 // packet, so we're done processing this packet now
2516 if (fromserver && length >= (int)NET_HEADERSIZE && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol, net_messagetimeout.value)) == 2)
2517 CL_ParseServerMessage();
2522 void NetConn_QueryQueueFrame(void)
2525 unsigned maxqueries;
2526 char dpquery[53]; // theoretical max: 14+22+16+1
2527 double currentrealtime;
2528 static double querycounter = 0;
2529 static unsigned pass = 0, server = 0;
2530 unsigned queriesperserver = bound(1, net_slist_maxtries.integer, 8);
2532 if (!serverlist_querystage)
2535 // If the client stalls partway through a frame (test command: `alias a a;a`)
2536 // for correct pings we need to know what host.realtime would be if it were updated now.
2537 currentrealtime = host.realtime + (Sys_DirtyTime() - host.dirtytime);
2539 // apply a cool down time after master server replies,
2540 // to avoid messing up the ping times on the servers
2541 if (serverlist_querystage < SLIST_QUERYSTAGE_SERVERS)
2543 if (currentrealtime < masterquerytime + net_slist_timeout.value)
2546 // Report the masters that timed out or whose response was incomplete.
2547 for (index = 0; index < DPMASTER_COUNT; ++index)
2548 if (dpmasterstatus[index] && dpmasterstatus[index] < MASTER_RX_COMPLETE)
2549 Con_Printf(CON_WARN "WARNING: dpmaster %s %s\n", sv_masters[index].string, dpmasterstatus[index] == MASTER_TX_QUERY ? "timed out" : "response incomplete");
2550 for (index = 0; index < QWMASTER_COUNT; ++index)
2551 if (qwmasterstatus[index] && qwmasterstatus[index] < MASTER_RX_RESPONSE) // no EOT marker in QW lists
2552 Con_Printf(CON_WARN "WARNING: qwmaster %s timed out\n", sv_qwmasters[index].string);
2554 serverlist_querystage = SLIST_QUERYSTAGE_SERVERS;
2557 // each time querycounter reaches 1.0 issue a query
2558 querycounter += cl.realframetime * net_slist_queriespersecond.value;
2559 maxqueries = bound(0, (int)querycounter, net_slist_queriesperframe.integer);
2560 querycounter -= maxqueries;
2561 if (maxqueries == 0)
2564 if (pass < queriesperserver)
2566 // QW depends on waiting "long enough" between queries that responses "definitely" refer to the most recent querytime
2567 // DP servers can echo back a timestamp for reliable (and more frequent, see net_slist_interval) pings
2568 ServerList_BuildDPServerQuery(dpquery, sizeof(dpquery), currentrealtime);
2570 for (unsigned queries = 0; server < serverlist_cachecount; ++server)
2572 lhnetaddress_t address;
2574 serverlist_entry_t *entry = &serverlist_cache[server];
2576 if (queries >= maxqueries
2577 || currentrealtime <= entry->querytime + (entry->protocol == PROTOCOL_QUAKEWORLD ? net_slist_timeout : net_slist_interval).value)
2578 return; // continue this pass at the current server on a later frame
2580 LHNETADDRESS_FromString(&address, entry->info.cname, 0);
2581 if (entry->protocol == PROTOCOL_QUAKEWORLD)
2583 for (socket = 0; socket < cl_numsockets; ++socket)
2584 if (cl_sockets[socket])
2585 NetConn_WriteString(cl_sockets[socket], "\377\377\377\377status\n", &address);
2589 for (socket = 0; socket < cl_numsockets; ++socket)
2590 if (cl_sockets[socket])
2591 NetConn_WriteString(cl_sockets[socket], dpquery, &address);
2594 entry->querytime = currentrealtime;
2597 if (serverlist_consoleoutput)
2598 Con_Printf("querying %25s (%i. try)\n", entry->info.cname, pass + 1);
2604 for (; server < serverlist_cachecount; ++server)
2606 serverlist_entry_t *entry = &serverlist_cache[server];
2608 if (!entry->responded // no acceptable response during this refresh cycle
2609 && entry->info.ping) // visible in the list (has old ping from previous refresh cycle)
2611 if (currentrealtime > entry->querytime + net_slist_maxping.integer/1000)
2613 // you have no chance to survive make your timeout
2615 if(!net_slist_pause.integer)
2616 ServerList_ViewList_Remove(entry);
2617 entry->info.ping = 0; // removed later if net_slist_pause
2619 else // still has time
2620 return; // continue this pass at the current server on a later frame
2625 // We finished the pass, ie didn't stop at maxqueries
2626 // or a server that can't be (re)queried or timed out yet.
2630 if (pass == queriesperserver)
2632 // timeout pass begins next frame
2633 if (net_slist_debug.integer)
2634 Con_Printf("^2Finished querying masters and servers in %f\n", currentrealtime - masterquerytime);
2636 else if (pass > queriesperserver)
2638 // Nothing else to do until the next refresh cycle.
2639 if (net_slist_debug.integer)
2640 Con_Printf("^4Finished checking server timeouts in %f\n", currentrealtime - serverlist_cache[serverlist_cachecount - 1].querytime);
2641 serverlist_querystage = 0;
2647 void NetConn_ClientFrame(void)
2651 lhnetaddress_t peeraddress;
2652 unsigned char readbuffer[NET_HEADERSIZE+NET_MAXMESSAGE];
2654 NetConn_UpdateSockets();
2656 if (cls.connect_trying && cls.connect_nextsendtime < host.realtime)
2658 if (cls.connect_remainingtries > 0)
2660 cls.connect_remainingtries--;
2661 dpsnprintf(cl_connect_status, sizeof(cl_connect_status), "Connect: sending initial request, %i %s left...", cls.connect_remainingtries, cls.connect_remainingtries == 1 ? "retry" : "retries");
2667 cls.connect_trying = false;
2668 LHNETADDRESS_ToString(&cls.connect_address, address, sizeof(address), true);
2669 dp_strlcpy(cl_connect_status, "Connect: failed, no reply", sizeof(cl_connect_status));
2670 Con_Printf(CON_ERROR "%s from %s\n", cl_connect_status, address);
2673 cls.connect_nextsendtime = host.realtime + 1;
2675 // try challenge first (newer DP server or QW)
2676 NetConn_WriteString(cls.connect_mysocket, "\377\377\377\377getchallenge", &cls.connect_address);
2677 // then try netquake as a fallback (old server, or netquake)
2678 SZ_Clear(&cl_message);
2679 // save space for the header, filled in later
2680 MSG_WriteLong(&cl_message, 0);
2681 MSG_WriteByte(&cl_message, CCREQ_CONNECT);
2682 MSG_WriteString(&cl_message, "QUAKE");
2683 MSG_WriteByte(&cl_message, NET_PROTOCOL_VERSION);
2684 // extended proquake stuff
2685 MSG_WriteByte(&cl_message, 1); // mod = MOD_PROQUAKE
2686 // this version matches ProQuake 3.40, the first version to support
2687 // the NAT fix, and it only supports the NAT fix for ProQuake 3.40 or
2688 // higher clients, so we pretend we are that version...
2689 MSG_WriteByte(&cl_message, 34); // version * 10
2690 MSG_WriteByte(&cl_message, 0); // flags
2691 MSG_WriteLong(&cl_message, 0); // password
2692 // write the packetsize now...
2693 StoreBigLong(cl_message.data, NETFLAG_CTL | (cl_message.cursize & NETFLAG_LENGTH_MASK));
2694 NetConn_Write(cls.connect_mysocket, cl_message.data, cl_message.cursize, &cls.connect_address);
2695 SZ_Clear(&cl_message);
2698 for (i = 0;i < cl_numsockets;i++)
2700 while (cl_sockets[i] && (length = NetConn_Read(cl_sockets[i], readbuffer, sizeof(readbuffer), &peeraddress)) > 0)
2702 // R_TimeReport("clientreadnetwork");
2703 NetConn_ClientParsePacket(cl_sockets[i], readbuffer, length, &peeraddress);
2704 // R_TimeReport("clientparsepacket");
2708 NetConn_QueryQueueFrame();
2710 if (cls.netcon && host.realtime > cls.netcon->timeout && !sv.active)
2711 CL_DisconnectEx(true, "Connection timed out");
2714 static void NetConn_BuildChallengeString(char *buffer, int bufferlength)
2718 for (i = 0;i < bufferlength - 1;i++)
2722 c = rand () % (127 - 33) + 33;
2723 } while (c == '\\' || c == ';' || c == '"' || c == '%' || c == '/');
2729 /// (div0) build the full response only if possible; better a getinfo response than no response at all if getstatus won't fit
2730 static qbool NetConn_BuildStatusResponse(const char* challenge, char* out_msg, size_t out_size, qbool fullstatus)
2732 prvm_prog_t *prog = SVVM_prog;
2734 unsigned int nb_clients = 0, nb_bots = 0, i;
2737 const char *crypto_idstring;
2738 const char *worldstatusstr;
2740 // How many clients are there?
2741 for (i = 0;i < (unsigned int)svs.maxclients;i++)
2743 if (svs.clients[i].active)
2746 if (!svs.clients[i].netconnection)
2752 worldstatusstr = PRVM_GetString(prog, PRVM_serverglobalstring(worldstatus));
2753 if(worldstatusstr && *worldstatusstr)
2758 for(q = worldstatusstr; *q && (size_t)(p - qcstatus) < (sizeof(qcstatus) - 1); ++q)
2759 if(*q != '\\' && *q != '\n')
2764 /// \TODO: we should add more information for the full status string
2765 crypto_idstring = Crypto_GetInfoResponseDataString();
2766 length = dpsnprintf(out_msg, out_size,
2767 "\377\377\377\377%s\x0A"
2768 "\\gamename\\%s\\modname\\%s\\gameversion\\%d\\sv_maxclients\\%d"
2769 "\\clients\\%d\\bots\\%d\\mapname\\%s\\hostname\\%s\\protocol\\%d"
2774 fullstatus ? "statusResponse" : "infoResponse",
2775 gamenetworkfiltername, com_modname, gameversion.integer, svs.maxclients,
2776 nb_clients, nb_bots, sv.worldbasename, hostname.string, NET_PROTOCOL_VERSION,
2777 *qcstatus ? "\\qcstatus\\" : "", qcstatus,
2778 challenge ? "\\challenge\\" : "", challenge ? challenge : "",
2779 crypto_idstring ? "\\d0_blind_id\\" : "", crypto_idstring ? crypto_idstring : "",
2780 fullstatus ? "\n" : "");
2782 // Make sure it fits in the buffer
2792 savelength = length;
2794 ptr = out_msg + length;
2795 left = (int)out_size - length;
2797 for (i = 0;i < (unsigned int)svs.maxclients;i++)
2799 client_t *client = &svs.clients[i];
2802 int nameind, cleanind, pingvalue;
2804 char cleanname [sizeof(client->name)];
2805 const char *statusstr;
2808 // Remove all characters '"' and '\' in the player name
2813 curchar = client->name[nameind++];
2814 if (curchar != '"' && curchar != '\\')
2816 cleanname[cleanind++] = curchar;
2817 if (cleanind == sizeof(cleanname) - 1)
2820 } while (curchar != '\0');
2821 cleanname[cleanind] = 0; // cleanind is always a valid index even at this point
2823 pingvalue = (int)(client->ping * 1000.0f);
2824 if(client->netconnection)
2825 pingvalue = bound(1, pingvalue, 9999);
2830 ed = PRVM_EDICT_NUM(i + 1);
2831 statusstr = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
2832 if(statusstr && *statusstr)
2837 for(q = statusstr; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
2838 if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
2843 if (IS_NEXUIZ_DERIVED(gamemode) && (teamplay.integer > 0))
2845 if(client->frags == -666) // spectator
2846 dp_strlcpy(teambuf, " 0", sizeof(teambuf));
2847 else if(client->colors == 0x44) // red team
2848 dp_strlcpy(teambuf, " 1", sizeof(teambuf));
2849 else if(client->colors == 0xDD) // blue team
2850 dp_strlcpy(teambuf, " 2", sizeof(teambuf));
2851 else if(client->colors == 0xCC) // yellow team
2852 dp_strlcpy(teambuf, " 3", sizeof(teambuf));
2853 else if(client->colors == 0x99) // pink team
2854 dp_strlcpy(teambuf, " 4", sizeof(teambuf));
2856 dp_strlcpy(teambuf, " 0", sizeof(teambuf));
2861 // note: team number is inserted according to SoF2 protocol
2863 length = dpsnprintf(ptr, left, "%s %d%s \"%s\"\n",
2869 length = dpsnprintf(ptr, left, "%d %d%s \"%s\"\n",
2878 // turn it into an infoResponse!
2879 out_msg[savelength] = 0;
2880 memcpy(out_msg + 4, "infoResponse\x0A", 13);
2881 memmove(out_msg + 17, out_msg + 19, savelength - 19);
2896 static qbool NetConn_PreventFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength, double floodtime, qbool renew)
2898 size_t floodslotnum, bestfloodslotnum;
2899 double bestfloodtime;
2900 lhnetaddress_t noportpeeraddress;
2901 // see if this is a connect flood
2902 noportpeeraddress = *peeraddress;
2903 LHNETADDRESS_SetPort(&noportpeeraddress, 0);
2904 bestfloodslotnum = 0;
2905 bestfloodtime = floodlist[bestfloodslotnum].lasttime;
2906 for (floodslotnum = 0;floodslotnum < floodlength;floodslotnum++)
2908 if (bestfloodtime >= floodlist[floodslotnum].lasttime)
2910 bestfloodtime = floodlist[floodslotnum].lasttime;
2911 bestfloodslotnum = floodslotnum;
2913 if (floodlist[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &floodlist[floodslotnum].address) == 0)
2915 // this address matches an ongoing flood address
2916 if (host.realtime < floodlist[floodslotnum].lasttime + floodtime)
2920 // renew the ban on this address so it does not expire
2921 // until the flood has subsided
2922 floodlist[floodslotnum].lasttime = host.realtime;
2924 //Con_Printf("Flood detected!\n");
2927 // the flood appears to have subsided, so allow this
2928 bestfloodslotnum = floodslotnum; // reuse the same slot
2932 // begin a new timeout on this address
2933 floodlist[bestfloodslotnum].address = noportpeeraddress;
2934 floodlist[bestfloodslotnum].lasttime = host.realtime;
2935 //Con_Printf("Flood detection initiated!\n");
2939 void NetConn_ClearFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength)
2941 size_t floodslotnum;
2942 lhnetaddress_t noportpeeraddress;
2943 // see if this is a connect flood
2944 noportpeeraddress = *peeraddress;
2945 LHNETADDRESS_SetPort(&noportpeeraddress, 0);
2946 for (floodslotnum = 0;floodslotnum < floodlength;floodslotnum++)
2948 if (floodlist[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &floodlist[floodslotnum].address) == 0)
2950 // this address matches an ongoing flood address
2952 floodlist[floodslotnum].address.addresstype = LHNETADDRESSTYPE_NONE;
2953 floodlist[floodslotnum].lasttime = 0;
2954 //Con_Printf("Flood cleared!\n");
2959 typedef qbool (*rcon_matchfunc_t) (lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen);
2961 static qbool hmac_mdfour_time_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen)
2967 Con_Print(CON_ERROR "LOGIC ERROR: RCon_Authenticate should never call the comparator with an empty password. Please report.\n");
2971 t1 = (long) time(NULL);
2972 t2 = strtol(s, NULL, 0);
2973 if(labs(t1 - t2) > rcon_secure_maxdiff.integer)
2976 if(!HMAC_MDFOUR_16BYTES((unsigned char *) mdfourbuf, (unsigned char *) s, slen, (unsigned char *) password, (int)strlen(password)))
2979 return !memcmp(mdfourbuf, hash, 16);
2982 static qbool hmac_mdfour_challenge_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen)
2988 Con_Print(CON_ERROR "LOGIC ERROR: RCon_Authenticate should never call the comparator with an empty password. Please report.\n");
2992 if(slen < (int)(sizeof(challenges[0].string)) - 1)
2995 // validate the challenge
2996 for (i = 0;i < MAX_CHALLENGES;i++)
2997 if(challenges[i].time > 0)
2998 if (!LHNETADDRESS_Compare(peeraddress, &challenges[i].address) && !strncmp(challenges[i].string, s, sizeof(challenges[0].string) - 1))
3000 // if the challenge is not recognized, drop the packet
3001 if (i == MAX_CHALLENGES)
3004 if(!HMAC_MDFOUR_16BYTES((unsigned char *) mdfourbuf, (unsigned char *) s, slen, (unsigned char *) password, (int)strlen(password)))
3007 if(memcmp(mdfourbuf, hash, 16))
3010 // unmark challenge to prevent replay attacks
3011 challenges[i].time = 0;
3016 static qbool plaintext_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen)
3019 Con_Print(CON_ERROR "LOGIC ERROR: RCon_Authenticate should never call the comparator with an empty password. Please report.\n");
3023 return !strcmp(password, hash);
3026 /// returns a string describing the user level, or NULL for auth failure
3027 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)
3029 const char *text, *userpass_start, *userpass_end, *userpass_startpass;
3030 static char buf[MAX_INPUTLINE];
3032 qbool restricted = false;
3033 qbool have_usernames = false;
3034 static char vabuf[1024];
3036 userpass_start = rcon_password.string;
3037 while((userpass_end = strchr(userpass_start, ' ')))
3039 have_usernames = true;
3040 dp_strlcpy(buf, userpass_start, ((size_t)(userpass_end-userpass_start) >= sizeof(buf)) ? (int)(sizeof(buf)) : (int)(userpass_end-userpass_start+1));
3041 if(buf[0]) // Ignore empty entries due to leading/duplicate space.
3042 if(comparator(peeraddress, buf, password, cs, cslen))
3044 userpass_start = userpass_end + 1;
3046 if(userpass_start[0]) // Ignore empty trailing entry due to trailing space or password not set.
3048 userpass_end = userpass_start + strlen(userpass_start);
3049 if(comparator(peeraddress, userpass_start, password, cs, cslen))
3054 have_usernames = false;
3055 userpass_start = rcon_restricted_password.string;
3056 while((userpass_end = strchr(userpass_start, ' ')))
3058 have_usernames = true;
3059 dp_strlcpy(buf, userpass_start, ((size_t)(userpass_end-userpass_start) >= sizeof(buf)) ? (int)(sizeof(buf)) : (int)(userpass_end-userpass_start+1));
3060 if(buf[0]) // Ignore empty entries due to leading/duplicate space.
3061 if(comparator(peeraddress, buf, password, cs, cslen))
3063 userpass_start = userpass_end + 1;
3065 if(userpass_start[0]) // Ignore empty trailing entry due to trailing space or password not set.
3067 userpass_end = userpass_start + strlen(userpass_start);
3068 if(comparator(peeraddress, userpass_start, password, cs, cslen))
3072 return NULL; // DENIED
3075 for(text = s; text != endpos; ++text)
3076 if((signed char) *text > 0 && ((signed char) *text < (signed char) ' ' || *text == ';'))
3077 return NULL; // block possible exploits against the parser/alias expansion
3081 size_t l = strlen(s);
3084 hasquotes = (strchr(s, '"') != NULL);
3085 // sorry, we can't allow these substrings in wildcard expressions,
3086 // as they can mess with the argument counts
3087 text = rcon_restricted_commands.string;
3088 while(COM_ParseToken_Console(&text))
3090 // com_token now contains a pattern to check for...
3091 if(strchr(com_token, '*') || strchr(com_token, '?')) // wildcard expression, * can only match a SINGLE argument
3094 if(matchpattern_with_separator(s, com_token, true, " ", true)) // note how we excluded tab, newline etc. above
3097 else if(strchr(com_token, ' ')) // multi-arg expression? must match in whole
3099 if(!strcmp(com_token, s))
3102 else // single-arg expression? must match the beginning of the command
3104 if(!strcmp(com_token, s))
3106 if(!memcmp(va(vabuf, sizeof(vabuf), "%s ", com_token), s, strlen(com_token) + 1))
3110 // if we got here, nothing matched!
3118 userpass_startpass = strchr(userpass_start, ':');
3119 if(have_usernames && userpass_startpass && userpass_startpass < userpass_end)
3120 return va(vabuf, sizeof(vabuf), "%srcon (username %.*s)", restricted ? "restricted " : "", (int)(userpass_startpass-userpass_start), userpass_start);
3122 return va(vabuf, sizeof(vabuf), "%srcon", restricted ? "restricted " : "");
3125 static void RCon_Execute(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, const char *addressstring2, const char *userlevel, const char *s, const char *endpos, qbool proquakeprotocol)
3129 // looks like a legitimate rcon command with the correct password
3130 const char *s_ptr = s;
3131 Con_Printf("server received %s command from %s: ", userlevel, host_client ? host_client->name : addressstring2);
3132 while(s_ptr != endpos)
3134 size_t l = strlen(s_ptr);
3136 Con_Printf(" %s;", s_ptr);
3141 if (!host_client || !host_client->netconnection || LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP)
3142 Con_Rcon_Redirect_Init(mysocket, peeraddress, proquakeprotocol);
3145 size_t l = strlen(s);
3148 client_t *host_client_save = host_client;
3149 //Cmd_ExecuteString(cmd_local, s, src_local, true); // no variable expansion
3150 // bones_was_here: prepending allows a loop such as `alias foo "bar; wait; foo"; foo`
3151 // to be broken with an alias or unalias command
3152 Cbuf_InsertText(cmd_local, s);
3153 host_client = host_client_save;
3154 // in case it is a command that changes host_client (like restart)
3158 Con_Rcon_Redirect_End();
3162 Con_Printf("server denied rcon access to %s\n", host_client ? host_client->name : addressstring2);
3166 static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *data, int length, lhnetaddress_t *peeraddress)
3168 int i, ret, clientnum, best;
3170 char *string, response[2800], addressstring2[128];
3171 static char stringbuf[16384]; // server only
3172 qbool islocal = (LHNETADDRESS_GetAddressType(peeraddress) == LHNETADDRESSTYPE_LOOP);
3173 char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE];
3174 size_t sendlength, response_len;
3175 char infostringvalue[MAX_INPUTLINE];
3180 // convert the address to a string incase we need it
3181 LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true);
3183 // see if we can identify the sender as a local player
3184 // (this is necessary for rcon to send a reliable reply if the client is
3185 // actually on the server, not sending remotely)
3186 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
3187 if (host_client->netconnection && host_client->netconnection->mysocket == mysocket && !LHNETADDRESS_Compare(&host_client->netconnection->peeraddress, peeraddress))
3189 if (i == svs.maxclients)
3192 if (length >= 5 && data[0] == 255 && data[1] == 255 && data[2] == 255 && data[3] == 255)
3194 // received a command string - strip off the packaging and put it
3195 // into our string buffer with NULL termination
3198 length = min(length, (int)sizeof(stringbuf) - 1);
3199 memcpy(stringbuf, data, length);
3200 stringbuf[length] = 0;
3203 if (developer_extra.integer)
3205 Con_Printf("NetConn_ServerParsePacket: %s sent us a command:\n", addressstring2);
3206 Com_HexDumpToConsole(data, length);
3209 sendlength = sizeof(senddata) - 4;
3210 switch(Crypto_ServerParsePacket(string, length, senddata+4, &sendlength, peeraddress))
3212 case CRYPTO_NOMATCH:
3218 memcpy(senddata, "\377\377\377\377", 4);
3219 NetConn_Write(mysocket, senddata, (int)sendlength+4, peeraddress);
3222 case CRYPTO_DISCARD:
3225 memcpy(senddata, "\377\377\377\377", 4);
3226 NetConn_Write(mysocket, senddata, (int)sendlength+4, peeraddress);
3230 case CRYPTO_REPLACE:
3231 string = senddata+4;
3232 length = (int)sendlength;
3236 if (length >= 12 && !memcmp(string, "getchallenge", 12) && (islocal || sv_public.integer > -3))
3238 for (i = 0, best = 0, besttime = host.realtime;i < MAX_CHALLENGES;i++)
3240 if(challenges[i].time > 0)
3241 if (!LHNETADDRESS_Compare(peeraddress, &challenges[i].address))
3243 if (besttime > challenges[i].time)
3244 besttime = challenges[best = i].time;
3246 // if we did not find an exact match, choose the oldest and
3247 // update address and string
3248 if (i == MAX_CHALLENGES)
3251 challenges[i].address = *peeraddress;
3252 NetConn_BuildChallengeString(challenges[i].string, sizeof(challenges[i].string));
3256 // flood control: drop if requesting challenge too often
3257 if(challenges[i].time > host.realtime - net_challengefloodblockingtimeout.value)
3260 challenges[i].time = host.realtime;
3261 // send the challenge
3262 memcpy(response, "\377\377\377\377", 4);
3263 dpsnprintf(response+4, sizeof(response)-4, "challenge %s", challenges[i].string);
3264 response_len = strlen(response) + 1;
3265 Crypto_ServerAppendToChallenge(string, length, response, &response_len, sizeof(response));
3266 NetConn_Write(mysocket, response, (int)response_len, peeraddress);
3269 if (length > 8 && !memcmp(string, "connect\\", 8))
3273 crypto_t *crypto = Crypto_ServerGetInstance(peeraddress);
3277 if(crypto && crypto->authenticated)
3279 // no need to check challenge
3280 if(crypto_developer.integer)
3282 Con_Printf("%s connection to %s is being established: client is %s@%s%.*s, I am %.*s@%s%.*s\n",
3283 crypto->use_aes ? "Encrypted" : "Authenticated",
3285 crypto->client_idfp[0] ? crypto->client_idfp : "-",
3286 (crypto->client_issigned || !crypto->client_keyfp[0]) ? "" : "~",
3287 crypto_keyfp_recommended_length, crypto->client_keyfp[0] ? crypto->client_keyfp : "-",
3288 crypto_keyfp_recommended_length, crypto->server_idfp[0] ? crypto->server_idfp : "-",
3289 (crypto->server_issigned || !crypto->server_keyfp[0]) ? "" : "~",
3290 crypto_keyfp_recommended_length, crypto->server_keyfp[0] ? crypto->server_keyfp : "-"
3296 if ((s = InfoString_GetValue(string, "challenge", infostringvalue, sizeof(infostringvalue))))
3298 // validate the challenge
3299 for (i = 0;i < MAX_CHALLENGES;i++)
3300 if(challenges[i].time > 0)
3301 if (!LHNETADDRESS_Compare(peeraddress, &challenges[i].address) && !strcmp(challenges[i].string, s))
3303 // if the challenge is not recognized, drop the packet
3304 if (i == MAX_CHALLENGES)
3309 if((s = InfoString_GetValue(string, "message", infostringvalue, sizeof(infostringvalue))))
3310 Con_DPrintf("Connecting client %s sent us the message: %s\n", addressstring2, s);
3312 if(!(islocal || sv_public.integer > -2))
3314 if (developer_extra.integer)
3315 Con_Printf("Datagram_ParseConnectionless: sending \"reject %s\" to %s.\n", sv_public_rejectreason.string, addressstring2);
3316 memcpy(response, "\377\377\377\377", 4);
3317 dpsnprintf(response+4, sizeof(response)-4, "reject %s", sv_public_rejectreason.string);
3318 NetConn_WriteString(mysocket, response, peeraddress);
3322 // check engine protocol
3323 if(!(s = InfoString_GetValue(string, "protocol", infostringvalue, sizeof(infostringvalue))) || strcmp(s, "darkplaces 3"))
3325 if (developer_extra.integer)
3326 Con_Printf("Datagram_ParseConnectionless: sending \"reject Wrong game protocol.\" to %s.\n", addressstring2);
3327 NetConn_WriteString(mysocket, "\377\377\377\377reject Wrong game protocol.", peeraddress);
3331 // see if this is a duplicate connection request or a disconnected
3332 // client who is rejoining to the same client slot
3333 for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++)
3335 if (client->netconnection && LHNETADDRESS_Compare(peeraddress, &client->netconnection->peeraddress) == 0)
3337 // this is a known client...
3338 if(crypto && crypto->authenticated)
3340 // reject if changing key!
3341 if(client->netconnection->crypto.authenticated)
3344 strcmp(client->netconnection->crypto.client_idfp, crypto->client_idfp)
3346 strcmp(client->netconnection->crypto.server_idfp, crypto->server_idfp)
3348 strcmp(client->netconnection->crypto.client_keyfp, crypto->client_keyfp)
3350 strcmp(client->netconnection->crypto.server_keyfp, crypto->server_keyfp)
3353 if (developer_extra.integer)
3354 Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to change key of crypto.\" to %s.\n", addressstring2);
3355 NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to change key of crypto.", peeraddress);
3362 // reject if downgrading!
3363 if(client->netconnection->crypto.authenticated)
3365 if (developer_extra.integer)
3366 Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to downgrade crypto.\" to %s.\n", addressstring2);
3367 NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to downgrade crypto.", peeraddress);
3373 // client crashed and is coming back,
3374 // keep their stuff intact
3375 if (developer_extra.integer)
3376 Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", addressstring2);
3377 NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress);
3378 if(crypto && crypto->authenticated)
3379 Crypto_FinishInstance(&client->netconnection->crypto, crypto);
3380 SV_SendServerinfo(client);
3384 // client is still trying to connect,
3385 // so we send a duplicate reply
3386 if (developer_extra.integer)
3387 Con_Printf("Datagram_ParseConnectionless: sending duplicate accept to %s.\n", addressstring2);
3388 if(crypto && crypto->authenticated)
3389 Crypto_FinishInstance(&client->netconnection->crypto, crypto);
3390 NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress);
3396 if (NetConn_PreventFlood(peeraddress, sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0]), net_connectfloodblockingtimeout.value, true))
3399 // find an empty client slot for this new client
3400 for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++)
3403 if (!client->active && (conn = NetConn_Open(mysocket, peeraddress)))
3405 // allocated connection
3406 if (developer_extra.integer)
3407 Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", conn->address);
3408 NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress);
3409 // now set up the client
3410 if(crypto && crypto->authenticated)
3411 Crypto_FinishInstance(&conn->crypto, crypto);
3412 SV_ConnectClient(clientnum, conn);
3413 NetConn_Heartbeat(1);
3418 // no empty slots found - server is full
3419 if (developer_extra.integer)
3420 Con_Printf("Datagram_ParseConnectionless: sending \"reject Server is full.\" to %s.\n", addressstring2);
3421 NetConn_WriteString(mysocket, "\377\377\377\377reject Server is full.", peeraddress);
3425 if (length >= 7 && !memcmp(string, "getinfo", 7) && (islocal || sv_public.integer > -1))
3427 const char *challenge = NULL;
3429 if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false))
3432 // If there was a challenge in the getinfo message
3433 if (length > 8 && string[7] == ' ')
3434 challenge = string + 8;
3436 if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), false))
3438 if (developer_extra.integer)
3439 Con_DPrintf("Sending reply to master %s - %s\n", addressstring2, response);
3440 NetConn_WriteString(mysocket, response, peeraddress);
3444 if (length >= 9 && !memcmp(string, "getstatus", 9) && (islocal || sv_public.integer > -1))
3446 const char *challenge = NULL;
3448 if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false))
3451 // If there was a challenge in the getinfo message
3452 if (length > 10 && string[9] == ' ')
3453 challenge = string + 10;
3455 if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), true))
3457 if (developer_extra.integer)
3458 Con_DPrintf("Sending reply to client %s - %s\n", addressstring2, response);
3459 NetConn_WriteString(mysocket, response, peeraddress);
3463 if (length >= 37 && !memcmp(string, "srcon HMAC-MD4 TIME ", 20))
3465 char *password = string + 20;
3466 char *timeval = string + 37;
3467 char *s = strchr(timeval, ' ');
3468 char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it
3469 const char *userlevel;
3471 if(rcon_secure.integer > 1)
3475 return true; // invalid packet
3478 userlevel = RCon_Authenticate(peeraddress, password, s, endpos, hmac_mdfour_time_matching, timeval, endpos - timeval - 1); // not including the appended \0 into the HMAC
3479 RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false);
3482 if (length >= 42 && !memcmp(string, "srcon HMAC-MD4 CHALLENGE ", 25))
3484 char *password = string + 25;
3485 char *challenge = string + 42;
3486 char *s = strchr(challenge, ' ');
3487 char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it
3488 const char *userlevel;
3490 return true; // invalid packet
3493 userlevel = RCon_Authenticate(peeraddress, password, s, endpos, hmac_mdfour_challenge_matching, challenge, endpos - challenge - 1); // not including the appended \0 into the HMAC
3494 RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false);
3497 if (length >= 5 && !memcmp(string, "rcon ", 5))
3500 char *s = string + 5;
3501 char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it
3504 if(rcon_secure.integer > 0)
3507 for (j = 0;!ISWHITESPACE(*s);s++)
3508 if (j < (int)sizeof(password) - 1)
3510 if(ISWHITESPACE(*s) && s != endpos) // skip leading ugly space
3513 if (!ISWHITESPACE(password[0]))
3515 const char *userlevel = RCon_Authenticate(peeraddress, password, s, endpos, plaintext_matching, NULL, 0);
3516 RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false);
3520 if (!strncmp(string, "extResponse ", 12))
3522 ++sv_net_extresponse_count;
3523 if(sv_net_extresponse_count > NET_EXTRESPONSE_MAX)
3524 sv_net_extresponse_count = NET_EXTRESPONSE_MAX;
3525 sv_net_extresponse_last = (sv_net_extresponse_last + 1) % NET_EXTRESPONSE_MAX;
3526 dpsnprintf(sv_net_extresponse[sv_net_extresponse_last], sizeof(sv_net_extresponse[sv_net_extresponse_last]), "'%s' %s", addressstring2, string + 12);
3529 if (!strncmp(string, "ping", 4))
3531 if (developer_extra.integer)
3532 Con_DPrintf("Received ping from %s, sending ack\n", addressstring2);
3533 NetConn_WriteString(mysocket, "\377\377\377\377ack", peeraddress);
3536 if (!strncmp(string, "ack", 3))
3538 // we may not have liked the packet, but it was a command packet, so
3539 // we're done processing this packet now
3542 // netquake control packets, supported for compatibility only, and only
3543 // when running game protocols that are normally served via this connection
3545 // (this protects more modern protocols against being used for
3546 // Quake packet flood Denial Of Service attacks)
3547 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)
3551 const char *protocolname;
3552 client_t *knownclient;
3553 client_t *newclient;
3556 SZ_Clear(&sv_message);
3557 SZ_Write(&sv_message, data, length);
3558 MSG_BeginReading(&sv_message);
3559 c = MSG_ReadByte(&sv_message);
3563 if (developer_extra.integer)
3564 Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_CONNECT from %s.\n", addressstring2);
3565 if(!(islocal || sv_public.integer > -2))
3567 if (developer_extra.integer)
3568 Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"%s\" to %s.\n", sv_public_rejectreason.string, addressstring2);
3569 SZ_Clear(&sv_message);
3570 // save space for the header, filled in later
3571 MSG_WriteLong(&sv_message, 0);
3572 MSG_WriteByte(&sv_message, CCREP_REJECT);
3573 MSG_WriteUnterminatedString(&sv_message, sv_public_rejectreason.string);
3574 MSG_WriteString(&sv_message, "\n");
3575 StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
3576 NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
3577 SZ_Clear(&sv_message);
3581 protocolname = MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring));
3582 protocolnumber = MSG_ReadByte(&sv_message);
3583 if (strcmp(protocolname, "QUAKE") || protocolnumber != NET_PROTOCOL_VERSION)
3585 if (developer_extra.integer)
3586 Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Incompatible version.\" to %s.\n", addressstring2);
3587 SZ_Clear(&sv_message);
3588 // save space for the header, filled in later
3589 MSG_WriteLong(&sv_message, 0);
3590 MSG_WriteByte(&sv_message, CCREP_REJECT);
3591 MSG_WriteString(&sv_message, "Incompatible version.\n");
3592 StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
3593 NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
3594 SZ_Clear(&sv_message);
3598 // see if this connect request comes from a known client
3599 for (clientnum = 0, knownclient = svs.clients;clientnum < svs.maxclients;clientnum++, knownclient++)
3601 if (knownclient->netconnection && LHNETADDRESS_Compare(peeraddress, &knownclient->netconnection->peeraddress) == 0)
3603 // this is either a duplicate connection request
3604 // or coming back from a timeout
3605 // (if so, keep their stuff intact)
3607 crypto_t *crypto = Crypto_ServerGetInstance(peeraddress);
3608 if((crypto && crypto->authenticated) || knownclient->netconnection->crypto.authenticated)
3610 if (developer_extra.integer)
3611 Con_Printf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Attempt to downgrade crypto.\" to %s.\n", addressstring2);
3612 SZ_Clear(&sv_message);
3613 // save space for the header, filled in later
3614 MSG_WriteLong(&sv_message, 0);
3615 MSG_WriteByte(&sv_message, CCREP_REJECT);
3616 MSG_WriteString(&sv_message, "Attempt to downgrade crypto.\n");
3617 StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
3618 NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
3619 SZ_Clear(&sv_message);
3624 if (developer_extra.integer)
3625 Con_DPrintf("Datagram_ParseConnectionless: sending duplicate CCREP_ACCEPT to %s.\n", addressstring2);
3626 SZ_Clear(&sv_message);
3627 // save space for the header, filled in later
3628 MSG_WriteLong(&sv_message, 0);
3629 MSG_WriteByte(&sv_message, CCREP_ACCEPT);
3630 MSG_WriteLong(&sv_message, LHNETADDRESS_GetPort(LHNET_AddressFromSocket(knownclient->netconnection->mysocket)));
3631 StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
3632 NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
3633 SZ_Clear(&sv_message);
3635 // if client is already spawned, re-send the
3636 // serverinfo message as they'll need it to play
3637 if (knownclient->begun)
3638 SV_SendServerinfo(knownclient);
3643 // this is a new client, check for connection flood
3644 if (NetConn_PreventFlood(peeraddress, sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0]), net_connectfloodblockingtimeout.value, true))
3647 // find a slot for the new client
3648 for (clientnum = 0, newclient = svs.clients;clientnum < svs.maxclients;clientnum++, newclient++)
3651 if (!newclient->active && (newclient->netconnection = conn = NetConn_Open(mysocket, peeraddress)) != NULL)
3653 // connect to the client
3654 // everything is allocated, just fill in the details
3655 dp_strlcpy (conn->address, addressstring2, sizeof (conn->address));
3656 if (developer_extra.integer)
3657 Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_ACCEPT to %s.\n", addressstring2);
3658 // send back the info about the server connection
3659 SZ_Clear(&sv_message);
3660 // save space for the header, filled in later
3661 MSG_WriteLong(&sv_message, 0);
3662 MSG_WriteByte(&sv_message, CCREP_ACCEPT);
3663 MSG_WriteLong(&sv_message, LHNETADDRESS_GetPort(LHNET_AddressFromSocket(conn->mysocket)));
3664 StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
3665 NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
3666 SZ_Clear(&sv_message);
3667 // now set up the client struct
3668 SV_ConnectClient(clientnum, conn);
3669 NetConn_Heartbeat(1);
3674 if (developer_extra.integer)
3675 Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Server is full.\" to %s.\n", addressstring2);
3676 // no room; try to let player know
3677 SZ_Clear(&sv_message);
3678 // save space for the header, filled in later
3679 MSG_WriteLong(&sv_message, 0);
3680 MSG_WriteByte(&sv_message, CCREP_REJECT);
3681 MSG_WriteString(&sv_message, "Server is full.\n");
3682 StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
3683 NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
3684 SZ_Clear(&sv_message);
3686 case CCREQ_SERVER_INFO:
3687 if (developer_extra.integer)
3688 Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_SERVER_INFO from %s.\n", addressstring2);
3689 if(!(islocal || sv_public.integer > -1))
3692 if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false))
3695 if (sv.active && !strcmp(MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)), "QUAKE"))
3698 char myaddressstring[128];
3699 if (developer_extra.integer)
3700 Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_SERVER_INFO to %s.\n", addressstring2);
3701 SZ_Clear(&sv_message);
3702 // save space for the header, filled in later
3703 MSG_WriteLong(&sv_message, 0);
3704 MSG_WriteByte(&sv_message, CCREP_SERVER_INFO);
3705 LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), myaddressstring, sizeof(myaddressstring), true);
3706 MSG_WriteString(&sv_message, myaddressstring);
3707 MSG_WriteString(&sv_message, hostname.string);
3708 MSG_WriteString(&sv_message, sv.name);
3709 // How many clients are there?
3710 for (i = 0, numclients = 0;i < svs.maxclients;i++)
3711 if (svs.clients[i].active)
3713 MSG_WriteByte(&sv_message, numclients);
3714 MSG_WriteByte(&sv_message, svs.maxclients);
3715 MSG_WriteByte(&sv_message, NET_PROTOCOL_VERSION);
3716 StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
3717 NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
3718 SZ_Clear(&sv_message);
3721 case CCREQ_PLAYER_INFO:
3722 if (developer_extra.integer)
3723 Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_PLAYER_INFO from %s.\n", addressstring2);
3724 if(!(islocal || sv_public.integer > -1))
3727 if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false))
3732 int playerNumber, activeNumber, clientNumber;
3735 playerNumber = MSG_ReadByte(&sv_message);
3737 for (clientNumber = 0, client = svs.clients; clientNumber < svs.maxclients; clientNumber++, client++)
3738 if (client->active && ++activeNumber == playerNumber)
3740 if (clientNumber != svs.maxclients)
3742 SZ_Clear(&sv_message);
3743 // save space for the header, filled in later
3744 MSG_WriteLong(&sv_message, 0);
3745 MSG_WriteByte(&sv_message, CCREP_PLAYER_INFO);
3746 MSG_WriteByte(&sv_message, playerNumber);
3747 MSG_WriteString(&sv_message, client->name);
3748 MSG_WriteLong(&sv_message, client->colors);
3749 MSG_WriteLong(&sv_message, client->frags);
3750 MSG_WriteLong(&sv_message, (int)(host.realtime - client->connecttime));
3751 if(sv_status_privacy.integer)
3752 MSG_WriteString(&sv_message, client->netconnection ? "hidden" : "botclient");
3754 MSG_WriteString(&sv_message, client->netconnection ? client->netconnection->address : "botclient");
3755 StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
3756 NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
3757 SZ_Clear(&sv_message);
3761 case CCREQ_RULE_INFO:
3762 if (developer_extra.integer)
3763 Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_RULE_INFO from %s.\n", addressstring2);
3764 if(!(islocal || sv_public.integer > -1))
3767 // no flood check here, as it only returns one cvar for one cvar and clients may iterate quickly
3774 // find the search start location
3775 prevCvarName = MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring));
3776 var = Cvar_FindVarAfter(&cvars_all, prevCvarName, CF_NOTIFY);
3778 // send the response
3779 SZ_Clear(&sv_message);
3780 // save space for the header, filled in later
3781 MSG_WriteLong(&sv_message, 0);
3782 MSG_WriteByte(&sv_message, CCREP_RULE_INFO);
3785 MSG_WriteString(&sv_message, var->name);
3786 MSG_WriteString(&sv_message, var->string);
3788 StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
3789 NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
3790 SZ_Clear(&sv_message);
3794 if (developer_extra.integer)
3795 Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_RCON from %s.\n", addressstring2);
3796 if (sv.active && !rcon_secure.integer)
3798 char password[2048];
3802 const char *userlevel;
3803 dp_strlcpy(password, MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)), sizeof(password));
3804 dp_strlcpy(cmd, MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)), sizeof(cmd));
3806 endpos = cmd + strlen(cmd) + 1; // one behind the NUL, so adding strlen+1 will eventually reach it
3807 userlevel = RCon_Authenticate(peeraddress, password, s, endpos, plaintext_matching, NULL, 0);
3808 RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, true);
3815 SZ_Clear(&sv_message);
3816 // we may not have liked the packet, but it was a valid control
3817 // packet, so we're done processing this packet now
3822 if ((ret = NetConn_ReceivedMessage(host_client->netconnection, data, length, sv.protocol, host_client->begun ? net_messagetimeout.value : net_connecttimeout.value)) == 2)
3824 SV_ReadClientMessage();
3831 void NetConn_ServerFrame(void)
3835 lhnetaddress_t peeraddress;
3836 unsigned char readbuffer[NET_HEADERSIZE+NET_MAXMESSAGE];
3838 for (i = 0;i < sv_numsockets;i++)
3839 while (sv_sockets[i] && (length = NetConn_Read(sv_sockets[i], readbuffer, sizeof(readbuffer), &peeraddress)) > 0)
3840 NetConn_ServerParsePacket(sv_sockets[i], readbuffer, length, &peeraddress);
3844 void NetConn_QueryMasters(qbool querydp, qbool queryqw)
3848 lhnetaddress_t masteraddress;
3850 char lookupstring[128];
3852 if (serverlist_cachecount >= SERVERLIST_TOTALSIZE)
3855 memset(dpmasterstatus, 0, sizeof(*dpmasterstatus));
3856 memset(qwmasterstatus, 0, sizeof(*qwmasterstatus));
3860 for (i = 0;i < cl_numsockets;i++)
3864 const char *cmdname, *extraoptions;
3865 lhnetaddresstype_t af = LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i]));
3867 // build the getservers message to send to the dpmaster master servers
3868 if (LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])) == LHNETADDRESSTYPE_INET6)
3870 cmdname = "getserversExt";
3871 extraoptions = " ipv4 ipv6"; // ask for IPv4 and IPv6 servers
3875 cmdname = "getservers";
3878 memcpy(request, "\377\377\377\377", 4);
3879 dpsnprintf(request+4, sizeof(request)-4, "%s %s %u empty full%s", cmdname, gamenetworkfiltername, NET_PROTOCOL_VERSION, extraoptions);
3882 for (masternum = 0; masternum < DPMASTER_COUNT; ++masternum)
3884 if(sv_masters[masternum].string[0]
3885 && LHNETADDRESS_FromString(&masteraddress, sv_masters[masternum].string, DPMASTER_PORT)
3886 && LHNETADDRESS_GetAddressType(&masteraddress) == af)
3888 if (serverlist_consoleoutput || net_slist_debug.integer)
3890 LHNETADDRESS_ToString(&masteraddress, lookupstring, sizeof(lookupstring), true);
3891 Con_Printf("Querying DP master %s (resolved from %s)\n", lookupstring, sv_masters[masternum].string);
3894 NetConn_WriteString(cl_sockets[i], request, &masteraddress);
3895 dpmasterstatus[masternum] = MASTER_TX_QUERY;
3899 // search favorite servers
3900 for(j = 0; j < nFavorites; ++j)
3901 if(LHNETADDRESS_GetAddressType(&favorites[j]) == af
3902 && LHNETADDRESS_ToString(&favorites[j], lookupstring, sizeof(lookupstring), true))
3903 NetConn_ClientParsePacket_ServerList_PrepareQuery(PROTOCOL_DARKPLACES7, lookupstring, true);
3908 // only query QuakeWorld servers when the user wants to
3911 dpsnprintf(request, sizeof(request), "c\n");
3913 for (i = 0;i < cl_numsockets;i++)
3917 lhnetaddresstype_t af = LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i]));
3920 for (masternum = 0; masternum < QWMASTER_COUNT; ++masternum)
3922 if(sv_qwmasters[masternum].string[0]
3923 && LHNETADDRESS_FromString(&masteraddress, sv_qwmasters[masternum].string, QWMASTER_PORT)
3924 && LHNETADDRESS_GetAddressType(&masteraddress) == af)
3926 if (serverlist_consoleoutput || net_slist_debug.integer)
3928 LHNETADDRESS_ToString(&masteraddress, lookupstring, sizeof(lookupstring), true);
3929 Con_Printf("Querying QW master %s (resolved from %s)\n", lookupstring, sv_qwmasters[masternum].string);
3932 NetConn_Write(cl_sockets[i], request, (int)strlen(request) + 1, &masteraddress);
3933 qwmasterstatus[masternum] = MASTER_TX_QUERY;
3937 // search favorite servers
3938 for(j = 0; j < nFavorites; ++j)
3939 if(LHNETADDRESS_GetAddressType(&favorites[j]) == af
3940 && LHNETADDRESS_ToString(&favorites[j], lookupstring, sizeof(lookupstring), true))
3941 NetConn_ClientParsePacket_ServerList_PrepareQuery(PROTOCOL_QUAKEWORLD, lookupstring, true);
3946 if (!masterquerycount)
3948 Con_Print(CON_ERROR "Unable to query master servers, no suitable network sockets active.\n");
3949 dp_strlcpy(cl_connect_status, "No network", sizeof(cl_connect_status));
3954 void NetConn_Heartbeat(int priority)
3956 lhnetaddress_t masteraddress;
3958 lhnetsocket_t *mysocket;
3960 // if it's a state change (client connected), limit next heartbeat to no
3961 // more than 30 sec in the future
3962 if (priority == 1 && nextheartbeattime > host.realtime + 30.0)
3963 nextheartbeattime = host.realtime + 30.0;
3965 // limit heartbeatperiod to 30 to 270 second range,
3966 // lower limit is to avoid abusing master servers with excess traffic,
3967 // upper limit is to avoid timing out on the master server (which uses
3969 if (sv_heartbeatperiod.value < 30)
3970 Cvar_SetValueQuick(&sv_heartbeatperiod, 30);
3971 if (sv_heartbeatperiod.value > 270)
3972 Cvar_SetValueQuick(&sv_heartbeatperiod, 270);
3974 // make advertising optional and don't advertise singleplayer games, and
3975 // only send a heartbeat as often as the admin wants
3976 if (sv.active && sv_public.integer > 0 && svs.maxclients >= 2 && (priority > 1 || host.realtime > nextheartbeattime))
3978 nextheartbeattime = host.realtime + sv_heartbeatperiod.value;
3979 for (masternum = 0; masternum < DPMASTER_COUNT; ++masternum)
3980 if (sv_masters[masternum].string[0]
3981 && LHNETADDRESS_FromString(&masteraddress, sv_masters[masternum].string, DPMASTER_PORT)
3982 && (mysocket = NetConn_ChooseServerSocketForAddress(&masteraddress)))
3983 NetConn_WriteString(mysocket, "\377\377\377\377heartbeat DarkPlaces\x0A", &masteraddress);
3987 static void Net_Heartbeat_f(cmd_state_t *cmd)
3990 NetConn_Heartbeat(2);
3992 Con_Print("No server running, can not heartbeat to master server.\n");
3995 static void PrintStats(netconn_t *conn)
3997 if ((cls.state == ca_connected && cls.protocol == PROTOCOL_QUAKEWORLD) || (sv.active && sv.protocol == PROTOCOL_QUAKEWORLD))
3998 Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->outgoing_unreliable_sequence, conn->qw.incoming_sequence);
4000 Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->nq.sendSequence, conn->nq.receiveSequence);
4001 Con_Printf("unreliable messages sent = %i\n", conn->unreliableMessagesSent);
4002 Con_Printf("unreliable messages recv = %i\n", conn->unreliableMessagesReceived);
4003 Con_Printf("reliable messages sent = %i\n", conn->reliableMessagesSent);
4004 Con_Printf("reliable messages received = %i\n", conn->reliableMessagesReceived);
4005 Con_Printf("packetsSent = %i\n", conn->packetsSent);
4006 Con_Printf("packetsReSent = %i\n", conn->packetsReSent);
4007 Con_Printf("packetsReceived = %i\n", conn->packetsReceived);
4008 Con_Printf("receivedDuplicateCount = %i\n", conn->receivedDuplicateCount);
4009 Con_Printf("droppedDatagrams = %i\n", conn->droppedDatagrams);
4012 void Net_Stats_f(cmd_state_t *cmd)
4015 Con_Print("connections =\n");
4016 for (conn = netconn_list;conn;conn = conn->next)
4021 void Net_Refresh_f(cmd_state_t *cmd)
4023 if (m_state != m_slist)
4025 Con_Print("Sending new requests to DP master servers\n");
4026 ServerList_QueryList(false, true, false, true);
4027 Con_Print("Listening for replies...\n");
4030 ServerList_QueryList(false, true, false, false);
4033 void Net_Slist_f(cmd_state_t *cmd)
4035 ServerList_ResetMasks();
4036 serverlist_sortbyfield = SLIF_PING;
4037 serverlist_sortflags = 0;
4038 if (m_state != m_slist)
4040 Con_Print("Sending requests to DP master servers\n");
4041 ServerList_QueryList(true, true, false, true);
4042 Con_Print("Listening for replies...\n");
4045 ServerList_QueryList(true, true, false, false);
4048 void Net_SlistQW_f(cmd_state_t *cmd)
4050 ServerList_ResetMasks();
4051 serverlist_sortbyfield = SLIF_PING;
4052 serverlist_sortflags = 0;
4053 if (m_state != m_slist)
4055 Con_Print("Sending requests to QW master servers\n");
4056 ServerList_QueryList(true, false, true, true);
4057 Con_Print("Listening for replies...\n");
4060 ServerList_QueryList(true, false, true, false);
4064 void NetConn_Init(void)
4068 lhnetaddress_t tempaddress;
4070 netconn_mempool = Mem_AllocPool("network connections", 0, NULL);
4071 Cmd_AddCommand(CF_SHARED, "net_stats", Net_Stats_f, "print network statistics");
4073 Cmd_AddCommand(CF_CLIENT, "net_slist", Net_Slist_f, "query dp master servers and print all server information");
4074 Cmd_AddCommand(CF_CLIENT, "net_slistqw", Net_SlistQW_f, "query qw master servers and print all server information");
4075 Cmd_AddCommand(CF_CLIENT, "net_refresh", Net_Refresh_f, "query dp master servers and refresh all server information");
4077 Cmd_AddCommand(CF_SERVER, "heartbeat", Net_Heartbeat_f, "send a heartbeat to the master server (updates your server information)");
4078 Cvar_RegisterVariable(&net_test);
4079 Cvar_RegisterVariable(&net_usesizelimit);
4080 Cvar_RegisterVariable(&net_burstreserve);
4081 Cvar_RegisterVariable(&rcon_restricted_password);
4082 Cvar_RegisterVariable(&rcon_restricted_commands);
4083 Cvar_RegisterVariable(&rcon_secure_maxdiff);
4086 Cvar_RegisterVariable(&net_slist_debug);
4087 Cvar_RegisterVariable(&net_slist_favorites);
4088 Cvar_RegisterCallback(&net_slist_favorites, NetConn_UpdateFavorites_c);
4089 Cvar_RegisterVariable(&net_slist_interval);
4090 Cvar_RegisterVariable(&net_slist_maxping);
4091 Cvar_RegisterVariable(&net_slist_maxtries);
4092 Cvar_RegisterVariable(&net_slist_pause);
4093 Cvar_RegisterCallback(&net_slist_pause, ServerList_RebuildViewList);
4094 Cvar_RegisterVariable(&net_slist_queriespersecond);
4095 Cvar_RegisterVariable(&net_slist_queriesperframe);
4096 Cvar_RegisterVariable(&net_slist_timeout);
4099 #ifdef IP_TOS // register cvar only if supported
4100 Cvar_RegisterVariable(&net_tos_dscp);
4102 Cvar_RegisterVariable(&net_messagetimeout);
4103 Cvar_RegisterVariable(&net_connecttimeout);
4104 Cvar_RegisterVariable(&net_connectfloodblockingtimeout);
4105 Cvar_RegisterVariable(&net_challengefloodblockingtimeout);
4106 Cvar_RegisterVariable(&net_getstatusfloodblockingtimeout);
4107 Cvar_RegisterVariable(&net_sourceaddresscheck);
4108 Cvar_RegisterVariable(&net_fakelag);
4109 Cvar_RegisterVariable(&net_fakeloss_send);
4110 Cvar_RegisterVariable(&net_fakeloss_receive);
4111 Cvar_RegisterVirtual(&net_fakelag, "cl_netlocalping");
4112 Cvar_RegisterVirtual(&net_fakeloss_send, "cl_netpacketloss_send");
4113 Cvar_RegisterVirtual(&net_fakeloss_receive, "cl_netpacketloss_receive");
4114 Cvar_RegisterVariable(&hostname);
4115 Cvar_RegisterVariable(&developer_networking);
4116 Cvar_RegisterVariable(&cl_netport);
4117 Cvar_RegisterCallback(&cl_netport, NetConn_CL_UpdateSockets_Callback);
4118 Cvar_RegisterVariable(&sv_netport);
4119 Cvar_RegisterCallback(&sv_netport, NetConn_sv_netport_Callback);
4120 Cvar_RegisterVariable(&net_address);
4121 Cvar_RegisterVariable(&net_address_ipv6);
4122 Cvar_RegisterVariable(&sv_public);
4123 Cvar_RegisterVariable(&sv_public_rejectreason);
4124 Cvar_RegisterVariable(&sv_heartbeatperiod);
4125 for (j = 0; j < DPMASTER_COUNT; ++j)
4126 Cvar_RegisterVariable(&sv_masters[j]);
4128 for (j = 0; j < QWMASTER_COUNT; ++j)
4129 Cvar_RegisterVariable(&sv_qwmasters[j]);
4131 Cvar_RegisterVariable(&gameversion);
4132 Cvar_RegisterVariable(&gameversion_min);
4133 Cvar_RegisterVariable(&gameversion_max);
4134 // 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.
4135 if ((i = Sys_CheckParm("-ip")) && i + 1 < sys.argc)
4137 if (LHNETADDRESS_FromString(&tempaddress, sys.argv[i + 1], 0) == 1)
4139 Con_Printf("-ip option used, setting net_address to \"%s\"\n", sys.argv[i + 1]);
4140 Cvar_SetQuick(&net_address, sys.argv[i + 1]);
4143 Con_Printf(CON_ERROR "-ip option used, but unable to parse the address \"%s\"\n", sys.argv[i + 1]);
4145 // 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
4146 if (((i = Sys_CheckParm("-port")) || (i = Sys_CheckParm("-ipport")) || (i = Sys_CheckParm("-udpport"))) && i + 1 < sys.argc)
4148 i = atoi(sys.argv[i + 1]);
4149 if (i >= 0 && i < 65536)
4151 Con_Printf("-port option used, setting port cvar to %i\n", i);
4152 Cvar_SetValueQuick(&sv_netport, i);
4155 Con_Printf(CON_ERROR "-port option used, but %i is not a valid port number\n", i);
4159 cl_message.data = cl_message_buf;
4160 cl_message.maxsize = sizeof(cl_message_buf);
4161 cl_message.cursize = 0;
4162 sv_message.data = sv_message_buf;
4163 sv_message.maxsize = sizeof(sv_message_buf);
4164 sv_message.cursize = 0;
4166 if (Thread_HasThreads())
4167 netconn_mutex = Thread_CreateMutex();
4170 void NetConn_Shutdown(void)
4172 NetConn_CloseClientPorts();
4173 NetConn_CloseServerPorts();
4176 Thread_DestroyMutex(netconn_mutex);
4177 netconn_mutex = NULL;