//======== (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 "vstdlib/ICommandLine.h"//-----------------------------------------------------------------------------// 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: CMaster( void ); virtual ~CMaster( void ); // Heartbeat functions. void Init( void ); void Shutdown( void ); // Sets up master address void InitConnection(void); void ShutdownConnection(void); void SendHeartbeat( struct adrlist_s *p ); void AddServer( struct netadr_s *adr ); void UseDefault ( void ); void CheckHeartbeat (void); void RespondToHeartbeatChallenge(void); void SetMaster_f( void ); void Heartbeat_f( void );private: // List of known master servers adrlist_t *m_pMasterAddresses; // If nomaster is true, the server will not send heartbeats to the master server bool m_bNoMasters;};static CMaster g_MasterServer;IMaster *master = (IMaster *)&g_MasterServer;#define HEARTBEAT_SECONDS 300#define MASTER_PARSE_FILE "woncomm.lst"//-----------------------------------------------------------------------------// Purpose: Constructor//-----------------------------------------------------------------------------CMaster::CMaster( void ){ m_pMasterAddresses = NULL; m_bNoMasters = false;}CMaster::~CMaster( void ){}//-----------------------------------------------------------------------------// Purpose: Sends a heartbeat to the master server// Input : *p - //-----------------------------------------------------------------------------void CMaster::SendHeartbeat ( adrlist_t *p ){ static char string[2048]; // Buffer for sending heartbeat int active; // Number of active client connections char szGD[ MAX_OSPATH ];// if ( gfUseLANAuthentication ) // No HB on lan games // return; if ( !p ) return; // Still waiting on challenge response? if ( p->heartbeatwaiting ) return; // Waited too long if ( (realtime - p->heartbeatwaitingtime ) >= HB_TIMEOUT ) return; InitConnection(); // // count active users // SV_CountPlayers( &active ); memset(string, 0, 2048); COM_FileBase( com_gamedir, szGD ); // Send to master Q_snprintf (string, sizeof( string ), "%c\n%i\n%i\n%i\n%i\n%s\n", S2M_HEARTBEAT, p->heartbeatchallenge, 1, active, PROTOCOL_VERSION, szGD ); //The packet goes "a" "challengenumber" "1" "numberofplayers" "modname NET_SendPacket (NS_SERVER, strlen(string), string, p->adr );}//-----------------------------------------------------------------------------// Purpose: Requests a challenge so we can then send a heartbeat//-----------------------------------------------------------------------------void CMaster::CheckHeartbeat (void){ unsigned char c; // Buffer for sending heartbeat adrlist_t *p;/* if ( m_bNoMasters || // We are ignoring heartbeats gfUseLANAuthentication || // No HB on lan games sv_lan.GetInt() || // Lan servers don't heartbeat (svs.maxclients <= 1) || // not a multiplayer server. !sv.active ) // only heartbeat if a server is running. return;*/ InitConnection(); 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; } p->heartbeatwaiting = true; p->heartbeatwaitingtime = realtime; p->last_heartbeat = realtime; // Flag at start so we don't just keep trying for hb's when c = A2A_GETCHALLENGE; // Send to master asking for a challenge # NET_SendPacket (NS_SERVER, 1, "q", p->adr); }}//-----------------------------------------------------------------------------// Purpose: Server is shutting down, unload master servers list, tell masters that we are closing the server//-----------------------------------------------------------------------------void CMaster::ShutdownConnection( void ){ char string[2048]; adrlist_t *p; if ( !host_initialized ) return; /*if ( m_bNoMasters || // We are ignoring heartbeats gfUseLANAuthentication || // No HB on lan games sv_lan.GetInt() || // Lan servers don't heartbeat (svs.maxclients <= 1) ) // not a multiplayer server. return;*/ InitConnection(); Q_snprintf (string, sizeof( string ), "%c\n", S2M_SHUTDOWN); p = m_pMasterAddresses; while ( p ) { NET_SendPacket (NS_SERVER, strlen(string), string, p->adr); p = p->next; }}//-----------------------------------------------------------------------------// Purpose: Add server to the master list// Input : *adr - //-----------------------------------------------------------------------------void CMaster::AddServer( netadr_t *adr ){ adrlist_t *n; InitConnection(); // See if it's there n = m_pMasterAddresses; while ( n ) { if ( NET_CompareAdr( n->adr, *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 %i 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; // 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 ){ char szValveMasterAddress[256]; netadr_t adr; // Get default master Q_snprintf( szValveMasterAddress, sizeof( szValveMasterAddress ), VALVE_MASTER_ADDRESS ); // IP:PORT string // Convert to netadr_t if ( NET_StringToAdr ( szValveMasterAddress, &adr ) ) { // Add to master list AddServer( &adr ); }}//-----------------------------------------------------------------------------// Purpose: Initializes the default master server address//-----------------------------------------------------------------------------void CMaster::InitConnection( void ){ char szValveMasterAddress[256]; char szWONCommFile[MAX_OSPATH]; char *pbuffer; char *pstart; netadr_t adr; char szAdr[64]; int nPort; int nCount = 0; qboolean bIgnore; int nDefaultPort; static qboolean bInitialized = false;/* if ( m_bNoMasters || // We are ignoring heartbeats gfUseLANAuthentication || // No HB on lan games sv_lan.GetInt() || // Lan servers don't heartbeat (svs.maxclients <= 1) ) { return; }*/ // Already able to initialize at least once? if ( bInitialized ) return; // So we don't do this a second time. bInitialized = true; // Assume default master Q_snprintf( szValveMasterAddress, sizeof( szValveMasterAddress ), VALVE_MASTER_ADDRESS); // IP:PORT string Q_snprintf( szWONCommFile, sizeof( szWONCommFile ), "%s", MASTER_PARSE_FILE ); const char *pComFile = CommandLine()->ParmValue( "-comm" ); if ( pComFile ) { strcpy ( szWONCommFile, pComFile ); } // Read them in from WONCOMM.LST pbuffer = (char*)COM_LoadFile( szWONCommFile, 5, NULL ); // Use malloc if ( !pbuffer ) { Con_Printf( "Couldn't load woncomm.lst\nUsing default master\n" ); UseDefault(); return; } pstart = pbuffer; while ( 1 ) { pstart = COM_Parse( pstart ); if ( strlen(com_token) <= 0) break; bIgnore = true; nDefaultPort = PORT_MASTER; if ( !stricmp( com_token, "Master" ) ) { bIgnore = FALSE; } // Now parse all addresses between { } pstart = COM_Parse( pstart ); if ( strlen(com_token) <= 0 ) break; if ( stricmp ( com_token, "{" ) ) break; // Parse addresses until we get to "}" while ( 1 ) { char base[256]; // Now parse all addresses between { } pstart = COM_Parse( pstart ); if (strlen(com_token) <= 0) break; if ( !stricmp ( com_token, "}" ) ) break; Q_snprintf( base, sizeof( base ), "%s", com_token ); pstart = COM_Parse( pstart ); if (strlen(com_token) <= 0) break; if ( stricmp( com_token, ":" ) ) break; pstart = COM_Parse( pstart ); if (strlen(com_token) <= 0) break; nPort = atoi ( com_token ); if ( !nPort ) nPort = nDefaultPort; Q_snprintf( szAdr, sizeof( szAdr ), "%s:%i", base, nPort ); // Can we resolve it any better if ( !NET_StringToAdr( szAdr, &adr ) ) bIgnore = true; if ( !bIgnore ) { Con_Printf( "Adding master server %s\n", NET_AdrToString( adr ) ); AddServer( &adr ); nCount++; } } } if ( !nCount ) { Con_Printf( "No masters parsed from woncomm.lst\nUsing default master\n" ); UseDefault(); } free( pbuffer );}//-----------------------------------------------------------------------------// Purpose: //-----------------------------------------------------------------------------void CMaster::RespondToHeartbeatChallenge(void){ adrlist_t *p; // No masters, just ignore. /*if ( !m_pMasterAddresses ) return;*/ p = m_pMasterAddresses; while ( p ) { if ( NET_CompareAdr( p->adr, net_from ) ) break; p = p->next; } // Not a known master server. if ( !p ) return; // Kill timer p->heartbeatwaiting = false; p->heartbeatchallenge = MSG_ReadLong(); // Send the actual heartbeat request to this master server. SendHeartbeat( p );}//-----------------------------------------------------------------------------// Purpose: Add/remove master servers//-----------------------------------------------------------------------------void CMaster::SetMaster_f (void){ int i; char *pMasterAddress; // IP:Port string of master char *pMasterPort; // Port of master char szMaster[128]; // Full string int ipPort; // Numeric port char *pszCmd; char szMasterPort[20]; netadr_t adr; qboolean bfirst = false; ipPort = PORT_MASTER; // default Q_snprintf( szMasterPort, sizeof( szMasterPort ), "%i", ipPort ); master->InitConnection(); if ( !m_pMasterAddresses ) bfirst = true; i = Cmd_Argc(); // Usage help if ( ( i < 2 ) || ( i > 4 ) ) { adrlist_t *p; Con_Printf("Usage:\nSetmaster <add | remove | enable | disable> <IP:port>\n"); p = m_pMasterAddresses; if ( !p ) Con_Printf("Current: None\n"); else { int i = 1; Con_Printf("Current:\n"); while ( p ) { Con_Printf(" %i: %s\n", i++, NET_AdrToString( p->adr ) ); p = p->next; } } return; } pszCmd = Cmd_Argv(1); if ( !pszCmd || !pszCmd[0] ) return; // Check for disabling... if ( !stricmp( pszCmd, "disable") ) { m_bNoMasters = true; return; } else if (!stricmp( pszCmd, "enable") ) { m_bNoMasters = false; return; } if ( stricmp( pszCmd, "add" ) && stricmp( pszCmd, "remove" ) ) { Con_Printf( "Setmaster: Unknown command %s\n", pszCmd ); return; } // Get address and, if a port is specified get it, too. pMasterAddress = Cmd_Argv(2); if (i == 4) { pMasterPort = Cmd_Argv(3); if ( pMasterPort && pMasterPort[0] ) { ipPort = atoi(pMasterPort); if (ipPort == 0) // If any problem, assuem default ipPort = PORT_MASTER; } else pMasterPort = szMasterPort; } // Construct a full string Q_snprintf( szMaster, sizeof( szMaster ), "%s:%i", pMasterAddress, ipPort ); // Convert to a valid address if ( !NET_StringToAdr ( szMaster, &adr ) ) { Con_Printf(" Invalid address \"%s\", setmaster command ignored\n", szMaster ); return; } if ( !stricmp( pszCmd, "add" ) ) { master->AddServer( &adr ); // If we get here, masters are definitely being used. m_bNoMasters = false; Con_Printf ( "Adding master at %s\n", szMaster ); } else { // Find master server adrlist_t *p; if ( !m_pMasterAddresses ) { Con_Printf( "Can't remove master, list is empty\n" ); return; } p = m_pMasterAddresses; while ( p ) { if ( NET_CompareAdr( p->adr, adr ) ) break; p = p->next; } if ( !p ) { Con_Printf( "Can't remove master %s, not in list\n", szMaster ); return; } // p is pointing at the master to remove. if ( p == m_pMasterAddresses ) { m_pMasterAddresses = m_pMasterAddresses->next; free( p ); } else { adrlist_t *pbefore; pbefore = m_pMasterAddresses; while ( pbefore ) { if ( pbefore->next == p ) break; pbefore = pbefore->next; } if ( !pbefore ) // !!! return; // Unlink p pbefore->next = p->next; free( p ); } }}//-----------------------------------------------------------------------------// 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; p = p->next; }}//-----------------------------------------------------------------------------// Purpose: //-----------------------------------------------------------------------------void SetMaster_f( void ){ master->SetMaster_f();}//-----------------------------------------------------------------------------// Purpose: //-----------------------------------------------------------------------------void Heartbeat_f( void ){ master->Heartbeat_f();}static ConCommand setmaster("setmaster", SetMaster_f ); static ConCommand heartbeat("heartbeat", Heartbeat_f ); //-----------------------------------------------------------------------------// Purpose: Adds master server console commands//-----------------------------------------------------------------------------void CMaster::Init( void ){}//-----------------------------------------------------------------------------// 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;}