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.
128 lines
7.2 KiB
Markdown
128 lines
7.2 KiB
Markdown
# LAN discovery broadcasts
|
|
|
|
Broadcast participation is a **local setting of each network**, enabled by
|
|
default. Different networks on the same agent can have different settings;
|
|
each member advertises whether it accepts broadcasts over its authenticated
|
|
control session. It is not a network-wide vote or signed shared configuration.
|
|
|
|
```sh
|
|
tsunagi join -n games --no-broadcast
|
|
tsunagi join -n games --broadcast
|
|
tsunagi network broadcast games off
|
|
tsunagi network broadcast games on
|
|
tsunagi network broadcast games
|
|
```
|
|
|
|
Joining without either flag preserves the saved choice. The `network broadcast`
|
|
command can change a running network immediately or configure it while the
|
|
agent is stopped. It accepts the exact network name, full id or a unique id
|
|
prefix; ambiguous selectors list candidates without changing anything.
|
|
`status` reports the local setting. There is no global `up`
|
|
override: starting the agent preserves every network's independent policy.
|
|
|
|
## Delivery
|
|
|
|
At local TUN ingress, the IP router recognizes IPv4 UDP addressed to the limited
|
|
broadcast `255.255.255.255` or the configured overlay subnet's directed broadcast
|
|
(for example `10.13.37.255` for `/24`). These address forms follow
|
|
[RFC 1122 section 3.3.6](https://www.rfc-editor.org/rfc/rfc1122#section-3.3.6).
|
|
There is no port allowlist: discovery requests and server announcements on
|
|
game-specific UDP ports use the same mechanism. IPv4/UDP lengths are checked
|
|
before fanout, including handling IP fragments. IPv6 multicast, mDNS, DHCP
|
|
bridging, Ethernet/IPX and physical LAN capture are outside this feature.
|
|
|
|
The local source address selects exactly one broadcast domain. A limited
|
|
broadcast is never copied into all networks merely because they share a TUN.
|
|
A directed broadcast must belong to that source's network. Unspecified sources
|
|
and physical LAN sources have no authorized domain yet. Recipients are live,
|
|
authenticated, opted-in members with signed overlay address ownership. The
|
|
immutable recipient list is rebuilt with address/policy changes, not per packet.
|
|
|
|
The origin creates **one encrypted unicast copy per eligible peer** and hands it
|
|
to the existing shortest-path router. Each can travel through several transport
|
|
links. Transit handles opaque ciphertext with the existing hop limit and does
|
|
not create another fanout. The original IP source, destination, TTL, UDP ports,
|
|
checksum and payload remain unchanged. Reply packets use ordinary unicast.
|
|
There is no broadcast retransmission timer or application-payload deduplication:
|
|
games may legitimately repeat the same discovery query. WireGuard replay checks
|
|
reject duplicated encrypted packets delivered by the transport.
|
|
|
|
At the destination, the usual authenticated-source ownership check still runs.
|
|
Broadcast admission also checks the receiving network's setting, the sender's
|
|
announced participation, the destination domain and UDP framing. Disabling it
|
|
takes effect locally even while a remote sender still has an older announcement.
|
|
Received broadcasts terminate in the local TUN and never enter the fanout path.
|
|
An ordinary decrypted packet must target this host's address in that network;
|
|
the TUN is not a transit gateway to another overlay or physical subnet.
|
|
|
|
Opt-out disables originating and accepting broadcasts on that host/network.
|
|
It does not disable opaque unicast transit for other members: intermediate
|
|
routers cannot inspect another pair's encrypted IP payload.
|
|
|
|
## Host behavior and verification
|
|
|
|
A game's discovery packet only reaches Tsunagi if the operating system sends it
|
|
through the Tsunagi interface, and its reply only reaches the game if the host
|
|
firewall admits it. While a network has broadcast on, the agent installs
|
|
exactly 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:
|
|
|
|
- **Route** `255.255.255.255/32` through the overlay interface, with the
|
|
overlay address as its source (Linux: netlink, `ip route add 255.255.255.255/32
|
|
dev tsun0 src <overlay-ip>`; Windows: `New-NetRoute`, metric 1, active store).
|
|
Limited broadcasts from every program then go to the overlay and not to the
|
|
physical LAN while this is installed; this is the price of the feature.
|
|
- **Firewall allowance** for inbound **UDP only**, from the overlay range only,
|
|
on that interface only (Linux: an `iptables -I INPUT` rule tagged
|
|
`tsunagi:<interface>`; Windows: a Defender rule of that name). Replies to a
|
|
broadcast come from another address than the request went to, so a stateful
|
|
firewall never matches them to it. TCP services such as SSH stay unexposed.
|
|
|
|
Both are tagged, idempotent and reversible, and are never derived from a remote
|
|
announcement. A rule left by a crashed run is replaced on the next apply. If
|
|
`iptables` is missing (for example a host with only `nft`) or the process is not
|
|
privileged, the route is still installed and `status` prints a
|
|
`broadcast rules` line saying which half is missing and why. To undo it all,
|
|
turn broadcast off for the network: `tsunagi network broadcast <network> off`.
|
|
|
|
**One interface, several networks.** The host has one `255.255.255.255` route
|
|
per interface, so only one overlay address can be its source. The lowest network
|
|
id among broadcast-enabled networks with an address is used; `status` says when
|
|
that is not the network being shown. Directed broadcasts (`10.13.37.255`) follow
|
|
the ordinary connected route and are not affected.
|
|
|
|
**What it cannot fix.** A game that binds its socket to a physical address or
|
|
device, or sends to the physical subnet's broadcast address, still bypasses the
|
|
TUN. The Windows implementation could not be run while it was written; its
|
|
scripts are unit-tested only.
|
|
|
|
The suite tests real agents, authenticated iroh links and WireGuard encryption
|
|
with memory TUNs: limited/directed UDP discovery, multihop fanout, single-copy
|
|
delivery, opt-out, unicast replies, malformed input, source validation and
|
|
network isolation. Host rules are tested through a mock host and a scripted
|
|
`iptables`; the default suite changes no OS setting. It does not launch CS 1.6 or
|
|
Warcraft III.
|
|
|
|
## Future exported LANs
|
|
|
|
`overlay::broadcast` owns domain selection and recipient policy separately from
|
|
IP parsing, encryption and encrypted transit. A future LAN adapter should feed
|
|
an explicitly authorized ingress domain into this layer, and export routes
|
|
through the corresponding source/destination admission policy. It must not
|
|
relax address checks globally or reinterpret all `.255` addresses as broadcasts.
|
|
Multiple gateways to the same LAN will need origin identifiers and bounded
|
|
duplicate suppression at LAN ingress/egress before physical rebroadcast is
|
|
enabled. No subnet export or host forwarding configuration is added here.
|
|
|
|
## Compatibility
|
|
|
|
Control ALPN is `tsunagi/ctrl/3` because `Announce` now carries participation.
|
|
The encrypted transit envelope and data ALPN `tsunagi/data/4` stay unchanged.
|
|
Local control protocol 14 rejects commands from mismatched running binaries
|
|
with a restart message instead of decoding a different request shape.
|
|
Upgrade all members and restart the agent before using the new CLI.
|
|
|
|
SQLite schema 4 adds `networks.broadcast` with default `1`. Existing network
|
|
names, secrets, identity keys and signed records are retained. The old binary
|
|
does not understand schema 4; the migration is not a downgrade mechanism.
|