Skip to content

ADR 0071 — OOBI Satellite Federation Wave 2: P2P Enhancement + Auto/Hybrid (umbrella)

Status Date Author Supersedes Superseded by
Accepted 2026-05-19 André Luiz Gallon

Context

ADR 0049 shipped the OOBI Satellite Federation framework. Its Wave 1 implementation (merged via Wave-OSF PRs #834-#846) delivered:

  • Pattern A — CONNECT.Art rendezvous client (ADR 0050)
  • Pattern B — STUN-coord P2P client with ICE-lite hole-punching (ADR 0051)
  • Policy orchestrator with manual mode selection
  • Triple-encryption posture (TLS PQC + AEAD PSK + per-app payload)

The Wave 1 P2P implementation (Pattern B) is transport-layer opaque above the hole-punch — once the UDP path opens, the client returns an io.ReadWriteCloser and lets upper layers run plaintext. That was acceptable for Wave 1 because the customer's MGMT mesh ran mTLS over TCP on the same wire after handshake.

Wave 2 closes three remaining gaps:

  1. No NAT classification — the operator has no way to ask "will P2P even work for this peer?" before attempting. Currently you try, fail with ErrHolePunchFailed, and fall back. RFC 5780 behaviour discovery lets you predict P2P success rate per peer.

  2. No transport-layer encryption on the P2P data plane — once hole-punch succeeds, the UDP socket has no per-packet AEAD. An attacker between the peers (rare but possible at sovereign customer DCs) sees raw bytes. DTLS 1.3 (RFC 9147) is the industry-standard fix; matches what TLS 1.3 does over TCP.

  3. No auto/hybrid mode — the operator must pick A or B per deployment. Tailscale's experience (~70-80% peers go P2P direct, ~20-30% need relay) shows an automatic chooser with a fallback chain delivers better median latency than either-or-only.

This ADR is the umbrella for Wave 2. Three follow-up PRs formalize specific contracts (pkg/oobi/satellite/p2p/ package layout, signaling-only mode on CONNECT.Art, rollout/observability).

Architectural decision

Five LOCKED decisions, recorded here as the canonical reference.

D1: Default mode when operator does not choose = rendezvous

When a deployment registers without specifying satellite.mode, the Dashboard provisions mode: rendezvous. The Pattern A path covers 100% of NAT topologies including CGNAT.

Why not p2p as default: P2P fails on ~30-40% of customer NATs (symmetric, CGNAT). A failed default disrupts onboarding.

Why not auto as default: auto requires functioning P2P AND rendezvous infrastructure side-by-side; a fresh deployment may not yet have a customer-hosted rendezvous URL provisioned. Default must work with zero customer infrastructure beyond the operator's laptop reaching tlsstress.art.

Why not direct: requires explicit L3 link setup by the customer's network team. Cannot be a default.

Precedent: Tailscale ships DERP-on by default, falls to direct WireGuard only when ICE confirms reachability.

D2: Wave order = ship P2P enhancement first, auto/hybrid in Wave 2.5

Auto/hybrid composes P2P + Rendezvous + NAT classifier. It cannot exist before P2P matures. Splitting into two sub-waves:

  • Wave 2 — NAT classifier + DTLS data plane + signaling-only mode on CONNECT.Art. Operator continues choosing mode manually but with strictly better P2P reliability.
  • Wave 2.5 (follow-up) — auto orchestrator that consults the classifier and falls back automatically.

Wave 2 ships value alone; Wave 2.5 is incremental ~500 LoC on top.

D3: Auto-detection = pion/stun for RFC 5780 parsing + classifier in pkg/oobi/satellite/p2p/natclass/

NAT type classification (Open Internet, Full Cone, Restricted Cone, Port Restricted, Symmetric, CGNAT) is computed from RFC 5780 behaviour discovery probes. Reuse github.com/pion/stun for wire parsing (it is the de facto Go STUN library, used by Tailscale's netcheck and by Pion WebRTC). Keep the classifier policy (probe sequence, timing, recommendation table) in our own package under pkg/oobi/satellite/p2p/natclass/.

Why not roll our own STUN parser: RFC 5389 + 5780 + 8489 binding wire formats are well-understood but tedious; pion/stun is production-tested by millions of WebRTC streams daily.

D4: Signaling channel = CONNECT.Art in signaling-only mode

Wave 2 adds a new HTTP/2 endpoint to CONNECT.Art: POST /sat-signal/v1/exchange. Single round-trip ICE candidate exchange between two peers. No bridging, no relay — pure control-plane.

Why not pub/sub (MQTT/NATS): single-trust-chain principle. CONNECT.Art already has mTLS + Bearer + PQC + audit chain. Adding MQTT means standing up a second authn/authz surface and a second cluster to operate. Signaling is tiny (<1 KB per session, hundreds of sessions per day per customer) — bandwidth is not the constraint.

Why not DNS TXT: latency too high, cache pollution risk, operator's customer DNS may not be controlled by them.

Precedent: Tailscale's coordinator, Cloudflare Argo control plane, Zerotier roots — all reuse the rendezvous infra for signaling.

D5: DTLS library = pion/dtls v3 (DTLS 1.3, RFC 9147)

Go standard library has no DTLS. Wave 2 introduces a DTLS data plane wrapper at pkg/oobi/satellite/p2p/dtls/ using pion/dtls v3. DTLS 1.3 supports the same cipher suites as TLS 1.3 (RFC 8446), which means the PQC hybrid (X25519 + ML-KEM-768) LOCKED for Wave 1 ([memory: project_oobi_satellite_pqc_hybrid_locked_2026_05_14]) applies identically.

Constraints accepted with this third-party dependency:

  • Licence: pion/dtls is MIT, compatible with PolyForm Non-Commercial 1.0.0 + Appendix A.
  • Supply chain: vendor pin in go.mod + verify go.sum + Sigstore strict admission (Wave-4 D13) refuses unsigned binaries.
  • Maintenance: monitor pion/dtls releases via Dependabot. Critical CVEs propagate within 48h to all customer GATEWAY.Art binaries via the existing OTA channel.

Why not implement DTLS in-house: months of cryptographer work + independent audit + indefinite maintenance burden. The risk of an introduced cryptographic bug far exceeds the supply- chain risk of one well-maintained library.

Why not skip DTLS and run TLS over reliable-UDP: doable but slower (TCP semantics over UDP loses the simplicity). DTLS 1.3 is designed exactly for this case.

Wave 2 PR plan

Four sub-PRs deliver the foundation. Deep implementation (~7000 LoC) follows in Wave 2 deep-impl PRs, gated by this foundation landing first.

PR Title Scope
PR-SAT-P2P-W2-1 (this) ADR 0071 umbrella + 3 langs docs only
PR-SAT-P2P-W2-2 pkg/oobi/satellite/p2p/ scaffold (types + interfaces, no impl) ~600 LoC
PR-SAT-P2P-W2-3 Reviewer checklist + CONNECT.Art signaling-only mode spec ~400 LoC docs
PR-SAT-P2P-W2-4 Rollout runbook + Prometheus metric names ~500 LoC

Reviewer enforcement (PR checklist)

Any PR submitted under PR-SAT-P2P-W2-* MUST:

  • Not regress the dual-pattern operator-choice LOCK (memo project_oobi_satellite_dual_pattern_operator_choice_locked_2026_05_14)
  • Not regress the PQC hybrid LOCK (memo project_oobi_satellite_pqc_hybrid_locked_2026_05_14)
  • Not regress the dual-mesh + L7 heartbeat LOCK (memo project_oobi_satellite_dual_mesh_heartbeat_locked_2026_05_14)
  • Honour D1 — default mode is rendezvous when unset
  • Honour D5 — DTLS data plane uses pion/dtls v3, not custom code
  • Honour D3 — STUN behaviour discovery uses pion/stun, classifier policy in our own package
  • Honour D4 — signaling reuses CONNECT.Art, not a new bus
  • Honour D2 — auto/hybrid mode is OUT OF SCOPE for Wave 2 (Wave 2.5)
  • Ship matching Prometheus metric names + alerts when adding new runtime behaviour
  • Ship matching docs (en + pt-BR + es) when adding new operator surface (per feedback_always_document_new_functionality)

Reject any PR that:

  • Hardcodes a mode without honouring the per-deployment override
  • Adds DTLS implementation outside pion/dtls
  • Adds STUN parsing outside pion/stun
  • Introduces a new signaling bus (MQTT, NATS, pub/sub) in place of CONNECT.Art signaling-only mode
  • Ships code without the matching Prometheus metrics + runbook

Patent angle

Wave 2 + Wave 2.5 may anchor a new patent family (Family E extensions, claims 21/22/23):

  • Claim 21: NAT-classification-driven mode selection across a PQC-hybrid handshake. The novelty is the LOCKED-policy table that maps STUN behaviour outcomes to one of {direct, p2p, rendezvous, auto} with deterministic fallback ordering, all within a single managed-onboarding flow.
  • Claim 22: Triple-encryption posture preserved across DTLS 1.3 (UDP) and TLS 1.3 (TCP) paths — the same per-app payload AEAD applies identically, so a switch between Pattern A and Pattern B is transparent to the application above.
  • Claim 23: Signaling-only mode of a rendezvous service — same trust chain reused for two qualitatively different control-plane jobs (full bridging vs. signaling-only) without duplicating authn/authz surface.

Provisional filing target: Q4 2026, after Wave 2 deep-impl PRs land and at least one customer is running auto/hybrid in production.

Memory cross-references

  • [[project_oobi_satellite_dual_pattern_operator_choice_locked_2026_05_14]]
  • [[project_oobi_satellite_pqc_hybrid_locked_2026_05_14]]
  • [[project_oobi_satellite_dual_mesh_heartbeat_locked_2026_05_14]]
  • [[project_wave_osf_complete_2026_05_16]]
  • [[feedback_always_document_new_functionality]]

References

  • ADR 0049 — umbrella
  • ADR 0050 — Pattern A
  • ADR 0051 — Pattern B
  • ADR 0054 — PQC mandate
  • RFC 5389 — STUN
  • RFC 5780 — STUN behaviour discovery
  • RFC 8445 — ICE
  • RFC 8489 — STUN (revised)
  • RFC 9147 — DTLS 1.3
  • pion/dtls v3 — https://github.com/pion/dtls
  • pion/stun — https://github.com/pion/stun