Skip to content

feat(webrtc): track WebRTC-Direct STUN listener + v1/v2 (specs#715) after UdpMux #1437

Description

@acul71

Status (2026-08-28)

v1 STUN-dispatch listener + v1 dialer landed on main via #1449 (merge 1d0876da; stack #1446#1448 merged with it).

Default path: spec STUN on a shared UDP port (offer inferred from first packet). HTTP POST /sdp is opt-in (WebRTCTransportConfig(enable_sdp_http_harness=True)) for py↔py experiments only.

Still open on this issue: v2 listener/dialer flow, go/js interop, true ICE-Lite in aioice, optional UdpMux upstream.


Summary

Tracking issue for a spec-aligned /webrtc-direct listener that demuxes concurrent inbound dials on one advertised UDP port via STUN, including WebRTC-Direct v1 and v2 version-prefix dispatch per libp2p/specs#715.

v1 is on main (#1449). v2 and interop remain.

Built on the ICE-mux primitive (#1397 / spike #1352). Umbrella: #546. Earlier scaffolding (#1309) is superseded for node-to-node by the spec STUN path.

Background

WebRTC-Direct v1 and v2 are no-signaling transports. The listener reconstructs the dialer's offer from the inbound STUN connectivity check:

  • Read USERNAME = server_ufrag:client_ufrag
  • Dispatch on version prefix (libp2p+webrtc+v1/ vs libp2p+webrtc+v2/)
  • For v2: recover client_pwd by stripping libp2p+webrtc+v2/ from server_ufrag, then set the server's own ICE ufrag/pwd to server_ufrag before answering (critical for ICE)

v2 exists because Chromium is removing SDP ICE-credential munging (libp2p/specs#672). Spec PR libp2p/specs#715 is approved but not yet merged; Go reference support has landed (go-libp2p#3520); JS is in progress (js-libp2p#3480).

Internal note / roadmap compiled from the spec: downloads/AI-PR-REVIEWS/1309/WebRTC_NewSpecs.md (local maintainer doc).

Prerequisites

  • Spike: aiortc / aioice ICE mux + STUN USERNAME exposure — #1352
  • In-tree UdpMux shared-port demux — #1397
  • (Optional follow-up) Upstream UdpMux to aioice / pin tested aioice versions

Scope (checklist)

Listener (server) — primary

  • Wire UdpMux into listener.py (spec path default; HTTP /sdp harness opt-in and clearly marked experimental — #1449)
  • First-contact path via set_unknown_stun_handler: parse USERNAME, validate both ufrag halves (RFC 8839 ice-char / length), reject malformed input
  • Version dispatch — v1 + reject path (v2 acceptance not implemented yet):
    • libp2p+webrtc+v1/ → v1 flow
    • libp2p+webrtc+v2/ → v2 flow (recover client_pwd, set local ICE creds to server_ufrag before answer)
    • unknown / missing prefix → reject (do not assume v1)
  • Infer dialer offer from STUN (v1: ufrag/pwd, source IP for c= line — IP only, not port; placeholder fingerprint + DTLS verify disabled inbound per spec; Noise authenticates)
  • ICE Lite / DTLS roles per spec — partial on main:
    • DTLS server / setup:passive on listener answer (force_listener_dtls_server_role; inferred offer uses actpass)
    • True ICE-Lite controlled agent in aioice (dialer's synthesised answer carries a=ice-lite; listener is still a full controlled agent)
  • register_addr after ICE nomination (via attach_muxed_connection state callback); teardown unregister — covered in UdpMux unit tests (#1448)
  • Rate-limit / bound unknown-STUN handling (per-source-IP token bucket + in-flight cap — #1449)

Dialer / SDP seam

  • v1 dialer: make_v1_credential + build_synthetic_answer; spec-path ice_servers=[] (no default Google STUN)
  • v2 dialer: explicit v2 behaviour (no local offer munging; embed client_pwd in synthetic answer ufrag/pwd)
  • Document that browser dial requires v2 once NoSdpMangleUfrag ships widely

Interop and docs

  • Interop tests against go-libp2p (and js-libp2p when ready) for v2 browser→server and node→node where applicable
  • Module / README / newsfragments distinguish experimental HTTP harness vs spec STUN listener (#1449; newsfragments/1437.feature.rst)
  • README/docs: v1 (migration) vs v2 (recommended) once v2 ships
  • Track libp2p/specs#715 merge; refresh links when the v2 text lands on master

Non-goals (for this issue)

References

Resource Link
Merged stack (v1 listener + dialer) #1449 (+ #1446#1448)
Spec PR (webrtc-direct v2) libp2p/specs#715
Chrome munging issue libp2p/specs#672
Current webrtc-direct.md https://github.com/libp2p/specs/blob/master/webrtc/webrtc-direct.md
Umbrella #546
Scaffolding #1309
ICE mux spike #1352
UdpMux PR #1397
Go v2 libp2p/go-libp2p#3520
JS v2 libp2p/js-libp2p#3480

Suggested sequencing

  1. Merge feat(webrtc): add UdpMux for shared-port WebRTC-Direct inbound dispatch #1397 (UdpMux).
  2. STUN-dispatch listener v1 + v1 dialer — feat(webrtc): STUN-dispatch WebRTC-Direct listener (v1) + v1 dialer, /sdp harness opt-in #1449 merged.
  3. v2 listener + v2 dialer on top of the same UdpMux path.
  4. Add Go (then JS) interop vectors.
  5. Only then claim browser-dial support.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions