|
|
|
/*
|
|
|
|
masterlist.c - multi-master list
|
|
|
|
Copyright (C) 2018 mittorn
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
*/
|
|
|
|
#include "common.h"
|
|
|
|
#include "netchan.h"
|
|
|
|
#include "server.h"
|
|
|
|
|
|
|
|
typedef struct master_s
|
|
|
|
{
|
|
|
|
struct master_s *next;
|
|
|
|
qboolean sent; // TODO: get rid of this internal state
|
|
|
|
qboolean save;
|
|
|
|
string address;
|
|
|
|
netadr_t adr; // temporary, rewritten after each send
|
|
|
|
|
|
|
|
uint heartbeat_challenge;
|
|
|
|
double last_heartbeat;
|
|
|
|
} master_t;
|
|
|
|
|
|
|
|
static struct masterlist_s
|
|
|
|
{
|
|
|
|
master_t *list;
|
|
|
|
qboolean modified;
|
|
|
|
} ml;
|
|
|
|
|
|
|
|
static CVAR_DEFINE_AUTO( sv_verbose_heartbeats, "0", 0, "print every heartbeat to console" );
|
|
|
|
|
|
|
|
#define HEARTBEAT_SECONDS ((sv_nat.value > 0.0f) ? 60.0f : 300.0f) // 1 or 5 minutes
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_GetMasterHostByName
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
static net_gai_state_t NET_GetMasterHostByName( master_t *m )
|
|
|
|
{
|
|
|
|
net_gai_state_t res = NET_StringToAdrNB( m->address, &m->adr );
|
|
|
|
|
|
|
|
if( res == NET_EAI_OK )
|
|
|
|
return res;
|
|
|
|
|
|
|
|
m->adr.type = 0;
|
|
|
|
if( res == NET_EAI_NONAME )
|
|
|
|
Con_Reportf( "Can't resolve adr: %s\n", m->address );
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_SendToMasters
|
|
|
|
|
|
|
|
Send request to all masterservers list
|
|
|
|
return true if would block
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
qboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data )
|
|
|
|
{
|
|
|
|
master_t *list;
|
|
|
|
qboolean wait = false;
|
|
|
|
|
|
|
|
for( list = ml.list; list; list = list->next )
|
|
|
|
{
|
|
|
|
if( list->sent )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch( NET_GetMasterHostByName( list ))
|
|
|
|
{
|
|
|
|
case NET_EAI_AGAIN:
|
|
|
|
list->sent = false;
|
|
|
|
wait = true;
|
|
|
|
break;
|
|
|
|
case NET_EAI_NONAME:
|
|
|
|
list->sent = true;
|
|
|
|
break;
|
|
|
|
case NET_EAI_OK:
|
|
|
|
list->sent = true;
|
|
|
|
NET_SendPacket( sock, len, data, list->adr );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !wait )
|
|
|
|
{
|
|
|
|
// reset sent state
|
|
|
|
for( list = ml.list; list; list = list->next )
|
|
|
|
list->sent = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return wait;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_AnnounceToMaster
|
|
|
|
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
static void NET_AnnounceToMaster( master_t *m )
|
|
|
|
{
|
|
|
|
sizebuf_t msg;
|
|
|
|
char buf[16];
|
|
|
|
|
|
|
|
m->heartbeat_challenge = COM_RandomLong( 0, INT_MAX );
|
|
|
|
|
|
|
|
MSG_Init( &msg, "Master Join", buf, sizeof( buf ));
|
|
|
|
MSG_WriteBytes( &msg, "q\xFF", 2 );
|
|
|
|
MSG_WriteDword( &msg, m->heartbeat_challenge );
|
|
|
|
|
|
|
|
NET_SendPacket( NS_SERVER, MSG_GetNumBytesWritten( &msg ), MSG_GetBuf( &msg ), m->adr );
|
|
|
|
|
|
|
|
if( sv_verbose_heartbeats.value )
|
|
|
|
{
|
|
|
|
Con_Printf( S_NOTE "sent heartbeat to %s (%s, 0x%x)\n",
|
|
|
|
m->address, NET_AdrToString( m->adr ), m->heartbeat_challenge );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_AnnounceToMaster
|
|
|
|
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
void NET_MasterClear( void )
|
|
|
|
{
|
|
|
|
master_t *m;
|
|
|
|
|
|
|
|
for( m = ml.list; m; m = m->next )
|
|
|
|
m->last_heartbeat = MAX_HEARTBEAT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_MasterHeartbeat
|
|
|
|
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
void NET_MasterHeartbeat( void )
|
|
|
|
{
|
|
|
|
master_t *m;
|
|
|
|
|
|
|
|
if(( !public_server.value && !sv_nat.value ) || svs.maxclients == 1 )
|
|
|
|
return; // only public servers send heartbeats
|
|
|
|
|
|
|
|
for( m = ml.list; m; m = m->next )
|
|
|
|
{
|
|
|
|
if( host.realtime - m->last_heartbeat < HEARTBEAT_SECONDS )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch( NET_GetMasterHostByName( m ))
|
|
|
|
{
|
|
|
|
case NET_EAI_AGAIN:
|
|
|
|
m->last_heartbeat = MAX_HEARTBEAT; // retry on next frame
|
|
|
|
if( sv_verbose_heartbeats.value )
|
|
|
|
Con_Printf( S_NOTE "delay heartbeat to next frame until %s resolves\n", m->address );
|
|
|
|
|
|
|
|
break;
|
|
|
|
case NET_EAI_NONAME:
|
|
|
|
m->last_heartbeat = host.realtime; // try to resolve again on next heartbeat
|
|
|
|
break;
|
|
|
|
case NET_EAI_OK:
|
|
|
|
m->last_heartbeat = host.realtime;
|
|
|
|
NET_AnnounceToMaster( m );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
NET_MasterShutdown
|
|
|
|
|
|
|
|
Informs all masters that this server is going down
|
|
|
|
(ignored by master servers in current implementation)
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
void NET_MasterShutdown( void )
|
|
|
|
{
|
|
|
|
NET_Config( true, false ); // allow remote
|
|
|
|
while( NET_SendToMasters( NS_SERVER, 2, "\x62\x0A" ));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_GetMasterFromAdr
|
|
|
|
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
static master_t *NET_GetMasterFromAdr( netadr_t adr )
|
|
|
|
{
|
|
|
|
master_t *master;
|
|
|
|
|
|
|
|
for( master = ml.list; master; master = master->next )
|
|
|
|
{
|
|
|
|
if( NET_CompareAdr( adr, master->adr ))
|
|
|
|
return master;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_GetMaster
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
qboolean NET_GetMaster( netadr_t from, uint *challenge, double *last_heartbeat )
|
|
|
|
{
|
|
|
|
master_t *m;
|
|
|
|
|
|
|
|
m = NET_GetMasterFromAdr( from );
|
|
|
|
|
|
|
|
if( m )
|
|
|
|
{
|
|
|
|
*challenge = m->heartbeat_challenge;
|
|
|
|
*last_heartbeat = m->last_heartbeat;
|
|
|
|
}
|
|
|
|
|
|
|
|
return m != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_IsMasterAdr
|
|
|
|
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
qboolean NET_IsMasterAdr( netadr_t adr )
|
|
|
|
{
|
|
|
|
return NET_GetMasterFromAdr( adr ) != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_AddMaster
|
|
|
|
|
|
|
|
Add master to the list
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
static void NET_AddMaster( const char *addr, qboolean save )
|
|
|
|
{
|
|
|
|
master_t *master, *last;
|
|
|
|
|
|
|
|
for( last = ml.list; last && last->next; last = last->next )
|
|
|
|
{
|
|
|
|
if( !Q_strcmp( last->address, addr ) ) // already exists
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
master = Mem_Malloc( host.mempool, sizeof( master_t ) );
|
|
|
|
Q_strncpy( master->address, addr, MAX_STRING );
|
|
|
|
master->sent = false;
|
|
|
|
master->save = save;
|
|
|
|
master->next = NULL;
|
|
|
|
master->adr.type = 0;
|
|
|
|
|
|
|
|
// link in
|
|
|
|
if( last )
|
|
|
|
last->next = master;
|
|
|
|
else
|
|
|
|
ml.list = master;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void NET_AddMaster_f( void )
|
|
|
|
{
|
|
|
|
if( Cmd_Argc() != 2 )
|
|
|
|
{
|
|
|
|
Msg( S_USAGE "addmaster <address>\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
NET_AddMaster( Cmd_Argv( 1 ), true ); // save them into config
|
|
|
|
ml.modified = true; // save config
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_ClearMasters
|
|
|
|
|
|
|
|
Clear master list
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
static void NET_ClearMasters_f( void )
|
|
|
|
{
|
|
|
|
while( ml.list )
|
|
|
|
{
|
|
|
|
master_t *prev = ml.list;
|
|
|
|
ml.list = ml.list->next;
|
|
|
|
Mem_Free( prev );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_ListMasters_f
|
|
|
|
|
|
|
|
Display current master linked list
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
static void NET_ListMasters_f( void )
|
|
|
|
{
|
|
|
|
master_t *list;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
Msg( "Master servers\n=============\n" );
|
|
|
|
|
|
|
|
|
|
|
|
for( i = 1, list = ml.list; list; i++, list = list->next )
|
|
|
|
{
|
|
|
|
Msg( "%d\t%s", i, list->address );
|
|
|
|
if( list->adr.type != 0 )
|
|
|
|
Msg( "\t%s\n", NET_AdrToString( list->adr ));
|
|
|
|
else Msg( "\n" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_LoadMasters
|
|
|
|
|
|
|
|
Load master server list from xashcomm.lst
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
static void NET_LoadMasters( void )
|
|
|
|
{
|
|
|
|
byte *afile;
|
|
|
|
char *pfile;
|
|
|
|
char token[MAX_TOKEN];
|
|
|
|
|
|
|
|
afile = FS_LoadFile( "xashcomm.lst", NULL, true );
|
|
|
|
|
|
|
|
if( !afile ) // file doesn't exist yet
|
|
|
|
{
|
|
|
|
Con_Reportf( "Cannot load xashcomm.lst\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
pfile = (char*)afile;
|
|
|
|
|
|
|
|
// format: master <addr>\n
|
|
|
|
while( ( pfile = COM_ParseFile( pfile, token, sizeof( token ) ) ) )
|
|
|
|
{
|
|
|
|
if( !Q_strcmp( token, "master" ) ) // load addr
|
|
|
|
{
|
|
|
|
pfile = COM_ParseFile( pfile, token, sizeof( token ) );
|
|
|
|
|
|
|
|
NET_AddMaster( token, true );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Mem_Free( afile );
|
|
|
|
|
|
|
|
ml.modified = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_SaveMasters
|
|
|
|
|
|
|
|
Save master server list to xashcomm.lst, except for default
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
void NET_SaveMasters( void )
|
|
|
|
{
|
|
|
|
file_t *f;
|
|
|
|
master_t *m;
|
|
|
|
|
|
|
|
if( !ml.modified )
|
|
|
|
{
|
|
|
|
Con_Reportf( "Master server list not changed\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
f = FS_Open( "xashcomm.lst", "w", true );
|
|
|
|
|
|
|
|
if( !f )
|
|
|
|
{
|
|
|
|
Con_Reportf( S_ERROR "Couldn't write xashcomm.lst\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for( m = ml.list; m; m = m->next )
|
|
|
|
{
|
|
|
|
if( m->save )
|
|
|
|
FS_Printf( f, "master %s\n", m->address );
|
|
|
|
}
|
|
|
|
|
|
|
|
FS_Close( f );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
NET_InitMasters
|
|
|
|
|
|
|
|
Initialize master server list
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
void NET_InitMasters( void )
|
|
|
|
{
|
|
|
|
Cmd_AddRestrictedCommand( "addmaster", NET_AddMaster_f, "add address to masterserver list" );
|
|
|
|
Cmd_AddRestrictedCommand( "clearmasters", NET_ClearMasters_f, "clear masterserver list" );
|
|
|
|
Cmd_AddCommand( "listmasters", NET_ListMasters_f, "list masterservers" );
|
|
|
|
|
|
|
|
Cvar_RegisterVariable( &sv_verbose_heartbeats );
|
|
|
|
|
|
|
|
// keep main master always there
|
|
|
|
NET_AddMaster( MASTERSERVER_ADR, false );
|
|
|
|
NET_AddMaster( MASTERSERVER_ADR_TEST, false );
|
|
|
|
NET_LoadMasters( );
|
|
|
|
}
|