Added per-network LAN broadcast relay
LAN game discovery previously dropped IPv4 broadcasts at TUN ingress. Carry limited and subnet-directed UDP broadcasts to authenticated, opted-in members of the source network, including destinations reached through mesh relays. Preserve the original IP/UDP bytes and deliver received broadcasts only to the local TUN; never reflood them or expose another pair's plaintext at transit. Build immutable recipient snapshots on address and participation changes. The origin sends one ordinary end-to-end encrypted copy per recipient; the existing fast, bounded-hop transport router remains unchanged. Validate UDP framing, source ownership and destination admission without game-specific port rules. Keep network domains isolated and refuse implicit gateways to physical LANs. The separate broadcast policy/domain layer is the extension point for future authorized subnet exports; physical capture, bridging and LAN deduplication are deliberately not implemented yet. Persist default-on participation independently for each local network. Add join --no-broadcast/--broadcast and network broadcast <id> [on|off], including live updates and authenticated announcements. Joining without a flag preserves the saved choice. Opt-out stops local origination and delivery, while opaque unicast transit for other members keeps working. Migrate SQLite schema 3 to 4 without replacing identities or signed state. Use control ALPN 3 and local IPC protocol 14 for the new announcement/request shapes; update peers and restart running agents together. The data ALPN 4 envelope remains unchanged. No release version bump, tag or push is included. Document agent-owned commits in AGENTS.md: short English subjects, explanatory bodies, scoped staging, honest validation, and repository-local fallback author AB <ab@hexor.cy> only when an effective name/email is missing. Release actions remain the user's responsibility. Validation on Windows: cargo fmt --all -- --check; cargo check --locked --workspace --all-targets; cargo clippy --locked --workspace --all-targets -- -D warnings; release workspace/all-target tests: 313 passed. The two existing SQLite wipe failures (a_wipe_removes_everything_and_the_next_start_is_a_stranger and wiping_twice_is_as_ordinary_as_wiping_once) were explicitly skipped; the public-DHT smoke test and forwarding benchmark remain ignored by default. New coverage exercises real iroh/WireGuard multihop fanout, single delivery, runtime opt-out, unicast replies, domain isolation, malformed input and schema migration. TUNs are in-memory; actual games and OS adapter selection were not tested.
This commit is contained in:
@@ -60,6 +60,7 @@ not tunnel control sessions. See [routing.md](routing.md).
|
||||
| `dataplane::routing` | transport-independent graph, shortest paths and opaque flow identifiers |
|
||||
| `dataplane::relay` | immutable forwarding snapshots and transport-to-transport transit |
|
||||
| `dataplane` | the contract an IP protocol implements |
|
||||
| `overlay::broadcast` | domain-scoped IPv4 UDP discovery fanout and receive admission |
|
||||
| `overlay` | the one interface an agent owns: provisioning, the TUN, whose packet is whose |
|
||||
| `dns` | the DNS view of a network, and telling the system resolver about it |
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# 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 <network-id-or-prefix> off
|
||||
tsunagi network broadcast <network-id-or-prefix> on
|
||||
tsunagi network broadcast <network-id-or-prefix>
|
||||
```
|
||||
|
||||
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. `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
|
||||
|
||||
The game must send discovery through the Tsunagi interface. Traffic bound to a
|
||||
physical adapter never reaches this TUN, and this feature does not capture it.
|
||||
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. It does not launch CS 1.6 or Warcraft III or establish that
|
||||
every game/host chooses the virtual adapter automatically.
|
||||
|
||||
## 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.
|
||||
+8
-3
@@ -9,7 +9,7 @@ Two versions exist and are independent:
|
||||
|
||||
- **Identity scheme**, `tsunagi-network-id-v1`. Frozen. Changing it creates a
|
||||
different network space for the same name and secret.
|
||||
- **Control protocol**, ALPN `tsunagi/ctrl/2`, `PROTOCOL_VERSION = 2`.
|
||||
- **Control protocol**, ALPN `tsunagi/ctrl/3`, `PROTOCOL_VERSION = 3`.
|
||||
|
||||
Upgrading the crate or bumping the control protocol must never change an
|
||||
existing `NetworkId`.
|
||||
@@ -191,7 +191,7 @@ transport connection, not supplied by the frame. Intermediate nodes cannot
|
||||
decrypt or authenticate the inner WireGuard payload; the destination does that.
|
||||
Flow ids are routing hints, not authorization proofs.
|
||||
|
||||
Control ALPN 2 carries `Reachable { links: [{ peer, protocol }] }`. Each row
|
||||
Control ALPN 3 carries `Reachable { links: [{ peer, protocol }] }`. Each row
|
||||
belongs to the authenticated sender and is replaced atomically, expires after
|
||||
90 seconds, and is withdrawn on session closure. Only compatible authenticated
|
||||
members enter a protocol's graph; local edges always come from actual links.
|
||||
@@ -215,7 +215,7 @@ not affect other networks.
|
||||
|
||||
| message | meaning |
|
||||
|---|---|
|
||||
| `Announce { hostname, capabilities }` | this agent's hostname and IP-plugin capabilities |
|
||||
| `Announce { hostname, capabilities, broadcast }` | this agent's hostname, IP-plugin capabilities and local broadcast participation |
|
||||
| `Ping { seq, payload }` | small request used to verify the exchange |
|
||||
| `Pong { seq, payload }` | the echoed reply |
|
||||
| `State { records }` | a snapshot of signed records, merged into what the receiver holds |
|
||||
@@ -223,6 +223,11 @@ not affect other networks.
|
||||
| `Reachable { links }` | sender's current direct data links, scoped by protocol |
|
||||
| `Bye { reason }` | graceful goodbye; not a revocation of anything |
|
||||
|
||||
`broadcast` is a per-network local opt-in, enabled by default and treated as
|
||||
false until an authenticated announcement arrives. It governs IP broadcast
|
||||
fanout and local admission; encrypted transit still uses the existing envelope.
|
||||
See [broadcast.md](broadcast.md) for scope and persistence.
|
||||
|
||||
A `State` snapshot is merged, never substituted: an author missing from it is
|
||||
left untouched. Each record carries its own signature, so a peer forwarding
|
||||
somebody else's record cannot alter it, and a record that fails verification
|
||||
|
||||
+1
-1
@@ -86,5 +86,5 @@ counts sends without storing frames. Results are CPU forwarding cost, **not**
|
||||
end-to-end network latency or VPN throughput; encryption, fragmentation,
|
||||
sockets, congestion and scheduling contribute separately.
|
||||
|
||||
Wire compatibility: control ALPN `tsunagi/ctrl/2`, data ALPN `tsunagi/data/4`.
|
||||
Wire compatibility: control ALPN `tsunagi/ctrl/3`, data ALPN `tsunagi/data/4`.
|
||||
Upgrade every participant together; saved identities and network state persist.
|
||||
|
||||
@@ -76,6 +76,15 @@ links. Real encrypted 1280-byte TCP packets travel in both directions while the
|
||||
middle TUNs remain empty. A direct A—D link is enabled, then removed; the route
|
||||
switches back to the chain without replacing end-to-end tunnels.
|
||||
|
||||
Broadcast tests use limited and directed UDP game discovery packets over real
|
||||
WireGuard tunnels, including a missing direct link. Each willing peer gets one
|
||||
copy; disabling reception/origination works at runtime and unicast still works.
|
||||
IP-router tests cover source/destination domain checks, malformed UDP and the
|
||||
absence of reflection into outgoing fanout. SQLite migration tests preserve v3
|
||||
identity/settings and check opt-out after reopen/rejoin. CLI tests cover default
|
||||
on, per-network opt-out, runtime updates and conflicting flags. Actual games and
|
||||
host adapter selection are not simulated by these tests.
|
||||
|
||||
The ignored `forwarding_benchmark` measures the synchronous transit routine in
|
||||
release mode, excluding crypto and socket I/O. Run it explicitly as described
|
||||
in [routing.md](routing.md); it has no timing threshold in the default suite.
|
||||
|
||||
Reference in New Issue
Block a user