#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 void _ServerList_ViewList_Helper_InsertBefore( int index, serverlist_entry_t *entry )
{
int i;
- if( serverlist_viewcount == SERVERLIST_VIEWLISTSIZE )
- return;
+ if( serverlist_viewcount < SERVERLIST_VIEWLISTSIZE ) {
+ i = serverlist_viewcount++;
+ } else {
+ i = SERVERLIST_VIEWLISTSIZE - 1;
+ }
- for( i = serverlist_viewcount ; i > index ; i-- )
+ for( ; i > index ; i-- )
serverlist_viewlist[ i ] = serverlist_viewlist[ i - 1 ];
serverlist_viewlist[index] = entry;
- serverlist_viewcount++;
}
// we suppose serverlist_viewcount to be valid, ie > 0
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;
{
int start, end, mid;
- if( serverlist_viewcount == SERVERLIST_VIEWLISTSIZE )
- return;
-
+ // FIXME: change this to be more readable (...)
// now check whether it passes through the masks
for( start = 0 ; serverlist_andmasks[start].active && start < SERVERLIST_ANDMASKCOUNT ; start++ )
if( !_ServerList_Entry_Mask( &serverlist_andmasks[start], &entry->info ) )
serverlist_viewcount = 0;
for( i = 0 ; i < serverlist_cachecount ; i++ )
- if( serverlist_cache[i].finished )
+ if( serverlist_cache[i].query == SQS_QUERIED )
ServerList_ViewList_Insert( &serverlist_cache[i] );
}
int i;
for( i = 0 ; i < 1024 ; i++ ) {
memset( &serverlist_cache[serverlist_cachecount], 0, sizeof( serverlist_entry_t ) );
- serverlist_cache[serverlist_cachecount].info.ping = rand() % 450 + 250;
+ serverlist_cache[serverlist_cachecount].info.ping = 1000 + 1024 - i;
dpsnprintf( serverlist_cache[serverlist_cachecount].info.name, 128, "Black's ServerList Test %i", i );
serverlist_cache[serverlist_cachecount].finished = true;
sprintf( serverlist_cache[serverlist_cachecount].line1, "%i %s", serverlist_cache[serverlist_cachecount].info.ping, serverlist_cache[serverlist_cachecount].info.name );
serverlist_cachecount = 0;
serverlist_viewcount = 0;
serverlist_consoleoutput = false;
- NetConn_QueryMasters();
//_ServerList_Test();
+
+ NetConn_QueryMasters();
}
// rest
//#ifdef DEBUG
if (data->cursize == 0)
- Sys_Error("Datagram_SendMessage: zero length message\n");
+ {
+ Con_Printf ("Datagram_SendMessage: zero length message\n");
+ return -1;
+ }
if (data->cursize > (int)sizeof(conn->sendMessage))
- Sys_Error("Datagram_SendMessage: message too big (%u > %u)\n", data->cursize, sizeof(conn->sendMessage));
+ {
+ Con_Printf ("Datagram_SendMessage: message too big (%u > %u)\n", data->cursize, sizeof(conn->sendMessage));
+ return -1;
+ }
if (conn->canSend == false)
- Sys_Error("SendMessage: called with canSend == false\n");
+ {
+ Con_Printf ("SendMessage: called with canSend == false\n");
+ return -1;
+ }
//#endif
memcpy(conn->sendMessage, data->data, data->cursize);
//#ifdef DEBUG
if (data->cursize == 0)
- Sys_Error("Datagram_SendUnreliableMessage: zero length message\n");
+ {
+ Con_Printf ("Datagram_SendUnreliableMessage: zero length message\n");
+ return -1;
+ }
if (packetLen > (int)sizeof(sendbuffer))
- Sys_Error("Datagram_SendUnreliableMessage: message too big %u\n", data->cursize);
+ {
+ Con_Printf ("Datagram_SendUnreliableMessage: message too big %u\n", data->cursize);
+ return -1;
+ }
//#endif
header = (void *)sendbuffer;
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;
int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, qbyte *data, int length, lhnetaddress_t *peeraddress)
{
int ret, c, control;
- lhnetaddress_t svaddress;
const char *s;
char *string, addressstring2[128], cname[128], ipstring[32];
char stringbuf[16384];
// find a slot
if( serverlist_cachecount == SERVERLIST_TOTALSIZE )
return true;
- serverquerycount++;
memset(&serverlist_cache[serverlist_cachecount], 0, sizeof(serverlist_cache[serverlist_cachecount]));
// store the data the engine cares about (address and ping)
}
++serverlist_cachecount;
-
}
+
info = &serverlist_cache[n].info;
if ((s = SearchInfostring(string, "gamename" )) != NULL) strlcpy(info->game, s, sizeof (info->game));else info->game[0] = 0;
if ((s = SearchInfostring(string, "modname" )) != NULL) strlcpy(info->mod , s, sizeof (info->mod ));else info->mod[0] = 0;
if (serverlist_cache[n].line1[i] != ' ')
serverlist_cache[n].line1[i] -= 30;
}
- // and finally, update the view set
- if( serverlist_cache[n].finished )
- ServerList_ViewList_Remove( &serverlist_cache[n] );
- // else if not in the slist menu we should print the server to console (if wanted)
+ if( serverlist_cache[n].query == SQS_QUERIED ) {
+ ServerList_ViewList_Remove( &serverlist_cache[n] );
+ }
+ // if not in the slist menu we should print the server to console (if wanted)
else if( serverlist_consoleoutput )
Con_Printf("%s\n%s\n", serverlist_cache[n].line1, serverlist_cache[n].line2);
+ // and finally, update the view set
ServerList_ViewList_Insert( &serverlist_cache[n] );
- serverlist_cache[n].finished = true;
+ serverlist_cache[n].query = SQS_QUERIED;
return true;
}
{
serverquerycount++;
- LHNETADDRESS_FromString(&svaddress, ipstring, 0);
- NetConn_WriteString(mysocket, "\377\377\377\377getinfo", &svaddress);
-
memset(&serverlist_cache[serverlist_cachecount], 0, sizeof(serverlist_cache[serverlist_cachecount]));
// 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].querytime = realtime;
- // if not in the slist menu we should print the server to console
- if (serverlist_consoleoutput)
- Con_Printf("querying %s\n", ipstring);
+ 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;
}
+void NetConn_QueryQueueFrame(void)
+{
+ int index;
+ int queries;
+ int maxqueries;
+ double timeouttime;
+ static double querycounter = 0;
+
+ if (serverlist_querysleep)
+ return;
+
+ // 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;
+ }
+
+ // scan serverlist and issue queries as needed
+ serverlist_querysleep = true;
+
+ timeouttime = realtime - net_slist_timeout.value;
+ for( index = 0, queries = 0 ; index < serverlist_cachecount && queries < maxqueries ; index++ )
+ {
+ serverlist_entry_t *entry = &serverlist_cache[ index ];
+ if( entry->query != SQS_QUERYING )
+ {
+ continue;
+ }
+
+ serverlist_querysleep = false;
+ if( entry->querycounter != 0 && entry->querytime > timeouttime )
+ {
+ continue;
+ }
+
+ if( entry->querycounter != (unsigned) net_slist_maxtries.integer )
+ {
+ lhnetaddress_t address;
+ int socket;
+
+ LHNETADDRESS_FromString(&address, entry->info.cname, 0);
+ for (socket = 0; socket < cl_numsockets ; socket++) {
+ NetConn_WriteString(cl_sockets[socket], "\377\377\377\377getinfo", &address);
+ }
+
+ entry->querytime = realtime;
+ entry->querycounter++;
+
+ // if not in the slist menu we should print the server to console
+ if (serverlist_consoleoutput)
+ Con_Printf("querying %25s (%i. try)\n", entry->info.cname, entry->querycounter);
+
+ queries++;
+ }
+ else
+ {
+ entry->query = SQS_TIMEDOUT;
+ }
+ }
+}
+
void NetConn_ClientFrame(void)
{
int i, length;
NetConn_Write(cls.connect_mysocket, net_message.data, net_message.cursize, &cls.connect_address);
SZ_Clear(&net_message);
}
- for (i = 0;i < cl_numsockets;i++)
- while (cl_sockets[i] && (length = NetConn_Read(cl_sockets[i], readbuffer, sizeof(readbuffer), &peeraddress)) > 0)
+ for (i = 0;i < cl_numsockets;i++) {
+ while (cl_sockets[i] && (length = NetConn_Read(cl_sockets[i], readbuffer, sizeof(readbuffer), &peeraddress)) > 0) {
NetConn_ClientParsePacket(cl_sockets[i], readbuffer, length, &peeraddress);
+ }
+ }
+ NetConn_QueryQueueFrame();
if (cls.netcon && realtime > cls.netcon->timeout)
{
Con_Print("Connection timed out\n");
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);