Files
tsunagi/docs/broadcast.md
T
Aleksandr Bogomiakov 533921059a Install broadcast route and firewall allowance on the host while broadcast is on
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.
2026-10-01 15:03:43 +01:00

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.