Repository navigation
feat(webrtc): track WebRTC-Direct STUN listener + v1/v2 (specs#715) after UdpMux #1437
Description
Activity
Taking on this issue
- added 13 commits that reference this issue
on Aug 16, 2026 79 remaining items
Status update (2026-09-03 evening): Issue body refreshed after merges.
- #1459 merged (
1ba536d3) — v2 listener + opt-in v2 dialer, including the framing/SCTP stack from fix(webrtc): decode data-channel framing as a byte stream; SCTP stream-id parity by DTLS role #1460. - #1460 closed as superseded.
- #1471 interop tests open: py → go v1+v2 passes; go → py xfail (#1470 — ICE succeeds, then the py listener tears down).
- #1459 merged (
Triage note (2026-09-04): Keeping open as the WebRTC-Direct umbrella.
On
main:- v1 STUN-dispatch listener + dialer: feat(webrtc): STUN-dispatch WebRTC-Direct listener (v1) + v1 dialer, /sdp harness opt-in #1449
- v2 flow (
libp2p+webrtc+v2/): feat(webrtc): WebRTC-Direct v2 flow (libp2p/specs#715) — listener accepts v2, dialer opt-in #1459
Still open:
- go → py teardown / inbound interop: WebRTC-Direct: go-libp2p dialer can't connect to the py listener (ICE succeeds, then the connection is torn down) #1470
- interop tests PR: test(interop): go-libp2p WebRTC-Direct interop tests #1471 (py → go passing; go → py still xfail)
- Remaining checklist: ICE-Lite, docs, specs#715 polish
Close this tracker once inbound interop and the remaining checklist items are done (or split into focused issues).
- added a commit that references this issue
on Sep 4, 2026 - added a commit that references this issue
on Sep 6, 2026 Hi @yashksaini-coder — thanks again for landing the go interop harness in #1471. That closes a big chunk of the #1437 tracking work.
What’s still open before we can call the WebRTC-Direct STUN / v1+v2 track done:
-
#1470 — go → py inbound teardown (highest priority)
ICE completes, then the py listener drops the connection. The interop suite already has strictxfails for this; once fixed they should flip to pass. -
True ICE-Lite in aioice
Spec expects a Lite controlled agent on the listener; we still run a full controlled agent today (DTLS server role is already in place). -
Docs
README / module docs: v1 as migration vs v2 as recommended, plus a note that browser dial needs v2 once NoSdpMangleUfrag is widespread. -
Later / optional
Track libp2p/specs#715 merge and refresh links; js-libp2p interop when js-libp2p#3480 is ready; optional UdpMux upstream to aioice.
Would you be willing to keep going on this, starting with #1470? If yes, we can scope that first and leave ICE-Lite / docs as follow-ups. If bandwidth is tight, just say so and we’ll reassign or split the work.
-
Status update (2026-09-07): Issue body refreshed after #1495.
- #1495 merged (
86fd16f1) — go → py inbound dials complete; closes #1470. - With #1471 + fix(webrtc): complete go->py WebRTC-Direct inbound dials (#1470) #1495, py ↔ go v1 and v2 both directions are the expected green path.
- Still open on this track: true ICE-Lite in aioice, docs (v1 vs v2), specs#715 merge tracking, js interop when js-libp2p#3480 is ready.
- #1495 merged (
Thanks @acul71 — matches on my side: #1471 + #1495 merged, py↔go v1/v2 green both directions, and the go→py
xfails are now real assertions.I'll take docs next since it's self-contained: a webrtc package-docstring section separating the STUN listener (spec path) from the
/sdpharness (dev-only), plus a README note — v1 as migration, v2 recommended, and that browser dial needs v2 once NoSdpMangleUfrag is widespread. I'll open a PR for it.On true ICE-Lite: the listener is currently a full aioice agent in the controlled role (aioice has no lite mode), which interoperates because the dialer nominates. A real Lite agent is an aioice-side change — happy to pursue it upstream if you think it's worth it, otherwise I'd leave it as a documented ceiling for now.
specs#715 / js-libp2p#3480 I'll keep watching and refresh links when they move. Anything else you'd like me to prioritize?
@acul71 @seetadev — update on the docs + ICE-Lite follow-ups:
- Docs → docs(webrtc): STUN listener vs /sdp harness, and v1/v2 dial guidance #1513 (closes docs(webrtc): document WebRTC-Direct STUN listener vs /sdp harness, and v1 vs v2 #1511): the package docstring + README now separate the STUN listener (spec path) from the
/sdpharness (dev-only), and give v1-vs-v2 dial guidance — v1 as the migration default while specs#715 is open, v2 recommended and required for browser dialling. Ready for review. - True ICE-Lite → feat(webrtc): true ICE-Lite controlled agent for the WebRTC-Direct listener #1512: the spec mandates the server be ICE-Lite, and every reference impl gets it as a native ICE-library flag (go-libp2p
pion SetLite(true)inlistener.go, rust-libp2pwebrtc-rs set_lite). aioice has no local lite mode — onlyremote_is_lite— so we can't just flip a flag. Our listener already meets 3 of pion's 4 Lite requirements (single host candidate, always controlled, completes on the dialer's USE-CANDIDATE); the only gap is respond-only (we still send our own checks). Clean fix is upstream in aioice; the current controlled agent interoperates and is robust after fix(webrtc): complete go->py WebRTC-Direct inbound dials (#1470) #1495. Full findings are on feat(webrtc): true ICE-Lite controlled agent for the WebRTC-Direct listener #1512 — I've left it open as a documented ceiling. Happy to open the aioice feature request if you think it's worth pursuing now.
- Docs → docs(webrtc): STUN listener vs /sdp harness, and v1/v2 dial guidance #1513 (closes docs(webrtc): document WebRTC-Direct STUN listener vs /sdp harness, and v1 vs v2 #1511): the package docstring + README now separate the STUN listener (spec path) from the
Status update (2026-09-10): Issue body refreshed against
main.- #1513 merged (
82f23996) — docs for STUN vs/sdpharness and v1/v2 dial guidance; closes #1511. - Core track remains green: v1 (feat(webrtc): STUN-dispatch WebRTC-Direct listener (v1) + v1 dialer, /sdp harness opt-in #1449), v2 (feat(webrtc): WebRTC-Direct v2 flow (libp2p/specs#715) — listener accepts v2, dialer opt-in #1459), go interop (test(interop): go-libp2p WebRTC-Direct interop tests #1471 + fix(webrtc): complete go->py WebRTC-Direct inbound dials (#1470) #1495).
- Still open: true ICE-Lite (#1512), specs#715 merge / dialer default flip, js interop, optional UdpMux upstream.
- #1513 merged (
Status update (2026-09-14): Issue body refreshed after libp2p/specs#715 merged.
- specs#715 merged (2026-09-11,
78e75c6) — WebRTC-Direct v2 is live on specsmaster. - py-libp2p track still green on
main: feat(webrtc): STUN-dispatch WebRTC-Direct listener (v1) + v1 dialer, /sdp harness opt-in #1449 / feat(webrtc): WebRTC-Direct v2 flow (libp2p/specs#715) — listener accepts v2, dialer opt-in #1459 / test(interop): go-libp2p WebRTC-Direct interop tests #1471 / fix(webrtc): complete go->py WebRTC-Direct inbound dials (#1470) #1495 / docs(webrtc): STUN listener vs /sdp harness, and v1/v2 dial guidance #1513. - Follow-ups now due: flip
webrtc_direct_dial_versiondefault to 2 (and drop "while specs#715 is unmerged" wording); land true ICE-Lite (#1532 / #1512).
- specs#715 merged (2026-09-11,
might be good to wait on setting default dial version to 2 until network has wider support. js-libp2p will keep using v1 to dial if munging is possible in the environment.
Status update (2026-10-02): Issue body refreshed after #1532.
- #1532 merged (
758328c4) — true ICE-Lite listener; closes #1512. - Spec-aligned STUN v1/v2 + ICE-Lite track is complete on
main(feat(webrtc): STUN-dispatch WebRTC-Direct listener (v1) + v1 dialer, /sdp harness opt-in #1449 / feat(webrtc): WebRTC-Direct v2 flow (libp2p/specs#715) — listener accepts v2, dialer opt-in #1459 / test(interop): go-libp2p WebRTC-Direct interop tests #1471 / fix(webrtc): complete go->py WebRTC-Direct inbound dials (#1470) #1495 / docs(webrtc): STUN listener vs /sdp harness, and v1/v2 dial guidance #1513 / feat(webrtc): true ICE-Lite listener for WebRTC-Direct (#1512) #1532; specs#715 already merged). - Dialer default stays v1 for now (@tabcat: wait for wider network support). Remaining optional: v2 default flip later, js interop, UdpMux upstream.
- #1532 merged (
@tabcat thanks — agreed. We'll keep the dialer default at
webrtc_direct_dial_version=1until there's wider network support (js and others still preferring v1 when munging works). v2 stays opt-in; listener already accepts both. Issue body updated to mark the default flip as deferred rather than due.
Status (2026-10-02)
libp2p/specs#715 MERGED (2026-09-11,
78e75c6). WebRTC-Direct v2 is on specsmaster(webrtc-direct.md).Core stack on
main:1d0876da)1ba536d3)/sdp, v1/v2) — #1513758328c4); closes #1512Default path: spec STUN on a shared UDP port. HTTP
POST /sdpremains opt-in (enable_sdp_http_harness=True) for py↔py experiments only.Dialer default stays
webrtc_direct_dial_version=1. Per @tabcat (2026-09-14): wait for wider network support; js-libp2p keeps dialing v1 when munging is possible. Flip to v2 later (stale "while specs#715 is unmerged" wording in config/docs is a small cleanup).Remaining (optional / deferred): dialer-default flip when the network is ready; js-libp2p interop when #3480 lands; optional UdpMux upstream to aioice. Spec-aligned STUN v1/v2 + ICE-Lite listener track is otherwise complete on
main.Summary
Tracking issue for a spec-aligned
/webrtc-directlistener 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 (merged).v1 (#1449), v2 (#1459), go interop (#1471 + #1495), docs (#1513), and true ICE-Lite (#1532) are on
main.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:
USERNAME=server_ufrag:client_ufraglibp2p+webrtc+v1/vslibp2p+webrtc+v2/)client_pwdby strippinglibp2p+webrtc+v2/fromserver_ufrag, then set the server's own ICE ufrag/pwd toserver_ufragbefore answering (critical for ICE)v2 exists because Chromium is removing SDP ICE-credential munging (libp2p/specs#672). Spec PR libp2p/specs#715 is merged (2026-09-11); Go reference support has landed (go-libp2p#3520); JS is in progress (js-libp2p#3480).
Prerequisites
UdpMuxshared-port demux — #1397UdpMuxto aioice / pin tested aioice versionsScope (checklist)
Listener (server) — primary
UdpMuxintolistener.py(spec path default; HTTP/sdpharness opt-in — #1449)set_unknown_stun_handler: parseUSERNAME, validate both ufrag halves, reject malformed inputlibp2p+webrtc+v1/→ v1 flowlibp2p+webrtc+v2/→ v2 flow — #1459setup:passiveon listener answerregister_addrafter ICE nomination; rate-limit unknown-STUN handlingDialer / SDP seam
make_v1_credential+build_synthetic_answerwebrtc_direct_dial_version=2— #1459Interop and docs
Non-goals (for this issue)
/webrtcvia relay) polish — see #773 / umbrella #546References
758328c4); closes #1512Suggested sequencing
UdpMux).