#define MASTER_PORT 27950
-cvar_t sv_public = {0, "sv_public", "1"};
+// note this defaults on for dedicated servers, off for listen servers
+cvar_t sv_public = {0, "sv_public", "0"};
static cvar_t sv_heartbeatperiod = {CVAR_SAVE, "sv_heartbeatperiod", "120"};
// FIXME: resolve DNS on masters whenever their value changes and cache it (to avoid major delays in active servers when they heartbeat)
cvar_t cl_netlocalping = {0, "cl_netlocalping","0"};
static cvar_t cl_netpacketloss = {0, "cl_netpacketloss","0"};
-
+static cvar_t net_slist_queriespersecond = {0, "net_slist_queriespersecond", "20"};
+static cvar_t net_slist_queriesperframe = {0, "net_slist_queriesperframe", "4"};
+static cvar_t net_slist_timeout = {0, "net_slist_timeout", "4"};
+static cvar_t net_slist_maxtries = {0, "net_slist_maxtries", "3"};
/* statistic counters */
static int packetsSent = 0;
int serverquerycount = 0;
int serverreplycount = 0;
+// this is only false if there are still servers left to query
+int serverlist_querysleep = true;
+
static qbyte sendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE];
static qbyte readbuffer[NET_HEADERSIZE+NET_MAXMESSAGE];
static qboolean _ServerList_CompareStr( const char *A, serverlist_maskop_t op, const char *B )
{
+ int i;
+ char bufferA[ 256 ], bufferB[ 256 ]; // should be more than enough
+ for (i = 0;i < sizeof(bufferA)-1 && A[i];i++)
+ bufferA[i] = (A[i] >= 'A' && A[i] <= 'Z') ? (A[i] + 'a' - 'A') : A[i];
+ bufferA[i] = 0;
+ for (i = 0;i < sizeof(bufferB)-1 && B[i];i++)
+ bufferB[i] = (B[i] >= 'A' && B[i] <= 'Z') ? (B[i] + 'a' - 'A') : B[i];
+ bufferB[i] = 0;
+
// Same here, also using an intermediate & final return would be more appropriate
// A info B mask
switch( op ) {
case SLMO_CONTAINS:
- return *B && !!strstr( A, B ); // we want a real bool
+ return *bufferB && !!strstr( bufferA, bufferB ); // we want a real bool
case SLMO_NOTCONTAIN:
- return !*B || !strstr( A, B );
+ return !*bufferB || !strstr( bufferA, bufferB );
case SLMO_LESS:
- return strcmp( A, B ) < 0;
+ return strcmp( bufferA, bufferB ) < 0;
case SLMO_LESSEQUAL:
- return strcmp( A, B ) <= 0;
+ return strcmp( bufferA, bufferB ) <= 0;
case SLMO_EQUAL:
- return strcmp( A, B ) == 0;
+ return strcmp( bufferA, bufferB ) == 0;
case SLMO_GREATER:
- return strcmp( A, B ) > 0;
+ return strcmp( bufferA, bufferB ) > 0;
case SLMO_NOTEQUAL:
- return strcmp( A, B ) != 0;
+ return strcmp( bufferA, bufferB ) != 0;
case SLMO_GREATEREQUAL:
- return strcmp( A, B ) >= 0;
+ return strcmp( bufferA, bufferB ) >= 0;
default:
Con_DPrint( "_ServerList_CompareStr: Bad op!\n" );
return false;
if( !serverlist_viewcount ) {
_ServerList_ViewList_Helper_InsertBefore( 0, entry );
return;
- }
+ }
// ok, insert it, we just need to find out where exactly:
// two special cases
serverlist_cachecount = 0;
serverlist_viewcount = 0;
serverlist_consoleoutput = false;
-
+
//_ServerList_Test();
-
+
NetConn_QueryMasters();
}
M_Update_Return_Reason("");
// the connection request succeeded, stop current connection and set up a new connection
CL_Disconnect();
+ // if we're connecting to a remote server, shut down any local server
+ if (LHNETADDRESS_GetAddressType(peeraddress) != LHNETADDRESSTYPE_LOOP && sv.active)
+ Host_ShutdownServer (false);
+ // allocate a net connection to keep track of things
cls.netcon = NetConn_Open(mysocket, peeraddress);
Con_Printf("Connection accepted to %s\n", cls.netcon->address);
key_dest = key_game;
// store the data the engine cares about (address and ping)
strlcpy (serverlist_cache[serverlist_cachecount].info.cname, ipstring, sizeof (serverlist_cache[serverlist_cachecount].info.cname));
serverlist_cache[serverlist_cachecount].info.ping = 100000;
- serverlist_cache[serverlist_cachecount].query = SQS_PENDING;
+ serverlist_cache[serverlist_cachecount].query = SQS_QUERYING;
++serverlist_cachecount;
}
data += 7;
length -= 7;
}
+ // begin or resume serverlist queries
+ serverlist_querysleep = false;
return true;
}
/*
return ret;
}
-#define QUERIES_PER_FRAME 4
void NetConn_QueryQueueFrame(void)
{
- int index = 0;
- int queries = 0;
- static float time = -1;
- int maxqueries = 0;
+ int index;
+ int queries;
+ int maxqueries;
+ double timeouttime;
+ static double querycounter = 0;
- if( time == -1 ) {
- time = realtime;
- }
+ if (serverlist_querysleep)
+ return;
- maxqueries = QUERIES_PER_FRAME * (realtime - time );
+ // each time querycounter reaches 1.0 issue a query
+ querycounter += host_realframetime * net_slist_queriespersecond.value;
+ maxqueries = (int)querycounter;
+ maxqueries = bound(0, maxqueries, net_slist_queriesperframe.integer);
+ querycounter -= maxqueries;
if( maxqueries == 0 ) {
return;
}
- for( ; index < serverlist_cachecount && queries < QUERIES_PER_FRAME ; index++ )
+ // scan serverlist and issue queries as needed
+ serverlist_querysleep = true;
+
+ timeouttime = realtime - net_slist_timeout.value;
+ for( index = 0, queries = 0 ; index < serverlist_cachecount && queries < maxqueries ; index++ )
{
- if( serverlist_cache[ index ].query == SQS_PENDING )
+ serverlist_entry_t *entry = &serverlist_cache[ index ];
+ if( entry->query != SQS_QUERYING )
+ {
+ continue;
+ }
+
+ serverlist_querysleep = false;
+ if( entry->querycounter != 0 && entry->querytime > timeouttime )
+ {
+ continue;
+ }
+
+ if( entry->querycounter != (unsigned) net_slist_maxtries.integer )
{
lhnetaddress_t address;
int socket;
- LHNETADDRESS_FromString(&address, serverlist_cache[ index ].info.cname, 0);
+ LHNETADDRESS_FromString(&address, entry->info.cname, 0);
for (socket = 0; socket < cl_numsockets ; socket++) {
NetConn_WriteString(cl_sockets[socket], "\377\377\377\377getinfo", &address);
}
-
- serverlist_cache[ index ].querytime = realtime;
- serverlist_cache[ index ].query = SQS_QUERYING;
+
+ entry->querytime = realtime;
+ entry->querycounter++;
// if not in the slist menu we should print the server to console
if (serverlist_consoleoutput)
- Con_Printf("querying %s\n", serverlist_cache[ index ].info.cname);
-
+ Con_Printf("querying %25s (%i. try)\n", entry->info.cname, entry->querycounter);
queries++;
}
+ else
+ {
+ entry->query = SQS_TIMEDOUT;
+ }
}
-
- time = realtime;
}
void NetConn_ClientFrame(void)
buffer[i] = 0;
}
+static qboolean NetConn_BuildStatusResponse(const char* challenge, char* out_msg, size_t out_size, qboolean fullstatus)
+{
+ unsigned int nb_clients = 0, i;
+ int length;
+
+ // How many clients are there?
+ for (i = 0;i < (unsigned int)svs.maxclients;i++)
+ if (svs.clients[i].active)
+ nb_clients++;
+
+ // TODO: we should add more information for the full status string
+ length = dpsnprintf(out_msg, out_size,
+ "\377\377\377\377%s\x0A"
+ "\\gamename\\%s\\modname\\%s\\sv_maxclients\\%d"
+ "\\clients\\%d\\mapname\\%s\\hostname\\%s""\\protocol\\%d"
+ "%s%s"
+ "%s",
+ fullstatus ? "statusResponse" : "infoResponse",
+ gamename, com_modname, svs.maxclients,
+ nb_clients, sv.name, hostname.string, NET_PROTOCOL_VERSION,
+ challenge ? "\\challenge\\" : "", challenge ? challenge : "",
+ fullstatus ? "\n" : "");
+
+ // Make sure it fits in the buffer
+ if (length < 0)
+ return false;
+
+ if (fullstatus)
+ {
+ char *ptr;
+ int left;
+
+ ptr = out_msg + length;
+ left = out_size - length;
+
+ for (i = 0;i < (unsigned int)svs.maxclients;i++)
+ {
+ client_t *cl = &svs.clients[i];
+ if (cl->active)
+ {
+ int nameind, cleanind;
+ char curchar;
+ char cleanname [sizeof(cl->name)];
+
+ // Remove all characters '"' and '\' in the player name
+ nameind = 0;
+ cleanind = 0;
+ do
+ {
+ curchar = cl->name[nameind++];
+ if (curchar != '"' && curchar != '\\')
+ {
+ cleanname[cleanind++] = curchar;
+ if (cleanind == sizeof(cleanname) - 1)
+ break;
+ }
+ } while (curchar != '\0');
+
+ length = dpsnprintf(ptr, left, "%d %d \"%s\"\n",
+ cl->frags,
+ (int)(cl->ping * 1000.0f),
+ cleanname);
+ if(length < 0)
+ return false;
+ left -= length;
+ ptr += length;
+ }
+ }
+ }
+
+ return true;
+}
+
extern void SV_SendServerinfo (client_t *client);
int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, qbyte *data, int length, lhnetaddress_t *peeraddress)
{
- int i, n, ret, clientnum, responselength, best;
+ int i, ret, clientnum, best;
double besttime;
client_t *client;
netconn_t *conn;
if (length >= 7 && !memcmp(string, "getinfo", 7))
{
const char *challenge = NULL;
+
// If there was a challenge in the getinfo message
if (length > 8 && string[7] == ' ')
challenge = string + 8;
- for (i = 0, n = 0;i < svs.maxclients;i++)
- if (svs.clients[i].active)
- n++;
- responselength = dpsnprintf(response, sizeof(response), "\377\377\377\377infoResponse\x0A"
- "\\gamename\\%s\\modname\\%s\\sv_maxclients\\%d"
- "\\clients\\%d\\mapname\\%s\\hostname\\%s\\protocol\\%d%s%s",
- gamename, com_modname, svs.maxclients, n,
- sv.name, hostname.string, NET_PROTOCOL_VERSION, challenge ? "\\challenge\\" : "", challenge ? challenge : "");
- // does it fit in the buffer?
- if (responselength >= 0)
+
+ if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), false))
{
if (developer.integer)
Con_Printf("Sending reply to master %s - %s\n", addressstring2, response);
}
return true;
}
+ if (length >= 9 && !memcmp(string, "getstatus", 9))
+ {
+ const char *challenge = NULL;
+
+ // If there was a challenge in the getinfo message
+ if (length > 10 && string[9] == ' ')
+ challenge = string + 10;
+
+ if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), true))
+ {
+ if (developer.integer)
+ Con_Printf("Sending reply to client %s - %s\n", addressstring2, response);
+ NetConn_WriteString(mysocket, response, peeraddress);
+ }
+ return true;
+ }
/*
if (!strncmp(string, "ping", 4))
{
Cmd_AddCommand("net_stats", Net_Stats_f);
Cmd_AddCommand("net_slist", Net_Slist_f);
Cmd_AddCommand("heartbeat", Net_Heartbeat_f);
+ Cvar_RegisterVariable(&net_slist_queriespersecond);
+ Cvar_RegisterVariable(&net_slist_queriesperframe);
+ Cvar_RegisterVariable(&net_slist_timeout);
+ Cvar_RegisterVariable(&net_slist_maxtries);
Cvar_RegisterVariable(&net_messagetimeout);
Cvar_RegisterVariable(&net_messagerejointimeout);
Cvar_RegisterVariable(&net_connecttimeout);