Пример #1
0
        /// <summary>
        /// Queue initial heartbeats to active servers
        /// </summary>
        public void QueueInitialHeartbeats()
        {
            DateTime Now = DateTime.UtcNow;
            uint     HeartbeatsStarted = 0;

            lock (ActiveServerTable)
            {
                foreach (var Pair in ActiveServerTable)
                {
                    NWGameServer Server = Pair.Value;

                    lock (Server)
                    {
                        if (!Server.Online)
                        {
                            continue;
                        }

                        Server.InitialHeartbeat = true;
                        Server.StartHeartbeat();
                        HeartbeatsStarted += 1;
                    }
                }
            }

            Logger.Log(LogLevel.Normal, "NWServerTracker.QueueInitialHeartbeats(): Queued {0} initial server heartbeat requests.", HeartbeatsStarted);

            PendingGameServersSweepTimer.Start();
            ScavengerSweepTimer.Start();
            BlacklistSweepTimer.Start();
        }
Пример #2
0
        /// <summary>
        /// Request a heartbeat on behalf of a server object.
        /// </summary>
        /// <param name="Server">Supplies the server object instance.</param>
        /// <returns>True if future heartbeats are enabled.</returns>
        public bool RequestHeartbeat(NWGameServer Server)
        {
            //
            // Check if heartbeats are enabled.  If so, take the heartbeat lock
            // and request the heartbeat.
            //

            lock (HeartbeatLock)
            {
                if (!HeartbeatsEnabled)
                {
                    return(false);
                }

                MasterServer.RefreshServerStatus(Server.ServerAddress);
            }

            return(true);
        }
Пример #3
0
        /// <summary>
        /// Attempt to look up a game server by address, creating the server if
        /// requested (if it did not exist).
        /// </summary>
        /// <param name="ServerAddress">Supplies the server address to look up
        /// in the active server table.</param>
        /// <param name="Create">Supplies true if the server instance should be
        /// created if it did not exist.</param>
        /// <returns>The active server instance, else null if no server
        /// satisfied the given criteria.</returns>
        public NWGameServer LookupServerByAddress(IPEndPoint ServerAddress, bool Create = true)
        {
            NWGameServer Server;

            lock (ActiveServerTable)
            {
                if (!ActiveServerTable.TryGetValue(ServerAddress, out Server))
                {
                    Server = null;
                }

                if ((Create != false) && (Server == null))
                {
                    Server = new NWGameServer(MasterServer, ServerAddress);
                    Server.Load();
                    ActiveServerTable.Add(ServerAddress, Server);
                }
            }

            return(Server);
        }
Пример #4
0
        /// <summary>
        /// This method attempts to determine whether a different server, homed
        /// at the same address, is a "NAT duplicate" of the current server.
        ///
        /// Some broken NAT devices will pick an ephemeral source port for the
        /// master heartbeat ping, and will (for a short time) even respond on
        /// this port, even though they also respond on the declared server
        /// internal port, which is the canonical address to select.
        ///
        /// However, it is legitimate for two different servers to reside at
        /// different ports on the same IP address.  In order to distinguish
        /// between these conditions, a test is made as to whether the data set
        /// returned by a BNXR message from both servers is identical, for the
        /// configuration settings.  If so, then a NAT duplicate is declared,
        /// otherwise both servers are considered to be legitimately distinct.
        ///
        /// In the condition where a NAT duplicate is detected, the internal
        /// advertised address of the server is assumed to be the canonical
        /// address.  In such a case, the InternalServer argument corresponds
        /// to the canonical server, and the this pointer corresponds to the
        /// NAT duplicate.  The NAT duplicate is marked offline and its
        /// heartbeat is then descheduled.
        /// </summary>
        /// <param name="InternalServer">Supplies the potential canonical half
        /// of a NAT duplicate server, to be compared against.</param>
        /// <returns>True if the current server was marked offline as a NAT
        /// duplicate.</returns>
        public bool CheckForNATDuplicate(NWGameServer InternalServer)
        {
            lock (this)
            {
                lock (InternalServer)
                {
                    if ((ModuleName == InternalServer.ModuleName) &&
                        (PrivateServer == InternalServer.PrivateServer) &&
                        (MinimumLevel == InternalServer.MinimumLevel) &&
                        (MaximumLevel == InternalServer.MaximumLevel) &&
                        (MaximumPlayerCount == InternalServer.MaximumPlayerCount) &&
                        (LocalVault == InternalServer.LocalVault) &&
                        (PVPLevel == InternalServer.PVPLevel) &&
                        (PlayerPause == InternalServer.PlayerPause) &&
                        (OnePartyOnly == InternalServer.OnePartyOnly) &&
                        (ELCEnforced == InternalServer.ELCEnforced) &&
                        (ILREnforced == InternalServer.ILREnforced) &&
                        (ExpansionsMask == InternalServer.ExpansionsMask))
                    {
                        NATDuplicateTick = (uint)Environment.TickCount;

                        if (Online)
                        {
                            Online = false;
                            StopHeartbeat();
                        }

                        Save();

                        return(true);
                    }
                }
            }

            return(false);
        }
Пример #5
0
        /// <summary>
        /// Attempt to load data from the database.  The server is assumed to
        /// be either locked or not yet published.
        /// </summary>
        public void Load()
        {
            string Query = String.Format(
                @"SELECT `game_server_id`,
    `expansions_mask`,
    `build_number`,
    `module_name`,
    `server_name`,
    `active_player_count`,
    `maximum_player_count`,
    `local_vault`,
    `last_heartbeat`,
    `server_address`,
    `online`,
    `private_server`,
    `module_description`,
    `module_url`,
    `game_type`,
    `minimum_level`,
    `maximum_level`,
    `pvp_level`,
    `player_pause`,
    `one_party_only`,
    `elc_enforced`,
    `ilr_enforced`,
    `pwc_url`,
    `server_description`
FROM `game_servers`
WHERE `product_id` = {0}
AND `server_address` = '{1}'",
                MasterServer.ProductID,
                MySqlHelper.EscapeString(ServerAddress.ToString()));

            try
            {
                using (MySqlDataReader Reader = MasterServer.ExecuteQuery(Query))
                {
                    if (!Reader.Read())
                    {
                        return;
                    }

                    DatabaseId = Reader.GetUInt32(0);

                    NWGameServer Server = new NWGameServer(MasterServer, ServerAddress);

                    ExpansionsMask      = (Byte)Reader.GetUInt32(1);
                    BuildNumber         = (UInt16)Reader.GetUInt32(2);
                    ModuleName          = Reader.GetString(3);
                    ServerName          = Reader.GetString(4);
                    MaximumPlayerCount  = Reader.GetUInt32(6);
                    LocalVault          = Reader.GetBoolean(7);
                    PrivateServer       = Reader.GetBoolean(11);
                    ModuleDescription   = Reader.GetString(12);
                    ModuleUrl           = Reader.GetString(13);
                    GameType            = Reader.GetUInt32(14);
                    MinimumLevel        = Reader.GetUInt32(15);
                    MaximumLevel        = Reader.GetUInt32(16);
                    PVPLevel            = Reader.GetUInt32(17);
                    Server.PlayerPause  = Reader.GetBoolean(18);
                    Server.OnePartyOnly = Reader.GetBoolean(19);
                    Server.ELCEnforced  = Reader.GetBoolean(20);
                    Server.ILREnforced  = Reader.GetBoolean(21);
                    PWCUrl            = Reader.GetString(22);
                    ServerDescription = Reader.GetString(23);

                    if (Online)
                    {
                        StartHeartbeat();
                    }
                }
            }
            catch (Exception e)
            {
                Logger.Log(LogLevel.Error, "NWMasterServer.Load(): Failed to load server {0}: Exception: {1}", ServerAddress, e);
            }
        }
Пример #6
0
        /// <summary>
        /// This timer callback runs when the scavenge sweep timer elapses.
        /// Its purpose is to cycle through offline game_servers records in the
        /// database, which have had a heartbeat more recent than
        /// ScavengerTimeSpan since the current time.  Such servers are then
        /// re-pinged in a low frequency interval so as to allow servers that
        /// had gone offline for a long period of time (but had been returned
        /// to service afterwards) to be automatically re-listed eventually.
        /// </summary>
        /// <param name="sender">Unused.</param>
        /// <param name="e">Unused.</param>
        private void ScavengerSweepTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                bool     Processed = false;
                DateTime Now       = DateTime.UtcNow;
                string   Query     = String.Format(
                    @"SELECT `game_server_id`,
    `server_address` 
FROM `game_servers` 
WHERE `product_id` = {0} 
AND `game_server_id` > {1}
AND `online` = false 
AND `last_heartbeat` >= '{2}' 
GROUP BY `game_server_id` 
ORDER BY `game_server_id` 
LIMIT 50",
                    MasterServer.ProductID,
                    ScavengeServerId,
                    MasterServer.DateToSQLDate(Now.Subtract(ScavengerTimeSpan)));

                //
                // Examine each server record returned and, if the server is
                // still offline, enqueue a ping to it.  Advance the iterator
                // to the next server as each server is processed.
                //

                using (MySqlDataReader Reader = MasterServer.ExecuteQuery(Query))
                {
                    while (Reader.Read())
                    {
                        uint       ServerId      = Reader.GetUInt32(0);
                        IPEndPoint ServerAddress = ConvertServerHostnameToIPEndPoint(Reader.GetString(1));

                        if (ServerId > ScavengeServerId)
                        {
                            ScavengeServerId = ServerId;
                        }

                        Processed = true;

                        if (ServerAddress == null)
                        {
                            continue;
                        }

                        NWGameServer Server = LookupServerByAddress(ServerAddress, false);

                        if (Server == null || Server.Online == false)
                        {
                            MasterServer.SendServerInfoRequest(ServerAddress);
                        }
                    }
                }

                //
                // If no servers were processed, then the end of the iterator
                // list must have been reached.  Re-start the sweep cycle at
                // the beginning next time.
                //

                if (Processed == false)
                {
                    ScavengeServerId = 0;
                }
            }
            catch (Exception ex)
            {
                Logger.Log(LogLevel.Error, "NWServerTracker.ScavengeSweepTimer_Elapsed(): Excepton processing scavenge server list: {0}", ex);
            }

            lock (HeartbeatLock)
            {
                if (!HeartbeatsEnabled)
                {
                    return;
                }

                ScavengerSweepTimer.Start();
            }
        }
Пример #7
0
        /// <summary>
        /// This timer callback runs when the pending game servers sweep timer
        /// elapses.  It checks whether there is a pending query queue, and if
        /// so, flushes it as appropriate.
        /// </summary>
        /// <param name="sender">Unused.</param>
        /// <param name="e">Unused.</param>
        private void PendingGameServersSweepTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                uint   HighestPendingServerId = 0;
                bool   DataReturned           = false;
                string Query = String.Format(
                    @"SELECT `pending_game_server_id`, 
    `server_address` 
FROM 
    `pending_game_servers` 
WHERE 
    `product_id` = {0} 
GROUP BY `pending_game_server_id` 
ORDER BY `pending_game_server_id` 
LIMIT 100 ",
                    MasterServer.ProductID);

                using (MySqlDataReader Reader = MasterServer.ExecuteQuery(Query))
                {
                    while (Reader.Read())
                    {
                        uint       PendingServerId = Reader.GetUInt32(0);
                        string     Hostname        = Reader.GetString(1);
                        IPEndPoint ServerAddress;

                        if (PendingServerId > HighestPendingServerId)
                        {
                            HighestPendingServerId = PendingServerId;
                        }

                        if (DataReturned == false)
                        {
                            DataReturned = true;
                        }

                        ServerAddress = ConvertServerHostnameToIPEndPoint(Hostname);
                        if (ServerAddress == null)
                        {
                            Logger.Log(LogLevel.Error, "NWServerTracker.PendingGameServersSweepTimer_Elapsed(): Server {0} has invalid hostname '{1}'.",
                                       PendingServerId,
                                       Hostname);
                            continue;
                        }

                        NWGameServer Server = LookupServerByAddress(ServerAddress, false);

                        if (Server == null || Server.Online == false)
                        {
                            MasterServer.SendServerInfoRequest(ServerAddress);
                        }
                    }
                }

                if (DataReturned)
                {
                    //
                    // Remove records that have already been processed.
                    //

                    MasterServer.ExecuteQueryNoReader(String.Format(
                                                          @"DELETE FROM 
    `pending_game_servers` 
WHERE 
    `product_id` = {0} 
AND 
    `pending_game_server_id` < {1}",
                                                          MasterServer.ProductID,
                                                          HighestPendingServerId + 1));
                }
            }
            catch (Exception ex)
            {
                Logger.Log(LogLevel.Error, "NWServerTracker.PendingGameServersSweepTimer_Elapsed(): Exception processing pending servers list: {0}.", ex);
            }

            lock (HeartbeatLock)
            {
                if (!HeartbeatsEnabled)
                {
                    return;
                }

                PendingGameServersSweepTimer.Start();
            }
        }
Пример #8
0
        /// <summary>
        /// Prepare the database, creating tables (if they did not already
        /// exist).
        /// </summary>
        private void InitializeDatabase()
        {
            uint ServersAdded;

            if (String.IsNullOrEmpty(MasterServer.ConnectionString))
            {
                return;
            }

            try
            {
                MasterServer.ExecuteQueryNoReader(
                    @"CREATE TABLE IF NOT EXISTS `game_servers` (
    `game_server_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `product_id` int(10) UNSIGNED NOT NULL,
    `expansions_mask` int(10) UNSIGNED NOT NULL,
    `build_number` int(10) UNSIGNED NOT NULL,
    `module_name` varchar(32) NOT NULL,
    `server_name` varchar(256) NOT NULL,
    `active_player_count` int(10) UNSIGNED NOT NULL,
    `maximum_player_count` int(10) UNSIGNED NOT NULL,
    `local_vault` bool NOT NULL,
    `last_heartbeat` datetime NOT NULL,
    `server_address` varchar(128) NOT NULL,
    `online` bool NOT NULL,
    `private_server` bool NOT NULL,
    `module_description` varchar(256) NOT NULL,
    `module_url` varchar(256) NOT NULL,
    `game_type` int(10) UNSIGNED NOT NULL,
    `minimum_level` int(10) UNSIGNED NOT NULL,
    `maximum_level` int(10) UNSIGNED NOT NULL,
    `pvp_level` int(10) UNSIGNED NOT NULL,
    `player_pause` bool NOT NULL,
    `one_party_only` bool NOT NULL,
    `elc_enforced` bool NOT NULL,
    `ilr_enforced` bool NOT NULL,
    `pwc_url` varchar(256) NOT NULL,
    `server_description` varchar(256) NOT NULL,
    `game_server_group_id` int(10) UNSIGNED,
    PRIMARY KEY (`game_server_id`),
    UNIQUE KEY (`product_id`, `server_address`),
    INDEX (`product_id`, `online`),
    INDEX (`product_id`, `online`, `server_name`),
    INDEX (`product_id`, `online`, `module_name`),
    INDEX (`product_id`, `online`, `game_type`),
    INDEX (`product_id`, `online`, `game_server_group_id`)
    )");

                MasterServer.ExecuteQueryNoReader(
                    @"CREATE TABLE IF NOT EXISTS `game_server_groups` (
    `game_server_group_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `product_id` int(10) UNSIGNED NOT NULL,
    `group_name` varchar(128) NOT NULL,
    `global_user_counts` bool NOT NULL,
    PRIMARY KEY (`game_server_group_id`),
    UNIQUE KEY (`product_id`, `group_name`)
    )");

                MasterServer.ExecuteQueryNoReader(
                    @"CREATE TABLE IF NOT EXISTS `pending_game_servers` (
    `pending_game_server_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `product_id` int(10) UNSIGNED NOT NULL,
    `server_address` varchar(128) NOT NULL,
    PRIMARY KEY (`pending_game_server_id`),
    UNIQUE KEY (`product_id`, `server_address`)
    )");

                MasterServer.ExecuteQueryNoReader(
                    @"CREATE TABLE IF NOT EXISTS `client_extension_update` (
    `update_id` int(10) UNSIGNED NOT NULL,
    `product_id` int(10) UNSIGNED NOT NULL,
    `update_message` varchar(4096) NOT NULL,
    `update_url` varchar(4096) NOT NULL,
    `update_info_url` varchar(4096) NOT NULL,
    `update_version` varchar(128) NOT NULL,
    `update_motd` varchar(4096) NOT NULL,
    PRIMARY KEY (`update_id`)
    )");

                MasterServer.ExecuteQueryNoReader(
                    @"CREATE TABLE IF NOT EXISTS `stat_counters` (
    `stat_counter_name` varchar(64) NOT NULL,
    `stat_counter_value` int(10) UNSIGNED NOT NULL,
    `stat_counter_last_update` datetime NOT NULL,
    PRIMARY KEY (`stat_counter_name`)
    )");

                MasterServer.ExecuteQueryNoReader(
                    @"CREATE TABLE IF NOT EXISTS `blacklist_entries` (
    `blacklist_entry_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `product_id` int(10) UNSIGNED NOT NULL,
    `blacklist_entry_type` int(10) UNSIGNED NOT NULL,
    `blacklist_entry_match` varchar(128) NOT NULL,
    PRIMARY KEY (`blacklist_entry_id`)
    )");

                string Query = String.Format(
                    @"SELECT `game_server_id`,
    `expansions_mask`,
    `build_number`,
    `module_name`,
    `server_name`,
    `active_player_count`,
    `maximum_player_count`,
    `local_vault`,
    `last_heartbeat`,
    `server_address`,
    `online`,
    `private_server`,
    `module_description`,
    `module_url`,
    `game_type`,
    `minimum_level`,
    `maximum_level`,
    `pvp_level`,
    `player_pause`,
    `one_party_only`,
    `elc_enforced`,
    `ilr_enforced`,
    `pwc_url`, 
    `server_description` 
FROM `game_servers`
WHERE `product_id` = {0}
AND `online` = true",
                    MasterServer.ProductID);

                RefreshBlacklist();

                ServersAdded = 0;
                using (MySqlDataReader Reader = MasterServer.ExecuteQuery(Query))
                {
                    while (Reader.Read())
                    {
                        uint       ServerId = Reader.GetUInt32(0);
                        string     Hostname = Reader.GetString(9);
                        int        i        = Hostname.IndexOf(':');
                        IPEndPoint ServerAddress;

                        if (i == -1)
                        {
                            Logger.Log(LogLevel.Error, "NWServerTracker.InitializeDatabase(): Server {0} has invalid hostname '{1}'.",
                                       ServerId,
                                       Hostname);
                            continue;
                        }

                        try
                        {
                            ServerAddress = new IPEndPoint(
                                IPAddress.Parse(Hostname.Substring(0, i)),
                                Convert.ToInt32(Hostname.Substring(i + 1)));
                        }
                        catch (Exception e)
                        {
                            Logger.Log(LogLevel.Error, "NWServerTracker.InitializeDatabase(): Error initializing hostname {0} for server {1}: Exception: {2}",
                                       Hostname,
                                       ServerId,
                                       e);
                            continue;
                        }

                        //
                        // Query for whether the server is blacklisted and do
                        // not re-list it if so.
                        //

                        BlacklistLookup Lookup = new BlacklistLookup();

                        Lookup.ServerAddress = ServerAddress;
                        Lookup.ModuleName    = Reader.GetString(3);

                        if (IsBlacklisted(Lookup))
                        {
                            continue;
                        }

                        NWGameServer Server = new NWGameServer(MasterServer, ServerAddress);

                        Server.DatabaseId         = ServerId;
                        Server.ExpansionsMask     = (Byte)Reader.GetUInt32(1);
                        Server.BuildNumber        = (UInt16)Reader.GetUInt32(2);
                        Server.ModuleName         = Reader.GetString(3);
                        Server.ServerName         = Reader.GetString(4);
                        Server.ActivePlayerCount  = Reader.GetUInt32(5);
                        Server.MaximumPlayerCount = Reader.GetUInt32(6);
                        Server.LocalVault         = Reader.GetBoolean(7);
                        Server.LastHeartbeat      = Reader.GetDateTime(8);
                        Server.Online             = Reader.GetBoolean(10);
                        Server.PrivateServer      = Reader.GetBoolean(11);
                        Server.ModuleDescription  = Reader.GetString(12);
                        Server.ModuleUrl          = Reader.GetString(13);
                        Server.GameType           = Reader.GetUInt32(14);
                        Server.MinimumLevel       = Reader.GetUInt32(15);
                        Server.MaximumLevel       = Reader.GetUInt32(16);
                        Server.PVPLevel           = Reader.GetUInt32(17);
                        Server.PlayerPause        = Reader.GetBoolean(18);
                        Server.OnePartyOnly       = Reader.GetBoolean(19);
                        Server.ELCEnforced        = Reader.GetBoolean(20);
                        Server.ILREnforced        = Reader.GetBoolean(21);
                        Server.PWCUrl             = Reader.GetString(22);
                        Server.ServerDescription  = Reader.GetString(23);

                        lock (ActiveServerTable)
                        {
                            ActiveServerTable.Add(ServerAddress, Server);
                        }

                        ServersAdded += 1;
                    }
                }

                Logger.Log(LogLevel.Normal, "NWServerTracker.InitializeDatabase(): Read {0} active servers from database.", ServersAdded);
            }
            catch (Exception e)
            {
                Logger.Log(LogLevel.Error, "NWServerTracker.InitializeDatabase(): Exception: {0}", e);
                MasterServer.Stop();
            }
        }
Пример #9
0
        /// <summary>
        /// Attempt to load data from the database.  The server is assumed to
        /// be either locked or not yet published.
        /// </summary>
        public void Load()
        {
            string Query = String.Format(
            @"SELECT `game_server_id`,
            `expansions_mask`,
            `build_number`,
            `module_name`,
            `server_name`,
            `active_player_count`,
            `maximum_player_count`,
            `local_vault`,
            `last_heartbeat`,
            `server_address`,
            `online`,
            `private_server`,
            `module_description`,
            `module_url`,
            `game_type`,
            `minimum_level`,
            `maximum_level`,
            `pvp_level`,
            `player_pause`,
            `one_party_only`,
            `elc_enforced`,
            `ilr_enforced`,
            `pwc_url`,
            `server_description`
            FROM `game_servers`
            WHERE `product_id` = {0}
            AND `server_address` = '{1}'",
                MasterServer.ProductID,
                MySqlHelper.EscapeString(ServerAddress.ToString()));

            try
            {
                using (MySqlDataReader Reader = MasterServer.ExecuteQuery(Query))
                {
                    if (!Reader.Read())
                        return;

                    DatabaseId = Reader.GetUInt32(0);

                    NWGameServer Server = new NWGameServer(MasterServer, ServerAddress);

                    ExpansionsMask = (Byte)Reader.GetUInt32(1);
                    BuildNumber = (UInt16)Reader.GetUInt32(2);
                    ModuleName = Reader.GetString(3);
                    ServerName = Reader.GetString(4);
                    MaximumPlayerCount = Reader.GetUInt32(6);
                    LocalVault = Reader.GetBoolean(7);
                    PrivateServer = Reader.GetBoolean(11);
                    ModuleDescription = Reader.GetString(12);
                    ModuleUrl = Reader.GetString(13);
                    GameType = Reader.GetUInt32(14);
                    MinimumLevel = Reader.GetUInt32(15);
                    MaximumLevel = Reader.GetUInt32(16);
                    PVPLevel = Reader.GetUInt32(17);
                    Server.PlayerPause = Reader.GetBoolean(18);
                    Server.OnePartyOnly = Reader.GetBoolean(19);
                    Server.ELCEnforced = Reader.GetBoolean(20);
                    Server.ILREnforced = Reader.GetBoolean(21);
                    PWCUrl = Reader.GetString(22);
                    ServerDescription = Reader.GetString(23);

                    if (Online)
                        StartHeartbeat();
                }
            }
            catch (Exception e)
            {
                Logger.Log(LogLevel.Error, "NWMasterServer.Load(): Failed to load server {0}: Exception: {1}", ServerAddress, e);
            }
        }
Пример #10
0
        /// <summary>
        /// This method attempts to determine whether a different server, homed
        /// at the same address, is a "NAT duplicate" of the current server.
        ///
        /// Some broken NAT devices will pick an ephemeral source port for the
        /// master heartbeat ping, and will (for a short time) even respond on
        /// this port, even though they also respond on the declared server
        /// internal port, which is the canonical address to select.
        ///
        /// However, it is legitimate for two different servers to reside at
        /// different ports on the same IP address.  In order to distinguish
        /// between these conditions, a test is made as to whether the data set
        /// returned by a BNXR message from both servers is identical, for the
        /// configuration settings.  If so, then a NAT duplicate is declared,
        /// otherwise both servers are considered to be legitimately distinct.
        /// 
        /// In the condition where a NAT duplicate is detected, the internal
        /// advertised address of the server is assumed to be the canonical
        /// address.  In such a case, the InternalServer argument corresponds
        /// to the canonical server, and the this pointer corresponds to the
        /// NAT duplicate.  The NAT duplicate is marked offline and its
        /// heartbeat is then descheduled.
        /// </summary>
        /// <param name="InternalServer">Supplies the potential canonical half
        /// of a NAT duplicate server, to be compared against.</param>
        /// <returns>True if the current server was marked offline as a NAT
        /// duplicate.</returns>
        public bool CheckForNATDuplicate(NWGameServer InternalServer)
        {
            lock (this)
            {
                lock (InternalServer)
                {
                    if ((ModuleName == InternalServer.ModuleName) &&
                        (PrivateServer == InternalServer.PrivateServer) &&
                        (MinimumLevel == InternalServer.MinimumLevel) &&
                        (MaximumLevel == InternalServer.MaximumLevel) &&
                        (MaximumPlayerCount == InternalServer.MaximumPlayerCount) &&
                        (LocalVault == InternalServer.LocalVault) &&
                        (PVPLevel == InternalServer.PVPLevel) &&
                        (PlayerPause == InternalServer.PlayerPause) &&
                        (OnePartyOnly == InternalServer.OnePartyOnly) &&
                        (ELCEnforced == InternalServer.ELCEnforced) &&
                        (ILREnforced == InternalServer.ILREnforced) &&
                        (ExpansionsMask == InternalServer.ExpansionsMask))
                    {
                        NATDuplicateTick = (uint)Environment.TickCount;

                        if (Online)
                        {
                            Online = false;
                            StopHeartbeat();
                        }

                        Save();

                        return true;
                    }
                }
            }

            return false;
        }
Пример #11
0
        /// <summary>
        /// Prepare the database, creating tables (if they did not already
        /// exist).
        /// </summary>
        private void InitializeDatabase()
        {
            uint ServersAdded;

            if (String.IsNullOrEmpty(MasterServer.ConnectionString))
                return;

            try
            {
                MasterServer.ExecuteQueryNoReader(
            @"CREATE TABLE IF NOT EXISTS `game_servers` (
            `game_server_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `product_id` int(10) UNSIGNED NOT NULL,
            `expansions_mask` int(10) UNSIGNED NOT NULL,
            `build_number` int(10) UNSIGNED NOT NULL,
            `module_name` varchar(32) NOT NULL,
            `server_name` varchar(256) NOT NULL,
            `active_player_count` int(10) UNSIGNED NOT NULL,
            `maximum_player_count` int(10) UNSIGNED NOT NULL,
            `local_vault` bool NOT NULL,
            `last_heartbeat` datetime NOT NULL,
            `server_address` varchar(128) NOT NULL,
            `online` bool NOT NULL,
            `private_server` bool NOT NULL,
            `module_description` varchar(256) NOT NULL,
            `module_url` varchar(256) NOT NULL,
            `game_type` int(10) UNSIGNED NOT NULL,
            `minimum_level` int(10) UNSIGNED NOT NULL,
            `maximum_level` int(10) UNSIGNED NOT NULL,
            `pvp_level` int(10) UNSIGNED NOT NULL,
            `player_pause` bool NOT NULL,
            `one_party_only` bool NOT NULL,
            `elc_enforced` bool NOT NULL,
            `ilr_enforced` bool NOT NULL,
            `pwc_url` varchar(256) NOT NULL,
            `server_description` varchar(256) NOT NULL,
            `game_server_group_id` int(10) UNSIGNED,
            PRIMARY KEY (`game_server_id`),
            UNIQUE KEY (`product_id`, `server_address`),
            INDEX (`product_id`, `online`),
            INDEX (`product_id`, `online`, `server_name`),
            INDEX (`product_id`, `online`, `module_name`),
            INDEX (`product_id`, `online`, `game_type`),
            INDEX (`product_id`, `online`, `game_server_group_id`)
            )");

                MasterServer.ExecuteQueryNoReader(
            @"CREATE TABLE IF NOT EXISTS `game_server_groups` (
            `game_server_group_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `product_id` int(10) UNSIGNED NOT NULL,
            `group_name` varchar(128) NOT NULL,
            `global_user_counts` bool NOT NULL,
            PRIMARY KEY (`game_server_group_id`),
            UNIQUE KEY (`product_id`, `group_name`)
            )");

                MasterServer.ExecuteQueryNoReader(
            @"CREATE TABLE IF NOT EXISTS `pending_game_servers` (
            `pending_game_server_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `product_id` int(10) UNSIGNED NOT NULL,
            `server_address` varchar(128) NOT NULL,
            PRIMARY KEY (`pending_game_server_id`),
            UNIQUE KEY (`product_id`, `server_address`)
            )");

                MasterServer.ExecuteQueryNoReader(
            @"CREATE TABLE IF NOT EXISTS `client_extension_update` (
            `update_id` int(10) UNSIGNED NOT NULL,
            `product_id` int(10) UNSIGNED NOT NULL,
            `update_message` varchar(4096) NOT NULL,
            `update_url` varchar(4096) NOT NULL,
            `update_info_url` varchar(4096) NOT NULL,
            `update_version` varchar(128) NOT NULL,
            `update_motd` varchar(4096) NOT NULL,
            PRIMARY KEY (`update_id`)
            )");

                MasterServer.ExecuteQueryNoReader(
            @"CREATE TABLE IF NOT EXISTS `stat_counters` (
            `stat_counter_name` varchar(64) NOT NULL,
            `stat_counter_value` int(10) UNSIGNED NOT NULL,
            `stat_counter_last_update` datetime NOT NULL,
            PRIMARY KEY (`stat_counter_name`)
            )");

                MasterServer.ExecuteQueryNoReader(
            @"CREATE TABLE IF NOT EXISTS `blacklist_entries` (
            `blacklist_entry_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `product_id` int(10) UNSIGNED NOT NULL,
            `blacklist_entry_type` int(10) UNSIGNED NOT NULL,
            `blacklist_entry_match` varchar(128) NOT NULL,
            PRIMARY KEY (`blacklist_entry_id`)
            )");

                string Query = String.Format(
            @"SELECT `game_server_id`,
            `expansions_mask`,
            `build_number`,
            `module_name`,
            `server_name`,
            `active_player_count`,
            `maximum_player_count`,
            `local_vault`,
            `last_heartbeat`,
            `server_address`,
            `online`,
            `private_server`,
            `module_description`,
            `module_url`,
            `game_type`,
            `minimum_level`,
            `maximum_level`,
            `pvp_level`,
            `player_pause`,
            `one_party_only`,
            `elc_enforced`,
            `ilr_enforced`,
            `pwc_url`,
            `server_description`
            FROM `game_servers`
            WHERE `product_id` = {0}
            AND `online` = true",
                    MasterServer.ProductID);

                RefreshBlacklist();

                ServersAdded = 0;
                using (MySqlDataReader Reader = MasterServer.ExecuteQuery(Query))
                {
                    while (Reader.Read())
                    {
                        uint ServerId = Reader.GetUInt32(0);
                        string Hostname = Reader.GetString(9);
                        int i = Hostname.IndexOf(':');
                        IPEndPoint ServerAddress;

                        if (i == -1)
                        {
                            Logger.Log(LogLevel.Error, "NWServerTracker.InitializeDatabase(): Server {0} has invalid hostname '{1}'.",
                                ServerId,
                                Hostname);
                            continue;
                        }

                        try
                        {
                            ServerAddress = new IPEndPoint(
                                IPAddress.Parse(Hostname.Substring(0, i)),
                                Convert.ToInt32(Hostname.Substring(i + 1)));
                        }
                        catch (Exception e)
                        {
                            Logger.Log(LogLevel.Error, "NWServerTracker.InitializeDatabase(): Error initializing hostname {0} for server {1}: Exception: {2}",
                                Hostname,
                                ServerId,
                                e);
                            continue;
                        }

                        //
                        // Query for whether the server is blacklisted and do
                        // not re-list it if so.
                        //

                        BlacklistLookup Lookup = new BlacklistLookup();

                        Lookup.ServerAddress = ServerAddress;
                        Lookup.ModuleName = Reader.GetString(3);

                        if (IsBlacklisted(Lookup))
                            continue;

                        NWGameServer Server = new NWGameServer(MasterServer, ServerAddress);

                        Server.DatabaseId = ServerId;
                        Server.ExpansionsMask = (Byte)Reader.GetUInt32(1);
                        Server.BuildNumber = (UInt16)Reader.GetUInt32(2);
                        Server.ModuleName = Reader.GetString(3);
                        Server.ServerName = Reader.GetString(4);
                        Server.ActivePlayerCount = Reader.GetUInt32(5);
                        Server.MaximumPlayerCount = Reader.GetUInt32(6);
                        Server.LocalVault = Reader.GetBoolean(7);
                        Server.LastHeartbeat = Reader.GetDateTime(8);
                        Server.Online = Reader.GetBoolean(10);
                        Server.PrivateServer = Reader.GetBoolean(11);
                        Server.ModuleDescription = Reader.GetString(12);
                        Server.ModuleUrl = Reader.GetString(13);
                        Server.GameType = Reader.GetUInt32(14);
                        Server.MinimumLevel = Reader.GetUInt32(15);
                        Server.MaximumLevel = Reader.GetUInt32(16);
                        Server.PVPLevel = Reader.GetUInt32(17);
                        Server.PlayerPause = Reader.GetBoolean(18);
                        Server.OnePartyOnly = Reader.GetBoolean(19);
                        Server.ELCEnforced = Reader.GetBoolean(20);
                        Server.ILREnforced = Reader.GetBoolean(21);
                        Server.PWCUrl = Reader.GetString(22);
                        Server.ServerDescription = Reader.GetString(23);

                        lock (ActiveServerTable)
                        {
                            ActiveServerTable.Add(ServerAddress, Server);
                        }

                        ServersAdded += 1;
                    }
                }

                Logger.Log(LogLevel.Normal, "NWServerTracker.InitializeDatabase(): Read {0} active servers from database.", ServersAdded);
            }
            catch (Exception e)
            {
                Logger.Log(LogLevel.Error, "NWServerTracker.InitializeDatabase(): Exception: {0}", e);
                MasterServer.Stop();
            }
        }
Пример #12
0
        /// <summary>
        /// Request a heartbeat on behalf of a server object.
        /// </summary>
        /// <param name="Server">Supplies the server object instance.</param>
        /// <returns>True if future heartbeats are enabled.</returns>
        public bool RequestHeartbeat(NWGameServer Server)
        {
            //
            // Check if heartbeats are enabled.  If so, take the heartbeat lock
            // and request the heartbeat.
            //

            lock (HeartbeatLock)
            {
                if (!HeartbeatsEnabled)
                    return false;

                MasterServer.RefreshServerStatus(Server.ServerAddress);
            }

            return true;
        }
Пример #13
0
        /// <summary>
        /// Attempt to look up a game server by address, creating the server if
        /// requested (if it did not exist).
        /// </summary>
        /// <param name="ServerAddress">Supplies the server address to look up
        /// in the active server table.</param>
        /// <param name="Create">Supplies true if the server instance should be
        /// created if it did not exist.</param>
        /// <returns>The active server instance, else null if no server
        /// satisfied the given criteria.</returns>
        public NWGameServer LookupServerByAddress(IPEndPoint ServerAddress, bool Create = true)
        {
            NWGameServer Server;

            lock (ActiveServerTable)
            {
                if (!ActiveServerTable.TryGetValue(ServerAddress, out Server))
                    Server = null;

                if ((Create != false) && (Server == null))
                {
                    Server = new NWGameServer(MasterServer, ServerAddress);
                    Server.Load();
                    ActiveServerTable.Add(ServerAddress, Server);
                }
            }

            return Server;
        }