fix(webrtc): decode data-channel framing as a byte stream; SCTP stream-id parity by DTLS role - #1460
Open
yashksaini-coder wants to merge 6 commits into
Open
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two pre-existing bugs that made every py↔go WebRTC-Direct connection fail, found by running the v1 code live against go-libp2p v0.49.0:
uvarint + pb.Messageper SCTP message, but go-msgio writes the varint prefix and the body as two separate writes → two SCTP messages. py failed at Noise msg#1 withmalformed handshake frame length(andWebRTCStream.on_datawould have dropped every split frame after that). Now both the Noise channel and streams decode frames from an accumulating byte buffer: split or batched frames are fine, the buffer is bounded byMAX_MESSAGE_SIZE, frames decoded before a malformed one are still delivered, a malformed frame resets the stream (RESET sent to the peer, further bytes ignored), and a whole SCTP message is applied in one trio hop.assert stream_id not in self._data_channels). We now re-seed the allocator's parity from the DTLS role (RFC 8832: client even, server odd) and let aiortc keep allocating and recycling ids.Also widens one Windows-sensitive loopback test's budget (two dials + two bounded closes).
Verified
Live against go-libp2p v0.49.0, both directions: go→py and py→go connect and complete Noise. Unit tests for split/batched frames, partial-delivery-then-reset, id parity and id reuse.
tests/core/transport/webrtc: 230 passed; mypy/ruff/pyrefly clean.Refs #1437 — first of two stacked PRs; the v2 flow (#1459) builds on this.