LAN discovery (CS 1.6 and similar) only worked after two manual steps on every
host: route 255.255.255.255 through the TUN, and allow inbound traffic on it.
Without the route the game's limited broadcast leaves via the physical adapter
and never reaches the overlay; without the firewall allowance the server's
unicast reply (from a different address than the one the request went to, so
never matched by conntrack) is dropped before the game's socket. Verified by
hand on Linux: with both in place the server answered A2S_INFO over the overlay.
The system level (not a plugin) now installs exactly those two things for the
interface it owns, and removes them when broadcast is turned off, the network
is left or stopped, or the agent exits.
Design:
- New boundary overlay::hostrules (BroadcastHostRules: idempotent apply/clear,
with Linux, Windows, Mock and Unsupported implementations). TunFactory gains
host_rules(); ManagedTunFactory::with_host_rules wires it in the CLI.
- Interface::sync_broadcast_rules reconciles the host against the routing
table (RoutingTable::broadcast_host_target) and is called after address sync,
on live `network broadcast on/off`, on network deactivation and by remove().
Failure is contained: the outcome is kept and logged, never fatal.
- One interface carries several networks but the host has one 255.255.255.255
route, so the source is the lowest NetworkId among broadcast-enabled networks
with an address (documented in docs/broadcast.md).
- Linux: the route is added on the existing netlink thread (preferred source =
overlay address, replace semantics). The firewall rule is `iptables -I INPUT
-i <if> -p udp -s <overlay range>` tagged with a comment `tsunagi:<if>`;
it is UDP-only and range-limited on purpose so TCP services stay unexposed.
iptables is located in fixed system directories, never via PATH; stale tagged
rules (e.g. from a crash) are replaced. A missing iptables leaves the route in
place and is reported. rp_filter is not touched.
- Windows: PowerShell (absolute path) New-NetRoute metric 1 and a tagged
inbound UDP Defender rule scoped to the interface alias and overlay range.
- status shows a `broadcast rules` row (ok, or which half failed and why).
IPC control protocol bumped 14 -> 15 because OverlayReport gained a field.
- AGENTS.md records the narrow exception to "never touch routing/firewall".
Compatibility: restart the agent after upgrading (control protocol 15). No wire
or schema change.
Checks run (Linux only): cargo fmt --check, cargo clippy --locked --workspace
--all-targets -D warnings, cargo test --locked --workspace --all-targets, all
passing. Tests use a mock host and a scripted iptables; no OS setting is
changed. NOT done: the Windows code was never compiled for or run on Windows
(cross-compiling failed for lack of a C toolchain), only its script building is
unit-tested; the real netlink route and iptables calls were not exercised
against a live kernel in the suite; CS 1.6 was not launched from this change.