/// <summary> /// Perform a blacklist query. /// </summary> /// <param name="Lookup">Supplies the query input parameters.</param> /// <returns>True if the query matches a blacklist entry.</returns> public bool IsBlacklisted(BlacklistLookup Lookup) { // // Locally capture the current blacklist and sweep through the list // searching for a match. // var LocalBlacklist = Blacklist; foreach (BlacklistEntry Entry in LocalBlacklist) { if (Lookup.ServerAddress != null && Entry.IsBlacklistMatched(Lookup.ServerAddress.ToString(), BlacklistEntry.BlacklistType.BlacklistTypeServerAddress)) { return(true); } if (Lookup.ModuleName != null && Entry.IsBlacklistMatched(Lookup.ModuleName, BlacklistEntry.BlacklistType.BlacklistTypeModuleName)) { return(true); } } return(false); }
/// <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(); } }
/// <summary> /// This method parses and handles a module load notify message from a /// game server. /// </summary> /// <param name="Parser">Supplies the message parse context.</param> /// <param name="Sender">Supplies the game server address.</param> /// <param name="Socket">Supplies the associated socket descriptor /// upon which the message was received.</param> private void OnRecvMstModuleLoadNotify(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket) { Byte ExpansionsMask; string ModuleName; if (!Parser.ReadBYTE(out ExpansionsMask)) return; if (!Parser.ReadSmallString(out ModuleName, 16)) return; // // Query for whether the sender is blacklisted and drop the message // if so. // BlacklistLookup Lookup = new BlacklistLookup(); Lookup.ServerAddress = Sender; Lookup.ModuleName = ModuleName; if (ServerTracker.IsBlacklisted(Lookup)) return; NWGameServer Server = ServerTracker.LookupServerByAddress(Sender); // // Record the module load and request an updated player count. // Server.OnModuleLoad(ExpansionsMask, ModuleName); RefreshServerStatus(Sender); Logger.Log(LogLevel.Verbose, "NWMasterServer.OnRecvMstModuleLoadNotify(): Server {0} ModuleName={1} ExpansionsMask={2}.", Sender, ModuleName, ExpansionsMask); }
/// <summary> /// This method parses and handles a server startup notify message from /// a game server. /// </summary> /// <param name="Parser">Supplies the message parse context.</param> /// <param name="Sender">Supplies the game server address.</param> /// <param name="Socket">Supplies the associated socket descriptor /// upon which the message was received.</param> private void OnRecvMstStartupNotify(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket) { Byte Platform; UInt16 BuildNumber; Byte Unknown0; // 0 Byte Unknown1; // 0 Byte Unknown2; // 1 Byte Unknown3; // 0 Byte Unknown4; // 3 if (!Parser.ReadBYTE(out Platform)) return; if (!Parser.ReadWORD(out BuildNumber)) return; if (!Parser.ReadBYTE(out Unknown0)) return; if (!Parser.ReadBYTE(out Unknown1)) return; if (!Parser.ReadBYTE(out Unknown2)) return; if (!Parser.ReadBYTE(out Unknown3)) return; if (!Parser.ReadBYTE(out Unknown4)) return; // // Query for whether the sender is blacklisted and drop the message // if so. // BlacklistLookup Lookup = new BlacklistLookup(); Lookup.ServerAddress = Sender; if (ServerTracker.IsBlacklisted(Lookup)) return; NWGameServer Server = ServerTracker.LookupServerByAddress(Sender); // // Record the server startup. // Server.OnStartupNotify(Platform, BuildNumber); Logger.Log(LogLevel.Verbose, "NWMasterServer.OnRecvMstStartupNotify(): Server {0} Platform={1} BuildNumber={2}.", Sender, (char)Platform, BuildNumber); }
/// <summary> /// This method parses a server info response from a game server. /// </summary> /// <param name="Parser">Supplies the message parser context.</param> /// <param name="Sender">Supplies the game server address.</param> /// <param name="Socket">Supplies the associated socket descriptor /// upon which the message was received.</param> private void OnRecvServerInfoResponse(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket) { UInt16 DataPort; Byte Reserved; // 0xFC Byte HasPlayerPassword; Byte MinLevel; Byte MaxLevel; Byte ActivePlayers; Byte MaximumPlayers; Byte IsLocalVault; Byte PVPLevel; Byte IsPlayerPauseAllowed; Byte IsOnePartyOnly; Byte IsELC; Byte HasILR; Byte ExpansionsMask; string ModuleName; string BuildNumber; ServerInfo Info = new ServerInfo(); if (!Parser.ReadWORD(out DataPort)) return; if (!Parser.ReadBYTE(out Reserved)) return; if (Mode == GameMode.NWN2) { if (Reserved != 0xFC) return; } else { if (Reserved != 0xFD) return; } if (!Parser.ReadBYTE(out HasPlayerPassword)) return; if (!Parser.ReadBYTE(out MinLevel)) return; if (!Parser.ReadBYTE(out MaxLevel)) return; if (!Parser.ReadBYTE(out ActivePlayers)) return; if (!Parser.ReadBYTE(out MaximumPlayers)) return; if (!Parser.ReadBYTE(out IsLocalVault)) return; if (!Parser.ReadBYTE(out PVPLevel)) return; if (!Parser.ReadBYTE(out IsPlayerPauseAllowed)) return; if (!Parser.ReadBYTE(out IsOnePartyOnly)) return; if (!Parser.ReadBYTE(out IsELC)) return; if (!Parser.ReadBYTE(out HasILR)) return; if (!Parser.ReadBYTE(out ExpansionsMask)) return; if (!Parser.ReadSmallString(out ModuleName)) return; if (Mode == GameMode.NWN2) { if (!Parser.ReadSmallString(out BuildNumber)) return; } else { BuildNumber = "0"; } try { Info.BuildNumber = Convert.ToUInt16(BuildNumber); } catch { Info.BuildNumber = 0; } // // Query for whether the sender is blacklisted and drop the message // if so. // BlacklistLookup Lookup = new BlacklistLookup(); Lookup.ServerAddress = Sender; Lookup.ModuleName = ModuleName; if (ServerTracker.IsBlacklisted(Lookup)) return; Info.HasPlayerPassword = (HasPlayerPassword != 0); Info.MinLevel = MinLevel; Info.MaxLevel = MaxLevel; Info.ActivePlayers = ActivePlayers; Info.MaximumPlayers = MaximumPlayers; Info.IsLocalVault = (IsLocalVault != 0); Info.PVPLevel = PVPLevel; Info.IsPlayerPauseAllowed = (IsPlayerPauseAllowed != 0); Info.IsOnePartyOnly = (IsOnePartyOnly != 0); Info.IsELC = (IsELC != 0); Info.HasILR = (HasILR != 0); Info.ExpansionsMask = ExpansionsMask; Info.ModuleName = ModuleName; // // Look up the server and update the current server information. // Since the BNXR reply is used to differentiate between broken // NATs and endpoints with multiple servers on the same IP address, // carefully check for whether a duplicate server record exists on // the server internal port before creating a new server record. // NWGameServer Server; Server = ServerTracker.LookupServerByAddress(Sender); if (Mode != GameMode.NWN2) Info.BuildNumber = Server.BuildNumber; Server.OnServerInfoUpdate(Info); if (DataPort == (UInt16)Sender.Port) { // // Both internal and external ports match; the sender is not // likely behind a NAT. No action is necessary behind the // creation of the server record above. // } else { NWGameServer ServerInternal; IPEndPoint InternalAddress = new IPEndPoint(Sender.Address, (int)DataPort); ServerInternal = ServerTracker.LookupServerByAddress(InternalAddress, false); if (ServerInternal == null) { // // No record of a server existing at the internal address // is yet known. Proceed to create the server record at // the external address (as was already performed above). // } else { // // A record exists for both internal and external // addresses for the server. If the configuration values // between both servers are the same, then mark the // external address version as offline and prefer the // internal server address as authoritative (since it must // be globally reachable for a response to have been // received). // if (ServerInternal.CheckForNATDuplicate(Server)) { Logger.Log(LogLevel.Normal, "NWMasterServer.OnRecvServerInfoResponse(): Removing NAT duplicate server {0} in preference of server {1}.", Sender, InternalAddress); return; } } } Logger.Log(LogLevel.Verbose, "NWMasterServer.OnRecvServerInfoResponse(): Server {0} has {1}/{2} players ({3}).", Sender, Info.ActivePlayers, Info.MaximumPlayers, Info.ModuleName); }
/// <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(); } }
/// <summary> /// Perform a blacklist query. /// </summary> /// <param name="Lookup">Supplies the query input parameters.</param> /// <returns>True if the query matches a blacklist entry.</returns> public bool IsBlacklisted(BlacklistLookup Lookup) { // // Locally capture the current blacklist and sweep through the list // searching for a match. // var LocalBlacklist = Blacklist; foreach (BlacklistEntry Entry in LocalBlacklist) { if (Lookup.ServerAddress != null && Entry.IsBlacklistMatched(Lookup.ServerAddress.ToString(), BlacklistEntry.BlacklistType.BlacklistTypeServerAddress)) return true; if (Lookup.ModuleName != null && Entry.IsBlacklistMatched(Lookup.ModuleName, BlacklistEntry.BlacklistType.BlacklistTypeModuleName)) return true; } return false; }