aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-30 23:22:10 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-30 23:22:10 +0200
commit3706dc15ff3be7acf2ade77977a9fca7a8b4efc5 (patch)
treec853fcf49247eb80ba62df9e99a57fc5a83f2c12 /docs
parentcebe64d6c14256a31247987bcafbce1a2bf2d8ba (diff)
downloadmeshbay-3706dc15ff3be7acf2ade77977a9fca7a8b4efc5.tar.gz
docs: add §2.12 STUN fallbacks and ICE filtering to draft v6
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'docs')
-rw-r--r--docs/meshbay-draft-v6.md60
1 files changed, 60 insertions, 0 deletions
diff --git a/docs/meshbay-draft-v6.md b/docs/meshbay-draft-v6.md
index c1ce3fc..a33ce75 100644
--- a/docs/meshbay-draft-v6.md
+++ b/docs/meshbay-draft-v6.md
@@ -15,6 +15,10 @@
> become unique per owner account (`name@owner`), and chat gains link previews. See §1
> rows 11–13 and §§2.8–2.10. Wire versions at that point: MNP 0.12, MHP 0.1; packages 0.8.0.
>
+> **Amended 2026-08-30** with WebRTC transport resilience: STUN server fallbacks on both
+> browser and node, configurable on the node side (Node page, CLI, `node.toml`); ICE
+> interface filtering exposed on the Node page. See §1 row 15 and §2.12.
+>
> The v5 convention is carried forward and is not negotiable: **a claim in this document
> must name the adversary it holds against.** A property that holds against a passive hub
> and not an active one is written that way.
@@ -54,6 +58,7 @@
| 12 | Group registry | A group name is **unique per owner account**, not globally; the group's identity is still its UUID. Listed everywhere as `name@owner` | §2.9 |
| 13 | Enrichment | **Chat link previews** — the node unfurls a pasted URL into an OpenGraph card. A new instance of the §2.7 "node on demand, asking device caches, nothing durable" rule; new SSRF surface, gated. MNP 0.12 | §2.10 |
| 14 | Node page | The Node page (D5) **exposes five `[node]` settings** — TTLs for invitations, pairing and device requests, the concurrent stream cap, and the transcode toggle. Editable from the panel, persisted in both `roster.db` and `node.toml` | §2.11 |
+| 15 | Transport | WebRTC ICE gathering uses **four public STUN servers** instead of one, configurable on the node side. ICE interface filtering is exposed on the Node page. Both follow the §2.11 persistence pattern | reliability — single-server STUN was a silent SPOF, §2.12 |
---
@@ -392,6 +397,61 @@ writer — comments and formatting survive.
writes changes and returns `node_settings_set_ack` with the updated keys. Both are
operator-only.
+### 2.12 WebRTC transport — STUN fallbacks and ICE interface filtering
+
+The WebRTC transport relied on a single hardcoded Google STUN server
+(`stun:stun.l.google.com:19302`) on both sides. If that server was unreachable or slow,
+ICE gathering waited the full 4-second timeout before completing — a silent single point
+of failure that added seconds to every connection.
+
+**Four default servers, two providers deep.**
+
+```
+stun:stun.l.google.com:19302
+stun:stun1.l.google.com:19302
+stun:stun.cloudflare.com:3478
+stun:stun.services.mozilla.com:3478
+```
+
+Both browser (`transport.js`) and node (`webrtc_server.py`) carry the same four defaults.
+The two sides gather ICE candidates independently and exchange them via SDP through the
+hub — they do not need to use the same STUN server, and neither learns which server the
+other used.
+
+**Node-side configuration.** The node's STUN list is editable three ways: the Node page
+(a dedicated section with add, remove, reorder, save and reset-to-defaults), the CLI
+(`meshbay-node stun list|add|remove|reset`), and `node.toml` (`stun_servers` under
+`[node]`). Persistence follows the §2.11 pattern: `roster.db` for immediate effect,
+`node.toml` so the value survives a DB wipe, roster override wins on startup. The live
+transport's STUN list is hot-swapped on save — no restart.
+
+**Browser-side: hardcoded, not configurable.** The browser's `iceServers` array is set in
+`transport.js`, shipped by the hub. No mechanism exists for a node to push STUN
+configuration to a browser — the hub relays SDP, not ICE policy — and none is needed:
+the browser is always on a consumer connection where the four public servers will work.
+
+**ICE interface filtering** (`ice_interfaces`) was already configurable in `node.toml`
+and enforced by a monkey-patch on `aioice`'s `get_host_addresses` (`ice_filter.py`). It
+is now also editable from the Node page — same persistence, same hot-swap (the filter is
+reinstalled on save). Two modes: auto (empty list — excludes virtual and VPN adapters by
+heuristic) and manual (explicit interface whitelist). The filter is **node-side only**: it
+controls which local addresses the node offers as ICE candidates. The browser's ICE
+gathering is governed by Chromium's own WebRTC stack and is not configurable from this
+application.
+
+**What prompted this.** A VPN client (ProtonVPN) adding virtual network interfaces caused
+the browser-side ICE gathering to include those interfaces' candidates. The remote node
+then spent seconds attempting to resolve `.local` mDNS candidates for addresses it could
+not reach, inflating connection time from ~1 s to ~6 s. The STUN fallback makes the
+server-reflexive path resilient; the ICE filter lets the operator exclude problematic
+interfaces on the node side. The browser-side delay from VPN interfaces is a Chromium
+behaviour outside this application's control.
+
+**Trust note.** A STUN server learns the querier's public IP and NAT mapping — that is
+its purpose. No content, no credentials, no group metadata passes through STUN; the
+protocol is a single binding request/response. Adding a STUN server is trusting its
+operator to learn your NAT topology, nothing more.
+
---
## 3. Filesystem portability as a security property