diff --git a/engine/common/common.h b/engine/common/common.h index 10e1b06c..0aa2ebbc 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -876,6 +876,10 @@ 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 ); +void NET_MasterHeartbeat( void ); +void NET_MasterClear( void ); +void NET_MasterShutdown( void ); +qboolean NET_GetMaster( netadr_t from, uint *challenge, double *last_heartbeat ); #ifdef REF_DLL #error "common.h in ref_dll" diff --git a/engine/common/masterlist.c b/engine/common/masterlist.c index bb2edcc8..0900918b 100644 --- a/engine/common/masterlist.c +++ b/engine/common/masterlist.c @@ -14,22 +14,49 @@ 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; + 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; -struct masterlist_s +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 = NA_UNUSED; + if( res == NET_EAI_NONAME ) + Con_Reportf( "Can't resolve adr: %s\n", m->address ); + + return res; +} + /* ======================== NET_SendToMasters @@ -45,65 +72,176 @@ qboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data ) for( list = ml.list; list; list = list->next ) { - int res; - if( list->sent ) continue; - res = NET_StringToAdrNB( list->address, &list->adr ); - - if( !res ) + switch( NET_GetMasterHostByName( list )) { - Con_Reportf( "Can't resolve adr: %s\n", list->address ); + case NET_EAI_AGAIN: + list->sent = false; + wait = true; + break; + case NET_EAI_NONAME: list->sent = true; - list->adr.type = NA_UNUSED; - continue; + break; + case NET_EAI_OK: + list->sent = true; + NET_SendPacket( sock, len, data, list->adr ); + break; } + } - if( res == 2 ) - { + if( !wait ) + { + // reset sent state + for( list = ml.list; list; list = list->next ) list->sent = false; - list->adr.type = NA_UNUSED; - wait = true; - continue; - } + } + + 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 ); - list->sent = true; + MSG_Init( &msg, "Master Join", buf, sizeof( buf )); + MSG_WriteBytes( &msg, "q\xFF", 2 ); + MSG_WriteDword( &msg, m->heartbeat_challenge ); - NET_SendPacket( sock, len, data, list->adr ); + 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 ); } +} - if( !wait ) +/* +======================== +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 ) { - list = ml.list; + if( host.realtime - m->last_heartbeat < HEARTBEAT_SECONDS ) + continue; - while( list ) + switch( NET_GetMasterHostByName( m )) { - list->sent = false; - list = list->next; + 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; } } +} - return wait; +/* +================= +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_IsMasterAdr +NET_GetMasterFromAdr ======================== */ -qboolean NET_IsMasterAdr( netadr_t adr ) +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 true; + return master; } - return false; + 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; } /* @@ -277,6 +415,8 @@ void NET_InitMasters( void ) 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_LoadMasters( ); diff --git a/engine/server/server.h b/engine/server/server.h index a62f9e4c..6c59ba62 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -374,9 +374,7 @@ typedef struct entity_state_t *baselines; // [GI->max_edicts] entity_state_t *static_entities; // [MAX_STATIC_ENTITIES]; - double last_heartbeat; challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting - uint heartbeat_challenge; } server_static_t; //============================================================================= @@ -444,6 +442,7 @@ extern convar_t skill; extern convar_t coop; extern convar_t sv_cheats; extern convar_t public_server; +extern convar_t sv_nat; extern convar_t *sv_pausable; // allows pause in multiplayer extern convar_t *sv_check_errors; diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index e4c1e8e5..9a4e0171 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -475,7 +475,7 @@ void SV_ConnectClient( netadr_t from ) Log_Printf( "\"%s<%i><%i><>\" connected, address \"%s\"\n", newcl->name, newcl->userid, i, NET_AdrToString( newcl->netchan.remote_address )); if( count == 1 || count == svs.maxclients ) - svs.last_heartbeat = MAX_HEARTBEAT; + NET_MasterClear(); } /* @@ -542,7 +542,7 @@ edict_t *SV_FakeConnect( const char *netname ) cl->state = cs_spawned; if( count == 1 || count == svs.maxclients ) - svs.last_heartbeat = MAX_HEARTBEAT; + NET_MasterClear(); return cl->edict; } @@ -619,7 +619,7 @@ void SV_DropClient( sv_client_t *cl, qboolean crash ) } if( i == svs.maxclients ) - svs.last_heartbeat = MAX_HEARTBEAT; + NET_MasterClear(); } /* @@ -3116,7 +3116,7 @@ void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) else if( !Q_strcmp( pcmd, "s" )) SV_AddToMaster( from, msg ); else if( !Q_strcmp( pcmd, "T" "Source" )) SV_TSourceEngineQuery( from ); else if( !Q_strcmp( pcmd, "i" )) NET_SendPacket( NS_SERVER, 5, "\xFF\xFF\xFF\xFFj", from ); // A2A_PING - else if( !Q_strcmp( pcmd, "c" ) && Cvar_VariableInteger( "sv_nat" ) && NET_IsMasterAdr( from )) + else if( !Q_strcmp( pcmd, "c" ) && sv_nat.value && NET_IsMasterAdr( from )) { netadr_t to; if( NET_StringToAdr( Cmd_Argv( 1 ), &to ) && !NET_IsReservedAdr( to )) diff --git a/engine/server/sv_cmds.c b/engine/server/sv_cmds.c index 1eb81d22..b2bcf469 100644 --- a/engine/server/sv_cmds.c +++ b/engine/server/sv_cmds.c @@ -749,9 +749,9 @@ void SV_ConSay_f( void ) SV_Heartbeat_f ================== */ -void SV_Heartbeat_f( void ) +static void SV_Heartbeat_f( void ) { - svs.last_heartbeat = MAX_HEARTBEAT; + NET_MasterClear(); } /* diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index 9f9efdca..3ac2043d 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -1012,7 +1012,7 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba } // heartbeats will always be sent to the id master - svs.last_heartbeat = MAX_HEARTBEAT; // send immediately + NET_MasterClear(); // get actual movevars SV_UpdateMovevars( true ); diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 50e8ee36..59923634 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -18,8 +18,6 @@ GNU General Public License for more details. #include "net_encode.h" #include "platform/platform.h" -#define HEARTBEAT_SECONDS ((sv_nat.value > 0.0f) ? 60.0f : 300.0f) // 1 or 5 minutes - // server cvars CVAR_DEFINE_AUTO( sv_lan, "0", 0, "server is a lan server ( no heartbeat, no authentication, no non-class C addresses, 9999.0 rate, etc." ); CVAR_DEFINE_AUTO( sv_lan_rate, "20000.0", 0, "rate for lan server" ); @@ -142,8 +140,6 @@ convar_t *sv_allow_mouse; convar_t *sv_allow_joystick; convar_t *sv_allow_vr; -static void Master_Heartbeat( void ); - //============================================================================ /* ================ @@ -668,7 +664,7 @@ void Host_ServerFrame( void ) Platform_UpdateStatusLine (); // send a heartbeat to the master if needed - Master_Heartbeat (); + NET_MasterHeartbeat (); } /* @@ -683,68 +679,6 @@ void Host_SetServerState( int state ) } //============================================================================ - -/* -================= -Master_Add -================= -*/ -static void Master_Add( void ) -{ - sizebuf_t msg; - char buf[16]; - uint challenge; - - NET_Config( true, false ); // allow remote - - svs.heartbeat_challenge = challenge = COM_RandomLong( 0, INT_MAX ); - - MSG_Init( &msg, "Master Join", buf, sizeof( buf )); - MSG_WriteBytes( &msg, "q\xFF", 2 ); - MSG_WriteDword( &msg, challenge ); - - if( NET_SendToMasters( NS_SERVER, MSG_GetNumBytesWritten( &msg ), MSG_GetBuf( &msg ))) - svs.last_heartbeat = MAX_HEARTBEAT; -} - -/* -================ -Master_Heartbeat - -Send a message to the master every few minutes to -let it know we are alive, and log information -================ -*/ -static void Master_Heartbeat( void ) -{ - if(( !public_server.value && !sv_nat.value ) || svs.maxclients == 1 ) - return; // only public servers send heartbeats - - // check for time wraparound - if( svs.last_heartbeat > host.realtime ) - svs.last_heartbeat = host.realtime; - - if(( host.realtime - svs.last_heartbeat ) < HEARTBEAT_SECONDS ) - return; // not time to send yet - - svs.last_heartbeat = host.realtime; - - Master_Add(); -} - -/* -================= -Master_Shutdown - -Informs all masters that this server is going down -================= -*/ -static void Master_Shutdown( void ) -{ - NET_Config( true, false ); // allow remote - while( NET_SendToMasters( NS_SERVER, 2, "\x62\x0A" )); -} - /* ================= SV_AddToMaster @@ -755,18 +689,19 @@ Master will validate challenge and this server to public list */ void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) { - uint challenge, challenge2; + uint challenge, challenge2, heartbeat_challenge; char s[MAX_INFO_STRING] = "0\n"; // skip 2 bytes of header int clients, bots; + double last_heartbeat; const int len = sizeof( s ); - if( !NET_IsMasterAdr( from )) + if( !NET_GetMaster( from, &heartbeat_challenge, &last_heartbeat )) { Con_Printf( S_WARN "unexpected master server info query packet from %s\n", NET_AdrToString( from )); return; } - if( svs.last_heartbeat + sv_master_response_timeout.value < host.realtime ) + if( last_heartbeat + sv_master_response_timeout.value < host.realtime ) { Con_Printf( S_WARN "unexpected master server info query packet (too late? try increasing sv_master_response_timeout value)\n"); return; @@ -775,7 +710,7 @@ void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) challenge = MSG_ReadDword( msg ); challenge2 = MSG_ReadDword( msg ); - if( challenge2 != svs.heartbeat_challenge ) + if( challenge2 != heartbeat_challenge ) { Con_Printf( S_WARN "unexpected master server info query packet (wrong challenge!)\n" ); return; @@ -1114,7 +1049,7 @@ void SV_Shutdown( const char *finalmsg ) SV_FinalMessage( finalmsg, false ); if( public_server.value && svs.maxclients != 1 ) - Master_Shutdown(); + NET_MasterShutdown(); NET_Config( false, false ); SV_UnloadProgs ();