From cf30d9b7b3a69683d4ed5d49fb8a9c70ff3ca673 Mon Sep 17 00:00:00 2001 From: Rob Date: Sun, 3 May 2026 15:09:09 -0400 Subject: [PATCH] =?UTF-8?q?Add=20=C2=A78.6=20AutoInterface=20multicast=20d?= =?UTF-8?q?iscovery=20(dev-experience=20#5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LAN auto-detect protocol — drop a Reticulum node on any IPv6-capable network, configure AutoInterface, peers find each other with zero manual config. Seven sub-sections: §8.6.1 IPv6 multicast group derivation: address built from SHA256(group_id) with multicast_address_type (4 bits, permanent/temporary) and discovery_scope (4 bits, link/ admin/site/org/global). Default group_id = 'reticulum'. §8.6.2 UDP ports: 29716 discovery (multicast announces), 29717 unicast probe (interface disambiguation), 42671 data (Reticulum packets after peering). §8.6.3 Cadence: ANNOUNCE_INTERVAL = 1.6s, PEERING_TIMEOUT = 22s, PEER_JOB_INTERVAL = 4s, MCAST_ECHO_TIMEOUT = 6.5s. §8.6.4 Discovery announce body — msgpack with group_hash + MTU + optional IFAC seal. Peers from different groups on the same link don't accidentally peer. §8.6.5 Post-discovery: plain UDP datagrams on the data port, one full Reticulum packet per datagram, HW_MTU = 1196 (Ethernet-MTU-friendly). §8.6.6 IFAC integration — peers with mismatched IFAC keys see each other's discovery but can't decode each other's data. §8.6.7 Source map. Co-Authored-By: Claude Opus 4.7 (1M context) --- SPEC.md | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ todo.md | 10 ++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/SPEC.md b/SPEC.md index 7dabd20..9735fa8 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1657,6 +1657,89 @@ Before transmitting, RNode firmware listens on the configured frequency for a sh For host-side use cases — i.e. a Reticulum client driving an RNode — the firmware handles all CSMA invisibly and the host should not attempt its own. Host-side rate limiting at the announce-cap layer (§4.5 SHOULD-rule for ingress, and `Reticulum.ANNOUNCE_CAP` for outbound) is sufficient. +### 8.6 AutoInterface multicast discovery (LAN auto-detect) + +`AutoInterface` is the IPv6-multicast-based protocol Reticulum nodes use to discover each other on a LAN with zero configuration. Drop a node on any IPv6-capable network, configure `[[Default Interface]] type = AutoInterface`, and it finds peers automatically — no static IPs, no rendezvous server. + +The reference implementation is `RNS/Interfaces/AutoInterface.py` (~700 lines). This section specifies the wire-visible bits a clean-room implementation needs. + +#### 8.6.1 IPv6 multicast group derivation + +`AutoInterface.py:202-212`. Each AutoInterface mesh is identified by a `group_id` (default `b"reticulum"`); the actual multicast address is derived from a SHA-256 of the group_id: + +```python +group_hash = SHA256(group_id) # 32 bytes + +# Build the lower 7 hextets from group_hash bytes [2..14]: +gt = "0" +gt += ":" + f"{(g[3]+(g[2]<<8)):02x}" # bytes [2:4] +gt += ":" + f"{(g[5]+(g[4]<<8)):02x}" # bytes [4:6] +gt += ":" + f"{(g[7]+(g[6]<<8)):02x}" # bytes [6:8] +gt += ":" + f"{(g[9]+(g[8]<<8)):02x}" # bytes [8:10] +gt += ":" + f"{(g[11]+(g[10]<<8)):02x}" # bytes [10:12] +gt += ":" + f"{(g[13]+(g[12]<<8)):02x}" # bytes [12:14] + +mcast_discovery_address = "ff" + multicast_address_type + discovery_scope + ":" + gt +``` + +Where: + +| Field | Default | Value bits | +|---|---|---| +| `multicast_address_type` | `"1"` (temporary) — alt `"0"` (permanent) | 4 bits, `RFC 4291` flags | +| `discovery_scope` | `"2"` (link-local) | 4 bits, IPv6 scope: `"2"=link, "4"=admin, "5"=site, "8"=org, "e"=global` | + +So with the default `group_id = b"reticulum"`, default `multicast_address_type = "1"`, default `discovery_scope = "2"`, and `SHA256(b"reticulum")[2:14]` filling the lower hextets, every default-config Reticulum node on the same link-local subnet finds the same multicast address. + +#### 8.6.2 UDP ports + +`AutoInterface.py:47-48`: + +| Port | Default | Use | +|---|---|---| +| `discovery_port` | `29716` (UDP) | Periodic discovery announces from each peer; receivers learn other peers' link-local IPv6 addresses by their incoming source addr. | +| `unicast_discovery_port` | `discovery_port + 1` = `29717` | Per-interface unicast probes, used to disambiguate which physical interface a peer is on. | +| `data_port` | `42671` (UDP) | Once peers know each other's addresses, actual Reticulum packets flow as plain UDP datagrams between them on this port. | + +A clean-room implementation MUST listen on the discovery port for inbound multicast packets and the data port for inbound unicast packets, and emit periodic announces to the multicast address+port. + +#### 8.6.3 Discovery cadence + +`AutoInterface.py:61-64`: + +| Constant | Value | Meaning | +|---|---|---| +| `ANNOUNCE_INTERVAL` | `1.6s` | Each AutoInterface emits a discovery announce on this cadence. | +| `PEERING_TIMEOUT` | `22.0s` | A peer not heard from within this window is dropped. | +| `PEER_JOB_INTERVAL` | `4.0s` | Cadence of the per-interface peer-management job (eviction, rediscovery). | +| `MCAST_ECHO_TIMEOUT` | `6.5s` | If our own multicast emissions are not echoed back within this window on a given physical interface, the multicast routing on that interface is presumed broken. | + +The 1.6s + 22s pairing means a new node is discovered within ~1.6s of join (as soon as one announce cycle completes from any existing peer); a departing node is forgotten within ~22s of last contact. Both bounds are implementation-private, but a clean-room with radically different values may have visible interop quirks (peer flap if you announce too rarely; bandwidth waste if too often). + +#### 8.6.4 Discovery announce body format + +The discovery announce is a plain UDP datagram on the multicast address. The body is implementation-private — upstream uses a small msgpack blob containing the peer's `group_hash` (so peers from a different group on the same link don't accidentally peer), interface MTU, and an optional IFAC seal (Interface Authentication Code; if present, peers without the matching IFAC key reject the announce). Specific bytes of the discovery announce body aren't part of the wire spec for **Reticulum**; they're part of the wire spec for **AutoInterface peering**, and a clean-room AutoInterface implementation needs to mirror upstream's format. Read `AutoInterface.py::announce_handler` and `AutoInterface.peer_jobs` for the full layout. + +#### 8.6.5 Once peers are discovered + +After the discovery handshake establishes that two nodes are in the same Reticulum group on the same physical link, all subsequent Reticulum packet traffic flows as **plain UDP datagrams** on the data port (`42671` by default), unicast between the discovered link-local IPv6 addresses. There's no per-packet framing beyond the UDP envelope — each datagram body is one complete Reticulum packet (§2). Out-of-order delivery is handled by Reticulum's normal dedup and (where present) Link sequencing; UDP packet loss is masked by Reticulum's PROOF receipts and Resource sliding-window retransmits. + +Effective HW_MTU is `1196` bytes (`AutoInterface.HW_MTU`, line 44) — chosen to fit comfortably within standard Ethernet MTU minus IPv6/UDP overhead. + +#### 8.6.6 IFAC integration + +If the AutoInterface is configured with an `ifac_identity` (out-of-band-shared key), every Reticulum packet on the data port is IFAC-sealed and unmasked using the standard Transport-level IFAC mechanism (`RNS/Transport.py:1338-1387`). Peers with mismatched IFAC keys can see each other's discovery announces but can't decode each other's data — a pragmatic privacy boundary on a shared LAN. + +#### 8.6.7 Source map + +| File | What | +|---|---| +| `RNS/Interfaces/AutoInterface.py:43-72` | Default ports, group_id, scope/address-type constants, intervals | +| `RNS/Interfaces/AutoInterface.py:202-212` | Multicast address derivation from `SHA256(group_id)` | +| `RNS/Interfaces/AutoInterface.py:108+` | Per-interface socket setup (multicast join, unicast bind) | +| `RNS/Interfaces/AutoInterface.py::announce_handler` | Discovery announce emission | +| `RNS/Interfaces/AutoInterface.py::peer_jobs` | Peer aging, multicast-echo loop, IFAC sealing | + --- ## 9. Implementation gotchas diff --git a/todo.md b/todo.md index f3e37bf..46f4b97 100644 --- a/todo.md +++ b/todo.md @@ -396,7 +396,15 @@ order: top three save the most debugging hours. one-line entry in §2.5; deserves its own §6.x sub-section with body format and lifecycle. -- [ ] **§8.x (new): AutoInterface multicast discovery.** UDP +- [x] **§8.6 (new): AutoInterface multicast discovery.** Done. + Seven sub-sections: IPv6 multicast group derivation from + `SHA256(group_id)` with scope/address-type bits, default + UDP ports (29716 discovery / 29717 unicast probe / 42671 data), + discovery cadence constants, discovery announce body format + (msgpack with group_hash + MTU + optional IFAC seal), + post-discovery data flow as plain unicast UDP on the data + port carrying full Reticulum packets, IFAC integration, + source map. HW_MTU = 1196 (Ethernet-MTU-friendly). UDP multicast on a known group/port for LAN auto-detection of peers. Specific multicast group, port, magic bytes, beacon cadence. `RNS/Interfaces/AutoInterface.py` is the reference.