Introduces IConnectionFilter and the ConnectionFilters registry: one seam the
accept path consults per inbound socket, so core no longer has to know where a
gate's data comes from. The registry is a plain array walked by an indexed loop,
so the hot path has no enumerator, no closure and no allocation; an interface
dispatch is noise next to the accept syscall. Filters register during the
Configure sweep, cheapest first, and the first denial short-circuits.
A filter that throws on the accept path is unregistered and the connection
fails open. A filter that faults once faults for every subsequent connection,
so leaving it registered would mean an exception and a log line per accept --
exactly the amplification an attacker wants -- and a broken filter must not be
able to deny every connection either.
With that seam in place the whole file blocklist moves to UOContent: it is
policy (which feeds, when to promote, what to report) built on an external file
format with an external producer, and core does not need any of it. Firewall
stays in core -- it is long-standing public API, it is what an admin reaches for
manually, and a shard running without UOContent still has to be able to block an
address -- but it now reaches the accept path through the same registry via a
small adapter, so the two remain separate implementations rather than one
conflated store.
Blocklist policy moves out of bans.json into a UOContent Configuration/
blocklist.json, next to crowdsec.json. bans.json keeps only what core decides:
reportRateLimitTrips and autoBanDuration.
Cleanups found while moving the code:
- BanChannel.Stop() persisted the Firewall. A contribution coordinator has no
business saving an enforcement store; that is now the firewall filter's Stop.
- BlocklistGate.Evaluate took a `whitelisted` flag that was hardcoded false at
its only call site, and no whitelist concept exists anywhere in core. Dropped.
- FileBlocklist was a static holding a snapshot and a promote-guard, which
forced its tests onto the sequential collection and a LoadForTesting reset
hook. It is now an instance, so each test owns its own state and they run in
parallel. BlocklistGate folds into it: the pure decision survives as an
internal Evaluate, which is all the separate type ever provided.
- The promote-guard sweep timer was registered from NetState.Configure, two
files from the guard it swept, and ran even with the blocklist disabled. It
now starts with the filter that owns it.
Core sheds ~570 source and ~340 test lines for ~90 of seam. 1344 tests pass.
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>
### Summary
- Puts back `EventSink.SocketConnect`.
- Reverts networking change to push the networking to a separate thread.
- Reverts changes to the firewall by removing the firewall queue.
- Fixes listeners not shutting down with the server.
- Fixes race condition causing connections to get stuck even after they are disposed.
> [!NOTE]
> **Developer Note**
> Networking has been reverted back to using the main thread instead of a background thread. This alleviated complexity and the requirement for concurrent queues all over the place.
## Breaking Changes
* The Firewall and IP Limiter have been rewritten. Please read the notes carefully!
* `TcpServer.Instances` moved back to `NetState.Instances` - sorry - it was stupid to move it to begin with.
> [!Note]
> Sockets that fail the IP Limiter or Firewall will be immediately and forcibly disconnected.
> This means they will be stuck at "Verifying account..." if it was a real client.
### Summary
- Removes firewall wildcard support.
- Removes `AccessRestrictions`.
- Moves Firewall/IPLimiter to the core.
- Moves `TcpServer` to its own thread.
- Removes the `SocketConnect` and `SocketDisconnect` event sinks.
- Moves `Instances` back to `NetState.Instances`.
- Fixes a long standing bug with bad handling of duplicate listener addresses.
#### Firewall
The firewall has been completely rewritten. There is now an "Admin Firewall" which saves to the config file. Secondarily, there is an internal firewall used exclusively by the TcpServer while processing sockets. The Admin firewall mirrors it's additions/deletions to the internal firewall by adding requests to a queue.
> [!IMPORTANT]
> **Wildcard firewall entries, such as `X`, `*`, `?` are not allowed.**
> **Ranges in between IP classes or sextets are not allowed.**
> **Please make sure to use one of the following:**
> * IP Address - `192.168.1.1`
> * CIDR - `192.168.1.0/24`
> * Range - `192.168.1.1-192.168.1.100`
#### IP Limiter
The IP Limiter has been completely rewritten. The available configurations are:
```json
"ipLimiter.enable": "True",
"ipLimiter.maxConnectionsPerIP": 10,
"ipLimiter.clearConnectionAttemptsDuration": "00:00:00:10",
"ipLimiter.clearThrottledDuration": "00:00:02:00",
```
The IP Limiter is set up to prevent spamming connections from the same IP. Every time an IP connects, it is added to a connection list. After 10 attempts, the IP is added to the throttle list. To keep the system fast, the connection list is entirely wiped every 10 seconds, and the throttle list is entirely wiped every 2 minutes.