diff --git a/Projects/UOContent.Tests/Tests/Network/BanExemptionsTests.cs b/Projects/UOContent.Tests/Tests/Network/BanExemptionsTests.cs index 7e930fbdf..b38364373 100644 --- a/Projects/UOContent.Tests/Tests/Network/BanExemptionsTests.cs +++ b/Projects/UOContent.Tests/Tests/Network/BanExemptionsTests.cs @@ -28,15 +28,15 @@ public class BanExemptionsTests private static readonly IPAddress _listed = IPAddress.Parse("192.0.2.10"); private static readonly IPAddress _unlisted = IPAddress.Parse("192.0.2.11"); - private static void WithFileAllowlist(string contents) => - FileAllowlist.LoadForTesting(BlocklistSnapshot.Build(Encoding.ASCII.GetBytes(contents), out _, out _)); + private static void WithManualAllowlist(string contents) => + ManualAllowlist.LoadForTesting(BlocklistSnapshot.Build(Encoding.ASCII.GetBytes(contents), out _, out _)); - private static void WithEmptyFileAllowlist() => FileAllowlist.LoadForTesting(BlocklistSnapshot.Empty); + private static void WithEmptyManualAllowlist() => ManualAllowlist.LoadForTesting(BlocklistSnapshot.Empty); [Fact] - public void File_allowlist_exempts_behavioral_contributions() + public void Manual_allowlist_exempts_behavioral_contributions() { - WithFileAllowlist("192.0.2.10"); + WithManualAllowlist("192.0.2.10"); // Subtracting from the blocklist does nothing for behavioural detections, which never consult it. Assert.True(BanExemptions.IsExempt(_listed, BanReasons.ForeignProtocol, NeverCalled)); @@ -46,10 +46,10 @@ public class BanExemptionsTests } [Fact] - public void File_allowlist_covers_cidr_entries() + public void Manual_allowlist_covers_cidr_entries() { // Carve-outs are CIDRs, so a shared-CGNAT player is only covered if ranges work here. - WithFileAllowlist("192.0.2.0/24"); + WithManualAllowlist("192.0.2.0/24"); Assert.True(BanExemptions.IsExempt(_listed, BanReasons.RateLimit, NeverCalled)); Assert.True(BanExemptions.IsExempt(IPAddress.Parse("192.0.2.254"), BanReasons.RateLimit, NeverCalled)); @@ -57,9 +57,9 @@ public class BanExemptionsTests } [Fact] - public void Manual_bans_are_never_exempt_even_when_file_allowlisted() + public void Manual_bans_are_never_exempt_even_when_allowlisted() { - WithFileAllowlist("192.0.2.10"); + WithManualAllowlist("192.0.2.10"); // An explicit decision outranks the operator's own carve-out, and must not cost a strike. Assert.False(BanExemptions.IsExempt(_listed, BanReasons.Manual, NeverCalled)); @@ -68,16 +68,16 @@ public class BanExemptionsTests [Fact] public void Unopted_reasons_are_never_exempt() { - WithFileAllowlist("192.0.2.10"); + WithManualAllowlist("192.0.2.10"); Assert.False(BanExemptions.IsExempt(_listed, BanReasons.Blocklist, NeverCalled)); Assert.False(BanExemptions.IsExempt(_listed, "some-future-reason", NeverCalled)); } [Fact] - public void File_allowlist_does_not_spend_the_earned_lists_strikes() + public void Manual_allowlist_does_not_spend_the_earned_lists_strikes() { - WithFileAllowlist("192.0.2.10"); + WithManualAllowlist("192.0.2.10"); // Unconditional, so the revocable list must not be consulted -- that would burn a strike. Assert.True(BanExemptions.IsExempt(_listed, BanReasons.RateLimit, NeverCalled)); @@ -86,7 +86,7 @@ public class BanExemptionsTests [Fact] public void Falls_through_to_the_login_allowlist_when_not_file_listed() { - WithEmptyFileAllowlist(); + WithEmptyManualAllowlist(); var consulted = 0; @@ -107,7 +107,7 @@ public class BanExemptionsTests [Fact] public void Null_address_is_never_exempt() { - WithEmptyFileAllowlist(); + WithEmptyManualAllowlist(); Assert.False(BanExemptions.IsExempt(null, BanReasons.RateLimit, NeverCalled)); } diff --git a/Projects/UOContent.Tests/Tests/Network/Bans/Blocklist/BlocklistConfigurationTests.cs b/Projects/UOContent.Tests/Tests/Network/Bans/Blocklist/BlocklistConfigurationTests.cs index f1ec00686..0b89c2657 100644 --- a/Projects/UOContent.Tests/Tests/Network/Bans/Blocklist/BlocklistConfigurationTests.cs +++ b/Projects/UOContent.Tests/Tests/Network/Bans/Blocklist/BlocklistConfigurationTests.cs @@ -65,7 +65,7 @@ public class BlocklistConfigurationTests Assert.False(new BlocklistSettings().Enabled); } - // Null, not the old default array. FileAllowlist warns on a non-empty value, so a default would + // Null, not the old default array. ManualAllowlist warns on a non-empty value, so a default would // fire that warning on every shard that never set it. [Fact] public void Deprecated_allowlist_files_defaults_to_null() diff --git a/Projects/UOContent.Tests/Tests/Network/Bans/Blocklist/FileAllowlistConfigurationTests.cs b/Projects/UOContent.Tests/Tests/Network/ManualAllowlist/ManualAllowlistConfigurationTests.cs similarity index 78% rename from Projects/UOContent.Tests/Tests/Network/Bans/Blocklist/FileAllowlistConfigurationTests.cs rename to Projects/UOContent.Tests/Tests/Network/ManualAllowlist/ManualAllowlistConfigurationTests.cs index b99c0922d..1146414b5 100644 --- a/Projects/UOContent.Tests/Tests/Network/Bans/Blocklist/FileAllowlistConfigurationTests.cs +++ b/Projects/UOContent.Tests/Tests/Network/ManualAllowlist/ManualAllowlistConfigurationTests.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright 2019-2026 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: FileAllowlistConfigurationTests.cs * + * File: ManualAllowlistConfigurationTests.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -19,16 +19,16 @@ using Server.Json; using Server.Network.Bans; using Xunit; -namespace Server.Tests.Network.Bans.Blocklist; +namespace Server.Tests.Network.ManualAllowlists; -public class FileAllowlistConfigurationTests +public class ManualAllowlistConfigurationTests { // Locks the JsonConfig casing contract: JsonConfig's options are case-SENSITIVE, so every settings // member must carry an explicit [JsonPropertyName("camelCase")] or it silently binds nothing. [Fact] - public void FileAllowlistSettings_RoundTripsThroughJsonConfig() + public void ManualAllowlistSettings_RoundTripsThroughJsonConfig() { - var original = new FileAllowlistSettings + var original = new ManualAllowlistSettings { Enabled = true, Files = ["D:/shared/ip-allowlist*.txt"], @@ -41,7 +41,7 @@ public class FileAllowlistConfigurationTests Assert.Contains("\"files\"", json); Assert.Contains("\"reloadInterval\"", json); - var restored = JsonSerializer.Deserialize(json, JsonConfig.DefaultOptions); + var restored = JsonSerializer.Deserialize(json, JsonConfig.DefaultOptions); Assert.NotNull(restored); Assert.Equal(original.Enabled, restored.Enabled); @@ -51,9 +51,9 @@ public class FileAllowlistConfigurationTests // The point of the flag: a shard that never opts in must not start the reload poll. [Fact] - public void File_allowlist_is_off_by_default() + public void Manual_allowlist_is_off_by_default() { - Assert.False(new FileAllowlistSettings().Enabled); + Assert.False(new ManualAllowlistSettings().Enabled); } // The generator creates ip-allowlist.txt beside the blocklist; the wildcard is what picks up a @@ -61,6 +61,6 @@ public class FileAllowlistConfigurationTests [Fact] public void Default_pattern_matches_the_generator_output_path() { - Assert.Equal(["Configuration/ip-allowlist*.txt"], new FileAllowlistSettings().Files); + Assert.Equal(["Configuration/ip-allowlist*.txt"], new ManualAllowlistSettings().Files); } } diff --git a/Projects/UOContent/Network/BanExemptions.cs b/Projects/UOContent/Network/BanExemptions.cs index 5566bd5e7..c5f6b726a 100644 --- a/Projects/UOContent/Network/BanExemptions.cs +++ b/Projects/UOContent/Network/BanExemptions.cs @@ -20,7 +20,7 @@ using Server.Network.Bans; namespace Server.Network; /// -/// Combines and into the one answer +/// Combines and into the one answer /// asks for, so neither source has to know about the other. /// public static class BanExemptions @@ -51,7 +51,7 @@ public static class BanExemptions } // Deliberate and unconditional, so it wins and must not spend the earned list's strikes. - if (FileAllowlist.Contains(address)) + if (ManualAllowlist.Contains(address)) { return true; } diff --git a/Projects/UOContent/Network/Blocklist/BlocklistConfiguration.cs b/Projects/UOContent/Network/Blocklist/BlocklistConfiguration.cs index 5461502d0..8033d1e67 100644 --- a/Projects/UOContent/Network/Blocklist/BlocklistConfiguration.cs +++ b/Projects/UOContent/Network/Blocklist/BlocklistConfiguration.cs @@ -75,7 +75,7 @@ public record BlocklistSettings /// /// Deprecated: moved to files in ip-allowlist.json, because the blocklist is only one of - /// two consumers. Still bound so can warn an operator who set it here + /// two consumers. Still bound so can warn an operator who set it here /// instead of dropping the carve-out silently. Null when absent, which is the normal case. /// [JsonPropertyName("allowlistFiles")] diff --git a/Projects/UOContent/Network/Blocklist/BlocklistFilter.cs b/Projects/UOContent/Network/Blocklist/BlocklistFilter.cs index 421809d01..030997803 100644 --- a/Projects/UOContent/Network/Blocklist/BlocklistFilter.cs +++ b/Projects/UOContent/Network/Blocklist/BlocklistFilter.cs @@ -102,10 +102,10 @@ public sealed class BlocklistFilter : IConnectionFilter // The operator's override on this gate, opted into separately. Without it only the generator's // subtraction covers carve-outs, and that does not cover ban contributions. - if (!FileAllowlist.Enabled) + if (!ManualAllowlist.Enabled) { logger.Warning( - "Blocklist is on but the file allowlist is not; set \"enabled\" in ip-allowlist.json so a " + + "Blocklist is on but the manual allowlist is not; set \"enabled\" in ip-allowlist.json so a " + "carve-out also suppresses ban contributions" ); } @@ -198,7 +198,7 @@ public sealed class BlocklistFilter : IConnectionFilter // Both are asked only once the list has matched, so they cost the common accept nothing. The file // list is usually redundant because the generator subtracts it — except right after an operator // adds an entry without regenerating, which is when someone is waiting to get back in. - if (FileAllowlist.Contains(address)) + if (ManualAllowlist.Contains(address)) { return false; } diff --git a/Projects/UOContent/Network/Blocklist/BlocklistSnapshot.cs b/Projects/UOContent/Network/Blocklist/BlocklistSnapshot.cs index 711b23ac1..d62df0752 100644 --- a/Projects/UOContent/Network/Blocklist/BlocklistSnapshot.cs +++ b/Projects/UOContent/Network/Blocklist/BlocklistSnapshot.cs @@ -182,7 +182,7 @@ public sealed class BlocklistSnapshot } /// - /// Plain set membership, for callers whose set is an ALLOWlist (see ) and for + /// Plain set membership, for callers whose set is an ALLOWlist (see ) and for /// whom would read backwards. The interval machinery is direction-agnostic. /// public bool Contains(IPAddress ip) => IsBanned(ip); diff --git a/Projects/UOContent/Network/LoginAllowlist/LoginAllowlist.cs b/Projects/UOContent/Network/LoginAllowlist/LoginAllowlist.cs index df28e7fcc..096a03a48 100644 --- a/Projects/UOContent/Network/LoginAllowlist/LoginAllowlist.cs +++ b/Projects/UOContent/Network/LoginAllowlist/LoginAllowlist.cs @@ -32,7 +32,7 @@ namespace Server.Network; /// /// Consulted only after the blocklist has already matched, so a normal accept pays nothing for it. An entry /// is evidence rather than a licence: enough strikes inside the window revokes it. It cannot bootstrap, so -/// it does not replace . Both dictionaries are game-loop state; only the file +/// it does not replace . Both dictionaries are game-loop state; only the file /// write runs off-loop, over a snapshot taken on the loop. /// See dev-docs/ip-bans-and-allowlists.md. /// diff --git a/Projects/UOContent/Network/Blocklist/FileAllowlist.cs b/Projects/UOContent/Network/ManualAllowlist/ManualAllowlist.cs similarity index 93% rename from Projects/UOContent/Network/Blocklist/FileAllowlist.cs rename to Projects/UOContent/Network/ManualAllowlist/ManualAllowlist.cs index f4ce4ea0d..d02d4ac45 100644 --- a/Projects/UOContent/Network/Blocklist/FileAllowlist.cs +++ b/Projects/UOContent/Network/ManualAllowlist/ManualAllowlist.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright 2019-2026 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: FileAllowlist.cs * + * File: ManualAllowlist.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -35,9 +35,9 @@ namespace Server.Network.Bans; /// next regeneration. Opt-in via ip-allowlist.json's enabled, since the poll runs for the /// whole uptime; no shield against a manual ban either — see . /// -public static class FileAllowlist +public static class ManualAllowlist { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(FileAllowlist)); + private static readonly ILogger logger = LogFactory.GetLogger(typeof(ManualAllowlist)); // Written by the reload poll (off-loop), read by the accept path (game loop). One volatile reference // swap is the whole synchronization story: readers see the old or the new snapshot, whole. @@ -58,8 +58,8 @@ public static class FileAllowlist public static void Initialize() { - FileAllowlistConfiguration.Load(); - var settings = FileAllowlistConfiguration.Settings; + ManualAllowlistConfiguration.Load(); + var settings = ManualAllowlistConfiguration.Settings; if (settings == null) { return; @@ -81,14 +81,14 @@ public static class FileAllowlist if (present > 0) { logger.Warning( - "File allowlist is off (\"enabled\" false in ip-allowlist.json) but {Count} allowlist file(s) " + + "Manual allowlist is off (\"enabled\" false in ip-allowlist.json) but {Count} allowlist file(s) " + "are present; those carve-outs will not suppress ban contributions", present ); } else { - logger.Information("File allowlist disabled (\"enabled\" false in ip-allowlist.json)"); + logger.Information("Manual allowlist disabled (\"enabled\" false in ip-allowlist.json)"); } return; @@ -96,7 +96,7 @@ public static class FileAllowlist if (_patterns.Length == 0) { - logger.Information("File allowlist disabled (\"files\" empty in ip-allowlist.json)"); + logger.Information("Manual allowlist disabled (\"files\" empty in ip-allowlist.json)"); return; } @@ -242,7 +242,7 @@ public static class FileAllowlist } catch (Exception e) { - logger.Warning(e, "File allowlist reload check failed; keeping last snapshot ({Count})", Count); + logger.Warning(e, "Manual allowlist reload check failed; keeping last snapshot ({Count})", Count); } } } @@ -291,7 +291,7 @@ public static class FileAllowlist _lastStamp = stamp; logger.Information( - "File allowlist loaded {Count} range(s) from {Files} file(s)", + "Manual allowlist loaded {Count} range(s) from {Files} file(s)", next.Count, files ); diff --git a/Projects/UOContent/Network/Blocklist/FileAllowlistConfiguration.cs b/Projects/UOContent/Network/ManualAllowlist/ManualAllowlistConfiguration.cs similarity index 83% rename from Projects/UOContent/Network/Blocklist/FileAllowlistConfiguration.cs rename to Projects/UOContent/Network/ManualAllowlist/ManualAllowlistConfiguration.cs index f60d6627f..dd21fe97c 100644 --- a/Projects/UOContent/Network/Blocklist/FileAllowlistConfiguration.cs +++ b/Projects/UOContent/Network/ManualAllowlist/ManualAllowlistConfiguration.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright 2019-2026 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: FileAllowlistConfiguration.cs * + * File: ManualAllowlistConfiguration.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -21,14 +21,14 @@ using Server.Json; namespace Server.Network.Bans; /// -/// Loads the from Configuration/ip-allowlist.json. Loaded once; +/// Loads the from Configuration/ip-allowlist.json. Loaded once; /// a missing file writes a template so operators have something to edit. /// -public static class FileAllowlistConfiguration +public static class ManualAllowlistConfiguration { private const string _path = "Configuration/ip-allowlist.json"; - public static FileAllowlistSettings Settings { get; private set; } + public static ManualAllowlistSettings Settings { get; private set; } public static void Load() { @@ -36,11 +36,11 @@ public static class FileAllowlistConfiguration if (File.Exists(path)) { - Settings = JsonConfig.Deserialize(path); + Settings = JsonConfig.Deserialize(path); } else { - Settings = new FileAllowlistSettings(); + Settings = new ManualAllowlistSettings(); Save(); } } @@ -52,11 +52,11 @@ public static class FileAllowlistConfiguration } /// -/// Bound configuration for . Its own file rather than a corner of +/// Bound configuration for . Its own file rather than a corner of /// blocklist.json: the blocklist is only one of two consumers, and the other /// () works on a shard that runs no blocklist at all. /// -public record FileAllowlistSettings +public record ManualAllowlistSettings { /// /// Whether the shard reads at all. Off by default: reading them costs a poll for diff --git a/dev-docs/ip-bans-and-allowlists.md b/dev-docs/ip-bans-and-allowlists.md index fa6b58b0d..a57447cb0 100644 --- a/dev-docs/ip-bans-and-allowlists.md +++ b/dev-docs/ip-bans-and-allowlists.md @@ -38,7 +38,7 @@ Two, with different authority: | List | Source | Revocable? | Covers | |---|---|---|---| -| `FileAllowlist` | every `ip-allowlist*.txt` (opt-in) | No — an operator said so | Blocking **and** escalation | +| `ManualAllowlist` | every `ip-allowlist*.txt` (opt-in) | No — an operator said so | Blocking **and** escalation | | `LoginAllowlist` | Earned by authenticating, 90-day TTL | Yes — 10 strikes/hour | Blocking **and** escalation | Both are consulted **only after the blocklist has already matched**, so a normal accept — the one an @@ -185,7 +185,7 @@ firewalled off. Shortening the 5s handshake window has been tried and broke real - **An allowlist cannot bootstrap.** A `LoginAllowlist` entry is only earned by getting in, so it can never repair an existing false positive, and it is weakest on rotating CGNAT — a player whose lease moved is a - stranger again. `FileAllowlist` is the fix for that, which is why it is manual — and opt-in, via + stranger again. `ManualAllowlist` is the fix for that, which is why it is manual — and opt-in, via `ip-allowlist.json`. - **A never-logged-in player on a shared address can still be caught**, for up to `badConnectDuration`, if a co-tenant misbehaves. Accepted: it is 4h and self-healing. The cheapest lever is `badConnectDuration`. @@ -218,7 +218,7 @@ A shard fronted by an upstream proxy can disable all of it and register nothing. | `Projects/Server/Network/Bans/BanReasons.cs` | Reason slugs + the behavioural opt-in set | | `Projects/UOContent/Network/BanExemptions.cs` | Combines both allowlists into one answer | | `Projects/UOContent/Network/Blocklist/BlocklistFilter.cs` | File-sourced blocklist filter | -| `Projects/UOContent/Network/Blocklist/FileAllowlist.cs` | Operator carve-outs, read from the allowlist files | +| `Projects/UOContent/Network/Blocklist/ManualAllowlist.cs` | Operator carve-outs, read from the allowlist files | | `Projects/UOContent/Network/LoginAllowlist/LoginAllowlist.cs` | Allowlist earned by authenticating | | `Projects/UOContent/Network/AutoDenylist/AutoDenylist.cs` | Short-lived local hold | | `Projects/UOContent/Network/CrowdSec/CrowdSecReporter.cs` | LAPI contribution sink |