//======177== (C) Copyright 1999, 2000 Valve, L.L.C. All rights reserved. ======== // // The copyright to the contents herein is the property of Valve, L.L.C. // The contents may be used and/or copied only with the written permission of // Valve, L.L.C., or in accordance with the terms and conditions stipulated in // the agreement/contract under which the contents have been supplied. // // Purpose: // // $Workfile: $ // $Date: $ // $NoKeywords: $ //============================================================================= #include "quakedef.h" #include "server.h" #include "master.h" #include "proto_oob.h" #include "host.h" #include "eiface.h" #include "server.h" #include "utlmap.h" extern ConVar sv_tags; extern ConVar sv_lan; #define S2A_EXTRA_DATA_HAS_GAMETAG_DATA 0x01 // Next bytes are the game tag string #define RETRY_INFO_REQUEST_TIME 0.4 // seconds #define MASTER_RESPONSE_TIMEOUT 1.5 // seconds #define INFO_REQUEST_TIMEOUT 5.0 // seconds static char g_MasterServers[][64] = { "185.192.97.130:27010", "168.138.92.21:27016", "135.125.188.162:27010" }; #ifdef DEDICATED #define IsLan() false #else #define IsLan() sv_lan.GetInt() #endif //----------------------------------------------------------------------------- // Purpose: List of master servers and some state info about them //----------------------------------------------------------------------------- typedef struct adrlist_s { // Next master in chain struct adrlist_s *next; // Challenge request sent to master qboolean heartbeatwaiting; // Challenge request send time float heartbeatwaitingtime; // Last one is Main master int heartbeatchallenge; // Time we sent last heartbeat double last_heartbeat; // Master server address netadr_t adr; } adrlist_t; //----------------------------------------------------------------------------- // Purpose: Implements the master server interface //----------------------------------------------------------------------------- class CMaster : public IMaster, public IServersInfo { public: CMaster( void ); virtual ~CMaster( void ); // Heartbeat functions. void Init( void ); void Shutdown( void ); // Sets up master address void ShutdownConnection(void); void SendHeartbeat( struct adrlist_s *p ); void AddServer( struct netadr_s *adr ); void UseDefault ( void ); void CheckHeartbeat (void); void RespondToHeartbeatChallenge( netadr_t &from, bf_read &msg ); void PingServer( netadr_t &svadr ); void ProcessConnectionlessPacket( netpacket_t *packet ); void AddMaster_f( const CCommand &args ); void Heartbeat_f( void ); void RunFrame(); void RetryServersInfoRequest(); void ReplyInfo( const netadr_t &adr, uint sequence ); newgameserver_t &ProcessInfo( bf_read &buf ); // SeversInfo void RequestInternetServerList( const char *gamedir, IServerListResponse *response ); void RequestLANServerList( const char *gamedir, IServerListResponse *response ); void AddServerAddresses( netadr_t **adr, int count ); void RequestServerInfo( const netadr_t &adr ); void StopRefresh(); private: // List of known master servers adrlist_t *m_pMasterAddresses; bool m_bInitialized; bool m_bRefreshing; int m_iServersResponded; double m_flStartRequestTime; double m_flRetryRequestTime; double m_flMasterRequestTime; uint m_iInfoSequence; char m_szGameDir[256]; // If nomaster is true, the server will not send heartbeats to the master server bool m_bNoMasters; CUtlMap m_serverAddresses; CUtlMap m_serversRequestTime; IServerListResponse *m_serverListResponse; }; static CMaster s_MasterServer; IMaster *master = (IMaster *)&s_MasterServer; IServersInfo *g_pServersInfo = (IServersInfo*)&s_MasterServer; EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CMaster, IServersInfo, SERVERLIST_INTERFACE_VERSION, s_MasterServer ); #define HEARTBEAT_SECONDS 140.0 //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CMaster::CMaster( void ) { m_pMasterAddresses = NULL; m_bNoMasters = false; m_bInitialized = false; m_iServersResponded = 0; m_serverListResponse = NULL; SetDefLessFunc( m_serverAddresses ); SetDefLessFunc( m_serversRequestTime ); m_bRefreshing = false; m_iInfoSequence = 1; Init(); } CMaster::~CMaster( void ) { } void CMaster::RunFrame() { CheckHeartbeat(); if( !m_bRefreshing ) return; if( m_serverListResponse && m_flStartRequestTime < Plat_FloatTime()-INFO_REQUEST_TIMEOUT ) { StopRefresh(); m_serverListResponse->RefreshComplete( NServerResponse::nServerFailedToRespond ); return; } if( m_iServersResponded > 0 && m_iServersResponded >= m_serverAddresses.Count() && m_flMasterRequestTime < Plat_FloatTime() - MASTER_RESPONSE_TIMEOUT ) { StopRefresh(); m_serverListResponse->RefreshComplete( NServerResponse::nServerResponded ); return; } if( m_flRetryRequestTime < Plat_FloatTime() - RETRY_INFO_REQUEST_TIME ) { m_flRetryRequestTime = Plat_FloatTime(); if( m_serverAddresses.Count() == 0 ) // Retry masterserver request { g_pServersInfo->RequestInternetServerList(m_szGameDir, NULL); return; } if( m_iServersResponded < m_serverAddresses.Count() ) RetryServersInfoRequest(); } } void CMaster::StopRefresh() { if( !m_bRefreshing ) return; m_iServersResponded = 0; m_bRefreshing = false; m_serverAddresses.RemoveAll(); m_serversRequestTime.RemoveAll(); } void CMaster::ReplyInfo( const netadr_t &adr, uint sequence ) { static char gamedir[MAX_OSPATH]; Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) ); CUtlBuffer buf; buf.EnsureCapacity( 2048 ); buf.PutUnsignedInt( LittleDWord( CONNECTIONLESS_HEADER ) ); buf.PutUnsignedChar( S2C_INFOREPLY ); buf.PutUnsignedInt(sequence); buf.PutUnsignedChar( PROTOCOL_VERSION ); // Hardcoded protocol version number buf.PutString( sv.GetName() ); buf.PutString( sv.GetMapName() ); buf.PutString( gamedir ); buf.PutString( serverGameDLL->GetGameDescription() ); // player info buf.PutUnsignedChar( sv.GetNumClients() ); buf.PutUnsignedChar( sv.GetMaxClients() ); buf.PutUnsignedChar( sv.GetNumFakeClients() ); // Password? buf.PutUnsignedChar( sv.GetPassword() != NULL ? 1 : 0 ); // Write a byte with some flags that describe what is to follow. const char *pchTags = sv_tags.GetString(); int nFlags = 0; if ( pchTags && pchTags[0] != '\0' ) nFlags |= S2A_EXTRA_DATA_HAS_GAMETAG_DATA; buf.PutUnsignedInt( nFlags ); if ( nFlags & S2A_EXTRA_DATA_HAS_GAMETAG_DATA ) buf.PutString( pchTags ); NET_SendPacket( NULL, NS_SERVER, adr, (unsigned char *)buf.Base(), buf.TellPut() ); } newgameserver_t &CMaster::ProcessInfo(bf_read &buf) { static newgameserver_t s; memset( &s, 0, sizeof(s) ); s.m_nProtocolVersion = buf.ReadByte(); buf.ReadString( s.m_szServerName, sizeof(s.m_szServerName) ); buf.ReadString( s.m_szMap, sizeof(s.m_szMap) ); buf.ReadString( s.m_szGameDir, sizeof(s.m_szGameDir) ); buf.ReadString( s.m_szGameDescription, sizeof(s.m_szGameDescription) ); // player info s.m_nPlayers = buf.ReadByte(); s.m_nMaxPlayers = buf.ReadByte(); s.m_nBotPlayers = buf.ReadByte(); // Password? s.m_bPassword = buf.ReadByte(); s.m_iFlags = buf.ReadLong(); if( s.m_iFlags & S2A_EXTRA_DATA_HAS_GAMETAG_DATA ) { buf.ReadString( s.m_szGameTags, sizeof(s.m_szGameTags) ); } return s; } void CMaster::ProcessConnectionlessPacket( netpacket_t *packet ) { static ALIGN4 char string[2048] ALIGN4_POST; // Buffer for sending heartbeat uint ip; uint16 port; bf_read msg = packet->message; char c = msg.ReadChar(); if ( c == 0 ) return; switch( c ) { case M2S_CHALLENGE: { RespondToHeartbeatChallenge( packet->from, msg ); break; } case M2C_QUERY: { if( !m_bRefreshing ) break; ip = msg.ReadLong(); port = msg.ReadShort(); while( ip != 0 && port != 0 ) { netadr_t adr(ip, port); unsigned short index = m_serverAddresses.Find(adr); if( index != m_serverAddresses.InvalidIndex() ) { ip = msg.ReadLong(); port = msg.ReadShort(); continue; } m_serverAddresses.Insert(adr, false); RequestServerInfo(adr); ip = msg.ReadLong(); port = msg.ReadShort(); } break; } case C2S_INFOREQUEST: { ReplyInfo(packet->from, msg.ReadLong()); break; } case S2C_INFOREPLY: { if( !m_bRefreshing ) break; uint sequence = msg.ReadLong(); newgameserver_t &s = ProcessInfo( msg ); unsigned short index = m_serverAddresses.Find(packet->from); unsigned short rindex = m_serversRequestTime.Find(sequence); if( index == m_serverAddresses.InvalidIndex() || rindex == m_serversRequestTime.InvalidIndex() ) break; double requestTime = m_serversRequestTime[rindex]; if( m_serverAddresses[index] ) // shit happens return; m_serverAddresses[index] = true; s.m_nPing = (Plat_FloatTime()-requestTime)*1000.0; s.m_NetAdr = packet->from; m_serverListResponse->ServerResponded( s ); m_iServersResponded++; break; } } } void CMaster::RequestServerInfo( const netadr_t &adr ) { static ALIGN4 char string[256] ALIGN4_POST; // Buffer for sending heartbeat bf_write msg( string, sizeof(string) ); msg.WriteLong( CONNECTIONLESS_HEADER ); msg.WriteByte( C2S_INFOREQUEST ); msg.WriteLong( m_iInfoSequence ); m_serversRequestTime.Insert(m_iInfoSequence, Plat_FloatTime()); m_iInfoSequence++; NET_SendPacket( NULL, NS_CLIENT, adr, msg.GetData(), msg.GetNumBytesWritten() ); } void CMaster::RetryServersInfoRequest() { FOR_EACH_MAP_FAST( m_serverAddresses, i ) { bool bResponded = m_serverAddresses.Element(i); if( bResponded ) continue; const netadr_t adr = m_serverAddresses.Key(i); RequestServerInfo( adr ); } } //----------------------------------------------------------------------------- // Purpose: Sends a heartbeat to the master server // Input : *p - x00\x00\x00\x00\x00\x00 //----------------------------------------------------------------------------- void CMaster::SendHeartbeat ( adrlist_t *p ) { static ALIGN4 char string[256] ALIGN4_POST; // Buffer for sending heartbeat char szGD[ MAX_OSPATH ]; if ( !p ) return; // Still waiting on challenge response? if ( p->heartbeatwaiting ) return; // Waited too long if ( (realtime - p->heartbeatwaitingtime ) >= HB_TIMEOUT ) return; // Send to master Q_FileBase( com_gamedir, szGD, sizeof( szGD ) ); bf_write buf( string, sizeof(string) ); buf.WriteByte( S2M_HEARTBEAT ); buf.WriteLong( p->heartbeatchallenge ); buf.WriteShort( PROTOCOL_VERSION ); buf.WriteString( szGD ); NET_SendPacket( NULL, NS_SERVER, p->adr, buf.GetData(), buf.GetNumBytesWritten() ); } //----------------------------------------------------------------------------- // Purpose: Requests a challenge so we can then send a heartbeat //----------------------------------------------------------------------------- void CMaster::CheckHeartbeat (void) { adrlist_t *p; ALIGN4 char buf[256] ALIGN4_POST; if ( m_bNoMasters || // We are ignoring heartbeats IsLan() || // Lan servers don't heartbeat (sv.GetMaxClients() <= 1) || // not a multiplayer server. !sv.IsActive() ) // only heartbeat if a server is running. return; p = m_pMasterAddresses; while ( p ) { // Time for another try? if ( ( realtime - p->last_heartbeat) < HEARTBEAT_SECONDS) // not time to send yet { p = p->next; continue; } // Should we resend challenge request? if ( p->heartbeatwaiting && ( ( realtime - p->heartbeatwaitingtime ) < HB_TIMEOUT ) ) { p = p->next; continue; } int32 challenge = RandomInt( 0, INT_MAX ); p->heartbeatwaiting = true; p->heartbeatwaitingtime = realtime; p->last_heartbeat = realtime; // Flag at start so we don't just keep trying for hb's when p->heartbeatchallenge = challenge; bf_write msg("Master Join", buf, sizeof(buf)); msg.WriteByte( S2M_GETCHALLENGE ); msg.WriteLong( challenge ); // Send to master asking for a challenge # NET_SendPacket( NULL, NS_SERVER, p->adr, msg.GetData(), msg.GetNumBytesWritten() ); } } //----------------------------------------------------------------------------- // Purpose: Server is shutting down, unload master servers list, tell masters that we are closing the server //----------------------------------------------------------------------------- void CMaster::ShutdownConnection( void ) { adrlist_t *p; if ( !host_initialized ) return; if ( m_bNoMasters || // We are ignoring heartbeats IsLan() || // Lan servers don't heartbeat (sv.GetMaxClients() <= 1) ) // not a multiplayer server. return; const char packet = S2M_SHUTDOWN; p = m_pMasterAddresses; while ( p ) { NET_SendPacket( NULL, NS_SERVER, p->adr, (unsigned char*)&packet, 1); p->last_heartbeat = -99999.0; p = p->next; } } //----------------------------------------------------------------------------- // Purpose: Add server to the master list // Input : *adr - //----------------------------------------------------------------------------- void CMaster::AddServer( netadr_t *adr ) { adrlist_t *n; // See if it's there n = m_pMasterAddresses; while ( n ) { if ( n->adr.CompareAdr( *adr ) ) break; n = n->next; } // Found it in the list. if ( n ) return; n = ( adrlist_t * ) malloc ( sizeof( adrlist_t ) ); if ( !n ) Sys_Error( "Error allocating %zd bytes for master address.", sizeof( adrlist_t ) ); memset( n, 0, sizeof( adrlist_t ) ); n->adr = *adr; // Queue up a full heartbeat to all master servers. n->last_heartbeat = -99999.0; // Link it in. n->next = m_pMasterAddresses; m_pMasterAddresses = n; } //----------------------------------------------------------------------------- // Purpose: Add built-in default master if woncomm.lst doesn't parse //----------------------------------------------------------------------------- void CMaster::UseDefault ( void ) { netadr_t adr; for( int i = 0; i < ARRAYSIZE(g_MasterServers);i++ ) { // Convert to netadr_t if ( NET_StringToAdr ( g_MasterServers[i], &adr ) ) { // Add to master list AddServer( &adr ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMaster::RespondToHeartbeatChallenge( netadr_t &from, bf_read &msg ) { adrlist_t *p; uint challenge, challenge2; // No masters, just ignore. if ( !m_pMasterAddresses ) return; p = m_pMasterAddresses; while ( p ) { if ( from.CompareAdr( p->adr ) ) break; p = p->next; } // Not a known master server. if ( !p ) return; challenge = msg.ReadLong(); challenge2 = msg.ReadLong(); if( p->heartbeatchallenge != challenge2 ) { Warning("unexpected master server info query packet (wrong challenge!)\n"); return; } // Kill timer p->heartbeatwaiting = false; p->heartbeatchallenge = challenge; // Send the actual heartbeat request to this master server. SendHeartbeat( p ); } //----------------------------------------------------------------------------- // Purpose: Add/remove master servers //----------------------------------------------------------------------------- void CMaster::AddMaster_f ( const CCommand &args ) { CUtlString cmd( ( args.ArgC() > 1 ) ? args[ 1 ] : "" ); netadr_t adr; if( !NET_StringToAdr(cmd.String(), &adr) ) { Warning("Invalid address\n"); return; } this->AddServer(&adr); } //----------------------------------------------------------------------------- // Purpose: Send a new heartbeat to the master //----------------------------------------------------------------------------- void CMaster::Heartbeat_f (void) { adrlist_t *p; p = m_pMasterAddresses; while ( p ) { // Queue up a full hearbeat p->last_heartbeat = -9999.0; p->heartbeatwaitingtime = -9999.0; p = p->next; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void AddMaster_f( const CCommand &args ) { master->AddMaster_f( args ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void Heartbeat1_f( void ) { master->Heartbeat_f(); } static ConCommand setmaster("addmaster", AddMaster_f ); static ConCommand heartbeat("heartbeat", Heartbeat1_f, "Force heartbeat of master servers" ); //----------------------------------------------------------------------------- // Purpose: Adds master server console commands //----------------------------------------------------------------------------- void CMaster::Init( void ) { // Already able to initialize at least once? if ( m_bInitialized ) return; // So we don't do this a send time.sv_mas m_bInitialized = true; UseDefault(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMaster::Shutdown(void) { adrlist_t *p, *n; // Free the master list now. p = m_pMasterAddresses; while ( p ) { n = p->next; free( p ); p = n; } m_pMasterAddresses = NULL; } // ServersInfo void CMaster::RequestInternetServerList(const char *gamedir, IServerListResponse *response) { if( m_bNoMasters ) return; strncpy( m_szGameDir, gamedir, sizeof(m_szGameDir) ); if( response ) { StopRefresh(); m_bRefreshing = true; m_serverListResponse = response; m_flRetryRequestTime = m_flStartRequestTime = m_flMasterRequestTime = Plat_FloatTime(); } ALIGN4 char buf[256] ALIGN4_POST; bf_write msg(buf, sizeof(buf)); msg.WriteByte( C2M_CLIENTQUERY ); msg.WriteString(gamedir); adrlist_t *p; p = m_pMasterAddresses; while ( p ) { NET_SendPacket(NULL, NS_CLIENT, p->adr, msg.GetData(), msg.GetNumBytesWritten() ); p = p->next; } } void CMaster::RequestLANServerList(const char *gamedir, IServerListResponse *response) { } void CMaster::AddServerAddresses( netadr_t **adr, int count ) { }