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>
Writes up IConnectionFilter for content authors: the accept-path contract
(allocation-free, non-blocking, side effects owned by the filter), registration
order and short-circuiting, the unregister-on-throw policy, and why this must
not be routed through EventSink.InvokeSocketConnect.
Also records the IPAddress <-> UInt128 normalization quirk. Addresses are
normalized to IPv6 form, so a v4 address round-tripped through UInt128 can come
back as InterNetworkV6 with IsIPv4MappedToIPv6 set. That is what the seemingly
redundant clause in ToUInt128 is defending, not a stray condition. Noted as a
follow-up rather than churned mid-feature: the normalization would read better
as an explicit "to canonical v6 bits" step that never needs the family check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Merge the old Firewall engine, AdminFirewall (parsing/persistence), and the
runtime TTL sweep into one single-threaded store: no locks/version-counter,
main-thread TTL expiry, and persistence to Configuration/firewall.json (with a
one-time firewall.cfg migration). Lookups go through a shared, coalesced
SortedRangeIndex<T> (binary search); IP conversion/parse helpers are collected
in IPAddressUtility. Firewall keeps IFirewallEntry for admin/gump/persistence.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>