engine: support querying legacy goldsrc master servers

* General refactoring, hide out of band messages under cl_log_outofband cvar, similar to sv_log_outofband.
This commit is contained in:
Alibek Omarov
2025-10-03 21:54:54 +05:00
parent 62a49d234f
commit cc5f95d1ca
5 changed files with 257 additions and 162 deletions

View File

@@ -98,13 +98,12 @@ CVAR_DEFINE_AUTO( rate, "25000", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE,
static CVAR_DEFINE_AUTO( cl_ticket_generator, "revemu2013", FCVAR_ARCHIVE, "you wouldn't steal a car" );
static CVAR_DEFINE_AUTO( cl_advertise_engine_in_name, "1", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, "add [Xash3D] to the nickname when connecting to GoldSrc servers" );
static CVAR_DEFINE_AUTO( cl_log_outofband, "0", FCVAR_ARCHIVE, "log out of band messages, can be useful for server admins and for engine debugging" );
client_t cl;
client_static_t cls;
clgame_static_t clgame;
static void CL_SendMasterServerScanRequest( void );
//======================================================================
int GAME_EXPORT CL_Active( void )
{
@@ -1232,7 +1231,13 @@ static void CL_CheckForResend( void )
qboolean bandwidthTest;
if( cls.internetservers_wait )
CL_SendMasterServerScanRequest();
{
cls.internetservers_wait = NET_MasterQuery(
cls.internetservers_key,
cls.internetservers_nat,
cls.internetservers_customfilter
);
}
// if the local server is running and we aren't then connect
if( cls.state == ca_disconnected && SV_Active( ))
@@ -1755,60 +1760,6 @@ static void CL_LocalServers_f( void )
Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_VERSION );
}
/*
=================
CL_BuildMasterServerScanRequest
=================
*/
static size_t NONNULL CL_BuildMasterServerScanRequest( char *buf, size_t size, uint32_t *key, qboolean nat, const char *filter )
{
size_t remaining;
char *info, temp[32];
if( unlikely( size < sizeof( MS_SCAN_REQUEST )))
return 0;
Q_strncpy( buf, MS_SCAN_REQUEST, size );
info = buf + sizeof( MS_SCAN_REQUEST ) - 1;
remaining = size - sizeof( MS_SCAN_REQUEST );
Q_strncpy( info, filter, remaining );
*key = COM_RandomLong( 0, 0x7FFFFFFF );
#ifndef XASH_ALL_SERVERS
Info_SetValueForKey( info, "gamedir", GI->gamefolder, remaining );
#endif
// let master know about client version
Info_SetValueForKey( info, "clver", XASH_VERSION, remaining );
Info_SetValueForKey( info, "nat", nat ? "1" : "0", remaining );
Info_SetValueForKey( info, "commit", g_buildcommit, remaining );
Info_SetValueForKey( info, "branch", g_buildbranch, remaining );
Info_SetValueForKey( info, "os", Q_buildos(), remaining );
Info_SetValueForKey( info, "arch", Q_buildarch(), remaining );
Q_snprintf( temp, sizeof( temp ), "%d", Q_buildnum() );
Info_SetValueForKey( info, "buildnum", temp, remaining );
Q_snprintf( temp, sizeof( temp ), "%x", *key );
Info_SetValueForKey( info, "key", temp, remaining );
return sizeof( MS_SCAN_REQUEST ) + Q_strlen( info );
}
/*
=================
CL_SendMasterServerScanRequest
=================
*/
static void CL_SendMasterServerScanRequest( void )
{
cls.internetservers_wait = NET_SendToMasters( NS_CLIENT,
cls.internetservers_query_len, cls.internetservers_query );
cls.internetservers_pending = true;
}
/*
=================
CL_InternetServers_f
@@ -1816,24 +1767,42 @@ CL_InternetServers_f
*/
static void CL_InternetServers_f( void )
{
qboolean nat = cl_nat.value != 0.0f;
uint32_t key;
if( Cmd_Argc( ) > 2 || ( Cmd_Argc( ) == 2 && !Info_IsValid( Cmd_Argv( 1 ))))
{
Con_Printf( S_USAGE "internetservers [filter]\n" );
return;
}
cls.internetservers_query_len = CL_BuildMasterServerScanRequest(
cls.internetservers_query, sizeof( cls.internetservers_query ),
&cls.internetservers_key, nat, Cmd_Argv( 1 ));
Con_Printf( "Scanning for servers on the internet area...\n" );
NET_Config( true, true ); // allow remote
CL_SendMasterServerScanRequest();
cls.internetservers_nat = cl_nat.value != 0.0f;
cls.internetservers_pending = true;
cls.internetservers_key = COM_RandomLong( 0, 0xFFFFFFFF );
Q_strncpy( cls.internetservers_customfilter, Cmd_Argv( 1 ), sizeof( cls.internetservers_customfilter ));
cls.internetservers_wait = NET_MasterQuery(
cls.internetservers_key,
cls.internetservers_nat,
cls.internetservers_customfilter
);
}
static void CL_QueryServer( netadr_t adr, connprotocol_t proto )
{
switch( proto )
{
case PROTO_GOLDSRC:
Netchan_OutOfBand( NS_CLIENT, adr, sizeof( A2S_GOLDSRC_INFO ), A2S_GOLDSRC_INFO ); // includes null terminator!
break;
case PROTO_LEGACY:
Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_LEGACY_VERSION );
break;
case PROTO_CURRENT:
Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_VERSION );
break;
}
}
static void CL_QueryServer_f( void )
@@ -1861,18 +1830,7 @@ static void CL_QueryServer_f( void )
if( !CL_StringToProtocol( Cmd_Argv( 2 ), &proto ))
return;
switch( proto )
{
case PROTO_GOLDSRC:
Netchan_OutOfBand( NS_CLIENT, adr, sizeof( A2S_GOLDSRC_INFO ), A2S_GOLDSRC_INFO ); // includes null terminator!
break;
case PROTO_LEGACY:
Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_LEGACY_VERSION );
break;
case PROTO_CURRENT:
Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_VERSION );
break;
}
CL_QueryServer( adr, proto );
}
/*
@@ -2059,6 +2017,9 @@ static void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg )
password = MSG_ReadByte( msg );
Q_strncpy( version, MSG_ReadString( msg ), sizeof( version ));
if( maxcl > MAX_CLIENTS || numcl > MAX_CLIENTS )
return;
if( MSG_CheckOverflow( msg ))
{
Con_Printf( "%s: malfored info packet from %s\n", __func__, NET_AdrToString( from ));
@@ -2466,29 +2427,34 @@ static void CL_Reject( const char *c, const char *args, netadr_t from )
static void CL_ServerList( netadr_t from, sizebuf_t *msg )
{
if( !NET_IsMasterAdr( from ))
connprotocol_t proto;
if( !NET_IsMasterAdr( from, &proto ))
{
Con_Printf( S_WARN "unexpected server list packet from %s\n", NET_AdrToString( from ));
return;
}
// check the extra header
if( MSG_ReadByte( msg ) == 0x7f )
if( proto == PROTO_CURRENT )
{
uint32_t key = MSG_ReadDword( msg );
if( cls.internetservers_key != key )
if( MSG_ReadByte( msg ) == 0x7f )
{
Con_Printf( S_WARN "unexpected server list packet from %s (invalid key)\n", NET_AdrToString( from ));
uint32_t key = MSG_ReadDword( msg );
if( cls.internetservers_key != key )
{
Con_Printf( S_WARN "unexpected server list packet from %s (invalid key)\n", NET_AdrToString( from ));
return;
}
MSG_ReadByte( msg ); // reserved byte
}
else
{
Con_Printf( S_WARN "invalid server list packet from %s (missing extra header)\n", NET_AdrToString( from ));
return;
}
MSG_ReadByte( msg ); // reserved byte
}
else
{
Con_Printf( S_WARN "invalid server list packet from %s (missing extra header)\n", NET_AdrToString( from ));
return;
}
// serverlist got from masterserver
@@ -2515,7 +2481,7 @@ static void CL_ServerList( netadr_t from, sizebuf_t *msg )
break;
NET_Config( true, false ); // allow remote
Netchan_OutOfBandPrint( NS_CLIENT, servadr, A2A_INFO" %i", PROTOCOL_VERSION );
CL_QueryServer( servadr, proto );
}
if( cls.internetservers_pending )
@@ -2545,7 +2511,8 @@ static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
Cmd_TokenizeString( args );
c = Cmd_Argv( 0 );
Con_Reportf( "%s: %s : %s\n", __func__, NET_AdrToString( from ), c );
if( cl_log_outofband.value )
Con_Reportf( "%s: %s : %s\n", __func__, NET_AdrToString( from ), c );
// server connection
if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ) || !Q_strcmp( c, S2C_CONNECTION ))
@@ -2611,7 +2578,7 @@ static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
if( len > 0 )
Netchan_OutOfBand( NS_SERVER, from, len, (byte *)buf );
}
else
else if( cl_log_outofband.value )
{
Con_DPrintf( S_ERROR "bad connectionless packet from %s:\n%s\n", NET_AdrToString( from ), args );
}
@@ -3376,6 +3343,7 @@ static void CL_InitLocal( void )
Cvar_RegisterVariable( &cl_ticket_generator );
Cvar_RegisterVariable( &cl_advertise_engine_in_name );
Cvar_RegisterVariable( &cl_log_outofband );
Cvar_RegisterVariable( &showpause );
Cvar_RegisterVariable( &mp_decals );

View File

@@ -622,11 +622,11 @@ typedef struct
file_t *demofile;
file_t *demoheader; // contain demo startup info in case we record a demo on this level
qboolean internetservers_wait; // internetservers is waiting for dns request
qboolean internetservers_wait; // internetservers is waiting for dns request
qboolean internetservers_pending; // if true, clean master server pings
uint32_t internetservers_key; // compare key to validate master server reply
char internetservers_query[512]; // cached query
uint32_t internetservers_query_len;
qboolean internetservers_nat;
string internetservers_customfilter;
uint32_t internetservers_key; // compare key to validate master server reply
// multiprotocol support
connprotocol_t legacymode;

View File

@@ -892,12 +892,12 @@ intptr_t V_GetGammaPtr( int parm );
//
void NET_InitMasters( void );
void NET_SaveMasters( void );
qboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data );
qboolean NET_IsMasterAdr( netadr_t adr );
qboolean NET_IsMasterAdr( netadr_t adr, connprotocol_t *proto );
void NET_MasterHeartbeat( void );
void NET_MasterClear( void );
void NET_MasterShutdown( void );
qboolean NET_GetMaster( netadr_t from, uint *challenge, double *last_heartbeat );
qboolean NET_MasterQuery( uint32_t key, qboolean net, const char *filter );
//
// munge.c

View File

@@ -22,6 +22,7 @@ typedef struct master_s
qboolean sent; // TODO: get rid of this internal state
qboolean save;
qboolean v6only;
qboolean gs;
string address;
netadr_t adr; // temporary, rewritten after each send
@@ -31,7 +32,7 @@ typedef struct master_s
static struct masterlist_s
{
master_t *list;
master_t *head, *tail;
qboolean modified;
} ml;
@@ -39,6 +40,43 @@ static CVAR_DEFINE_AUTO( sv_verbose_heartbeats, "0", 0, "print every heartbeat t
#define HEARTBEAT_SECONDS ((sv_nat.value > 0.0f) ? 60.0f : 300.0f) // 1 or 5 minutes
static size_t NET_BuildMasterServerScanRequest( char *buf, size_t size, uint32_t key, qboolean nat, const char *filter, connprotocol_t proto )
{
size_t remaining;
char *info, temp[32];
// TODO: pagination and region
Q_strncpy( buf, MS_SCAN_REQUEST, size );
info = buf + sizeof( MS_SCAN_REQUEST ) - 1;
remaining = size - sizeof( MS_SCAN_REQUEST );
Q_strncpy( info, filter, remaining );
#ifndef XASH_ALL_SERVERS
Info_SetValueForKey( info, "gamedir", GI->gamefolder, remaining );
#endif // XASH_ALL_SERVERS
if( proto != PROTO_GOLDSRC )
{
// let master know about client version
Info_SetValueForKey( info, "clver", XASH_VERSION, remaining );
Info_SetValueForKey( info, "nat", nat ? "1" : "0", remaining );
Info_SetValueForKey( info, "commit", g_buildcommit, remaining );
Info_SetValueForKey( info, "branch", g_buildbranch, remaining );
Info_SetValueForKey( info, "os", Q_buildos(), remaining );
Info_SetValueForKey( info, "arch", Q_buildarch(), remaining );
Q_snprintf( temp, sizeof( temp ), "%d", Q_buildnum() );
Info_SetValueForKey( info, "buildnum", temp, remaining );
Q_snprintf( temp, sizeof( temp ), "%x", key );
Info_SetValueForKey( info, "key", temp, remaining );
}
return sizeof( MS_SCAN_REQUEST ) + Q_strlen( info );
}
/*
========================
NET_GetMasterHostByName
@@ -66,28 +104,39 @@ Send request to all masterservers list
return true if would block
========================
*/
qboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data )
static qboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data, connprotocol_t proto )
{
master_t *list;
master_t *master;
qboolean wait = false;
for( list = ml.list; list; list = list->next )
for( master = ml.head; master; master = master->next )
{
if( list->sent )
if( master->sent )
continue;
switch( NET_GetMasterHostByName( list ))
if( master->gs )
{
if( proto != PROTO_GOLDSRC )
continue;
}
else
{
if( proto != PROTO_CURRENT )
continue;
}
switch( NET_GetMasterHostByName( master ))
{
case NET_EAI_AGAIN:
list->sent = false;
master->sent = false;
wait = true;
break;
case NET_EAI_NONAME:
list->sent = true;
master->sent = true;
break;
case NET_EAI_OK:
list->sent = true;
NET_SendPacket( sock, len, data, list->adr );
master->sent = true;
NET_SendPacket( sock, len, data, master->adr );
break;
}
}
@@ -95,8 +144,8 @@ qboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data )
if( !wait )
{
// reset sent state
for( list = ml.list; list; list = list->next )
list->sent = false;
for( master = ml.head; master; master = master->next )
master->sent = false;
}
return wait;
@@ -138,10 +187,34 @@ void NET_MasterClear( void )
{
master_t *m;
for( m = ml.list; m; m = m->next )
for( m = ml.head; m; m = m->next )
m->last_heartbeat = MAX_HEARTBEAT;
}
/*
=================
NET_MasterQuery
=================
*/
qboolean NET_MasterQuery( uint32_t key, qboolean nat, const char *filter )
{
char buf[512];
size_t len;
qboolean wait = false;
len = NET_BuildMasterServerScanRequest( buf, sizeof( buf ), key, nat, filter, PROTO_CURRENT );
wait = NET_SendToMasters( NS_CLIENT, len, buf, PROTO_CURRENT );
// goldsrc don't have nat traversal extensions
if( !nat )
{
len = NET_BuildMasterServerScanRequest( buf, sizeof( buf ), 0, false, filter, PROTO_GOLDSRC );
wait = NET_SendToMasters( NS_CLIENT, len, buf, PROTO_GOLDSRC );
}
return wait;
}
/*
========================
NET_MasterHeartbeat
@@ -155,11 +228,14 @@ void NET_MasterHeartbeat( void )
if(( !public_server.value && !sv_nat.value ) || svs.maxclients == 1 )
return; // only public servers send heartbeats
for( m = ml.list; m; m = m->next )
for( m = ml.head; m; m = m->next )
{
if( host.realtime - m->last_heartbeat < HEARTBEAT_SECONDS )
continue;
if( m->gs )
continue;
switch( NET_GetMasterHostByName( m ))
{
case NET_EAI_AGAIN:
@@ -190,7 +266,7 @@ Informs all masters that this server is going down
void NET_MasterShutdown( void )
{
NET_Config( true, false ); // allow remote
while( NET_SendToMasters( NS_SERVER, 2, "\x62\x0A" ));
while( NET_SendToMasters( NS_SERVER, 2, "\x62\x0A", PROTO_CURRENT ));
}
@@ -204,7 +280,7 @@ static master_t *NET_GetMasterFromAdr( netadr_t adr )
{
master_t *master;
for( master = ml.list; master; master = master->next )
for( master = ml.head; master; master = master->next )
{
if( NET_CompareAdr( adr, master->adr ))
return master;
@@ -240,9 +316,17 @@ NET_IsMasterAdr
========================
*/
qboolean NET_IsMasterAdr( netadr_t adr )
qboolean NET_IsMasterAdr( netadr_t adr, connprotocol_t *proto )
{
return NET_GetMasterFromAdr( adr ) != NULL;
master_t *master = NET_GetMasterFromAdr( adr );
if( !master )
return false;
if( proto )
*proto = master->gs ? PROTO_GOLDSRC : PROTO_CURRENT;
return true;
}
/*
@@ -252,40 +336,48 @@ NET_AddMaster
Add master to the list
========================
*/
static void NET_AddMaster( const char *addr, qboolean save, qboolean v6only )
static master_t *NET_AddMaster( const char *addr )
{
master_t *master, *last;
master_t *master;
for( last = ml.list; last && last->next; last = last->next )
for( master = ml.head; master; master = master->next )
{
if( !Q_strcmp( last->address, addr ) ) // already exists
return;
if( !Q_stricmp( master->address, addr )) // already exists
return master;
}
master = Mem_Malloc( host.mempool, sizeof( *master ) );
master = Mem_Calloc( host.mempool, sizeof( *master ));
Q_strncpy( master->address, addr, sizeof( master->address ));
master->sent = false;
master->save = save;
master->v6only = v6only;
master->next = NULL;
master->adr.type = 0;
// link in
if( last )
last->next = master;
if( ml.tail )
{
ml.tail->next = master;
ml.tail = master;
}
else
ml.list = master;
{
ml.head = ml.tail = master;
}
return master;
}
static void NET_AddMaster_f( void )
{
master_t *master;
if( Cmd_Argc() != 2 )
{
Msg( S_USAGE "addmaster <address>\n");
Msg( S_USAGE "addmaster <address> [gs]\n");
return;
}
NET_AddMaster( Cmd_Argv( 1 ), true, false ); // save them into config
master = NET_AddMaster( Cmd_Argv( 1 ));
master->save = true;
if( Q_stricmp( Cmd_Argv( 2 ), "gs" ))
master->gs = true;
ml.modified = true; // save config
}
@@ -298,12 +390,14 @@ Clear master list
*/
static void NET_ClearMasters_f( void )
{
while( ml.list )
while( ml.head )
{
master_t *prev = ml.list;
ml.list = ml.list->next;
Mem_Free( prev );
master_t *head = ml.head;
ml.head = ml.head->next;
Mem_Free( head );
}
ml.tail = NULL;
}
/*
@@ -315,17 +409,24 @@ Display current master linked list
*/
static void NET_ListMasters_f( void )
{
master_t *list;
master_t *master;
int i;
Con_Printf( "Master servers:\n" );
for( i = 1, list = ml.list; list; i++, list = list->next )
for( i = 1, master = ml.head; master; i++, master = master->next )
{
Con_Printf( "%d\t%s", i, list->address );
if( list->adr.type != 0 )
Con_Printf( "\t%s\n", NET_AdrToString( list->adr ));
else Con_Printf( "\n" );
Con_Printf( "%d\t%s", i, master->address );
if( master->adr.type != 0 )
Con_Printf( "\t%s", NET_AdrToString( master->adr ));
if( master->gs )
Con_Printf( " GoldSrc" );
if( master->v6only )
Con_Printf( " IPv6-only");
Con_Printf( "\n" );
}
}
@@ -353,18 +454,33 @@ static void NET_LoadMasters( void )
pfile = (char*)afile;
// format: master <addr>\n
while( ( pfile = COM_ParseFile( pfile, token, sizeof( token ) ) ) )
while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))))
{
master_t *master = NULL;
//if( !Q_strcmp( token, "clear" ))
// NET_ClearMasters_f();
if( !Q_strcmp( token, "master" )) // load addr
{
pfile = COM_ParseFile( pfile, token, sizeof( token ) );
NET_AddMaster( token, true, false );
pfile = COM_ParseFile( pfile, token, sizeof( token ));
master = NET_AddMaster( token );
}
else if( !Q_strcmp( token, "master6" ))
{
pfile = COM_ParseFile( pfile, token, sizeof( token ) );
NET_AddMaster( token, true, true );
pfile = COM_ParseFile( pfile, token, sizeof( token ));
master = NET_AddMaster( token );
master->v6only = true;
}
else if( !Q_strcmp( token, "mastergs" ))
{
pfile = COM_ParseFile( pfile, token, sizeof( token ));
master = NET_AddMaster( token );
master->gs = true;
}
if( master )
master->save = true;
}
Mem_Free( afile );
@@ -395,10 +511,21 @@ void NET_SaveMasters( void )
return;
}
for( m = ml.list; m; m = m->next )
for( m = ml.head; m; m = m->next )
{
if( m->save )
FS_Printf( f, "%s %s\n", m->v6only ? "master6" : "master", m->address );
const char *key;
if( !m->save )
continue;
if( m->v6only )
key = "master6";
else if( m->gs )
key = "mastergs";
else
key = "master";
FS_Printf( f, "%s %s\n", key, m->address );
}
FS_Close( f );
@@ -420,20 +547,20 @@ void NET_InitMasters( void )
Cvar_RegisterVariable( &sv_verbose_heartbeats );
{ // IPv4-only
NET_AddMaster( "mentality.rip:27010", false, false );
NET_AddMaster( "ms2.mentality.rip:27010", false, false );
NET_AddMaster( "ms3.mentality.rip:27010", false, false );
NET_AddMaster( "mentality.rip:27010" );
NET_AddMaster( "ms2.mentality.rip:27010" );
NET_AddMaster( "ms3.mentality.rip:27010" );
}
{ // IPv6-only
NET_AddMaster( "aaaa.mentality.rip:27010", false, true );
NET_AddMaster( "aaaa.ms2.mentality.rip:27010", false, true );
NET_AddMaster( "aaaa.mentality.rip:27010" )->v6only = true;
NET_AddMaster( "aaaa.ms2.mentality.rip:27010" )->v6only = true;
}
{ // testing servers, might be offline
NET_AddMaster( "mentality.rip:27011", false, false );
NET_AddMaster( "aaaa.mentality.rip:27011", false, true );
NET_AddMaster( "mentality.rip:27011" );
NET_AddMaster( "aaaa.mentality.rip:27011" )->v6only = true;
}
NET_LoadMasters( );
NET_LoadMasters();
}

View File

@@ -902,7 +902,7 @@ static void SV_ConnectNatClient( netadr_t from )
{
netadr_t to;
if( !sv_nat.value || !NET_IsMasterAdr( from ))
if( !sv_nat.value || !NET_IsMasterAdr( from, NULL ))
return;
if( !NET_StringToAdr( Cmd_Argv( 1 ), &to ))
@@ -3175,7 +3175,7 @@ void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
return;
}
if( NET_IsMasterAdr( from ))
if( NET_IsMasterAdr( from, NULL ))
{
if( !Q_strcmp( pcmd, M2S_CHALLENGE ))
{