Skip to content

feat(webrtc): WebRTC-Direct v2 flow (libp2p/specs#715) — listener accepts v2, dialer opt-in - #1459

Open
yashksaini-coder wants to merge 8 commits into
libp2p:mainfrom
yashksaini-coder:feat/webrtc-direct-v2
Open

feat(webrtc): WebRTC-Direct v2 flow (libp2p/specs#715) — listener accepts v2, dialer opt-in#1459
yashksaini-coder wants to merge 8 commits into
libp2p:mainfrom
yashksaini-coder:feat/webrtc-direct-v2

Conversation

@yashksaini-coder

@yashksaini-coder yashksaini-coder commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

What

The v2 flow from libp2p/specs#715, on top of the v1 listener that landed in #1449.

  • Listener accepts libp2p+webrtc+v2/ first contacts alongside v1: recovers client_pwd by stripping the prefix (rejected unless a valid ice-pwd, 22–256), infers the offer with ice-ufrag=client_ufrag / ice-pwd=client_pwd, and sets its own ufrag and pwd to server_ufrag verbatim. Unknown/missing prefix still rejected; v1 unchanged (pwd = client half).
  • Dialer gets an opt-in WebRTCTransportConfig(webrtc_direct_dial_version=2): no munging — it reads its aioice-generated pwd and encodes it in the synthetic answer's ufrag/pwd as libp2p+webrtc+v2/<client_pwd>. Default stays v1 until specs#715 merges.
  • Docs: README + module docstrings; newsfragment extended.

Verified

Stacked on #1460. Refs #1437 (v2 item); go/js interop tests come next.

…rity by DTLS role

go-libp2p (go-msgio pbio fallback) writes the uvarint prefix and the
protobuf body as two SCTP messages, so every py<->go WebRTC-Direct
connection died at Noise msg#1 with "malformed handshake frame length".
New stream._decode_frames pops complete frames off an accumulating
buffer (frames may be split or batched across SCTP messages, bounded by
MAX_MESSAGE_SIZE); DataChannelReadWriter and WebRTCStream.on_data both
use it. A malformed frame now resets the stream instead of dropping one
message and desynchronising.

With the handshake passing, a py listener then hit aiortc's
`assert stream_id not in self._data_channels`: aiortc picks DCEP stream
id parity from the ICE role, which is inverted for a WebRTC-Direct
listener (ICE-controlled, DTLS server), so it used even ids like go's
dialer. _create_channel now passes an explicit id by DTLS role
(RFC 8832: client even, server odd).

Verified live against go-libp2p v0.49.0: go->py and py->go, v1 and v2.

Refs libp2p#1437
- keep aiortc allocating (and recycling) SCTP stream ids; only re-seed the
  parity from the DTLS role (explicit ids tied the 16-bit space to our
  never-recycled counter)
- on_data: deliver frames decoded before a malformed one, reset the peer
  (RESET + cleanup) instead of a local-only reset, ignore bytes after reset,
  and apply a whole SCTP message in one trio hop
- _decode_frames: derive the max prefix length from MAX_MESSAGE_SIZE

Refs libp2p#1437
The test performs two full dials and two bounded peer-connection closes;
on Windows each close can take ~5 s when a datagram write is in flight, so
30 s was flaky (seen once on CI, no socket errors logged).

Refs libp2p#1437
… collides

Harness mode binds TCP on the same port number as the OS-chosen UDP mux
port; Windows reserves port ranges per protocol, so that TCP bind can fail
with WinError 10013 (seen on CI in the harness loopback test). Retry up to
five times with a new UDP port before giving up; explicit ports still fail
loudly.

Refs libp2p#1437
acul71 and others added 4 commits August 30, 2026 01:21
…epts v2, dialer opt-in

Listener: stop dropping libp2p+webrtc+v2/ first contacts. The server ufrag
minus the prefix is the dialer's ICE pwd (must be a valid one, 22..256
ice-chars, else rejected in parse_direct_username); the inferred offer uses
client_ufrag + that pwd, and our local ufrag/pwd stay server_ufrag verbatim
as in v1. _accept_v1 -> _accept(client_pwd=...).

Dialer: new WebRTCTransportConfig.webrtc_direct_dial_version (default 1).
v2 keeps the aioice ufrag/pwd (no munging) and sets the synthetic answer's
ufrag == pwd to "libp2p+webrtc+v2/" + local_password. Default stays v1 while
specs#715 is unmerged; unknown values raise WebRTCConnectionError.

Tests: v2 parse/reject cases, SDP shape, v2 loopback echo, no-munge check,
short-suffix rejection, mixed v1+v2 dialers on one listener.

Refs libp2p#1437
- validate webrtc_direct_dial_version in the config (was only checked
  mid-dial after the PC was built)
- parse_direct_username returns the validated client pwd; make_v2_credential;
  one pwd check instead of two version branches
- inline the v1 credential writes; trim the dial() comment

Refs libp2p#1437
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants