Core now owns the question -- "should this socket be denied?" -- and none of the answers. The firewall was the last implementation left in core, and the reasons to keep it did not survive scrutiny: it is not extended downstream, and a shard running bare core has no way to populate it anyway, since the admin gump and the commands that mutate it are both content. Larger shards front the server with an upstream proxy or edge scrubbing and never use it; it survives as the fallback an admin reaches for over a single player, which is squarely content's concern. Nothing about the firewall changes for operators: same Server.Network namespace, same Configuration/firewall.json, same gump and commands, same legacy .cfg migration. It reaches the accept path through ConnectionFilters like any other filter, and registers itself first because an empty set is the cheapest gate. Untangling core from the firewall entry types first: - NetworkUtilities built its reserved-network tables out of CidrFirewallEntry, which made core depend on the firewall for something with nothing to do with banning. Those are constant CIDR blocks answering "is this address in one of these ranges?", so they are now a SortedRangeIndex<UInt128> -- the same primitive the firewall and blocklist already share. Same semantics, same public API, one linear scan replaced by a binary search. - The CIDR -> normalized range parse those tables needed is now IPAddressUtility.TryParseCidrRange, and CidrFirewallEntry drops its private copy of that logic in favor of it. Core no longer references IFirewallEntry or Firewall anywhere. 1344 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
56 lines
2 KiB
C#
56 lines
2 KiB
C#
using System;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using Server.Collections;
|
|
|
|
namespace Server;
|
|
|
|
public static class NetworkUtilities
|
|
{
|
|
public static bool IsPrivateNetwork(this IPAddress ip) =>
|
|
ip.AddressFamily switch
|
|
{
|
|
AddressFamily.InterNetwork => ip.IsPrivateNetworkV4(),
|
|
AddressFamily.InterNetworkV6 => ip.IsPrivateNetworkV6(),
|
|
_ => false
|
|
};
|
|
|
|
// These are constant reserved ranges, not firewall entries -- they only ever answer "is this address
|
|
// in one of these blocks?", which is exactly what SortedRangeIndex is for. Building them through the
|
|
// firewall entry types was a convenience that made core depend on the firewall for something that has
|
|
// nothing to do with banning.
|
|
private static readonly SortedRangeIndex<UInt128> _privateNetworkV4 = BuildIndex(
|
|
"127.0.0.1/8",
|
|
"192.168.0.0/16",
|
|
"10.0.0.0/8",
|
|
"172.16.0.0/12",
|
|
"169.254.0.0/16",
|
|
"100.64.0.0/10"
|
|
);
|
|
|
|
private static readonly SortedRangeIndex<UInt128> _privateNetworkV6 = BuildIndex(
|
|
"fc00::/7",
|
|
"fe80::/10"
|
|
);
|
|
|
|
private static SortedRangeIndex<UInt128> BuildIndex(params ReadOnlySpan<string> cidrs)
|
|
{
|
|
var ranges = new SortedRangeIndex<UInt128>.Range[cidrs.Length];
|
|
for (var i = 0; i < cidrs.Length; i++)
|
|
{
|
|
if (!IPAddressUtility.TryParseCidrRange(cidrs[i], out var min, out var max))
|
|
{
|
|
throw new ArgumentException($"Invalid reserved-network CIDR \"{cidrs[i]}\"");
|
|
}
|
|
|
|
ranges[i] = new SortedRangeIndex<UInt128>.Range(min, max);
|
|
}
|
|
|
|
Array.Sort(ranges, SortedRangeIndex<UInt128>.ByMin);
|
|
return SortedRangeIndex<UInt128>.Build(ranges);
|
|
}
|
|
|
|
public static bool IsPrivateNetworkV4(this IPAddress ip) => _privateNetworkV4.Contains(ip.ToUInt128());
|
|
|
|
public static bool IsPrivateNetworkV6(this IPAddress ip) => _privateNetworkV6.Contains(ip.ToUInt128());
|
|
}
|