Skip to content

fix(webrtc): wire the WebRTC-Direct inbound path; spec Noise roles; real loopback test - #1447

Open
yashksaini-coder wants to merge 4 commits into
libp2p:mainfrom
yashksaini-coder:fix/webrtc-direct-inbound-wiring
Open

fix(webrtc): wire the WebRTC-Direct inbound path; spec Noise roles; real loopback test#1447
yashksaini-coder wants to merge 4 commits into
libp2p:mainfrom
yashksaini-coder:fix/webrtc-direct-inbound-wiring

Conversation

@yashksaini-coder

Copy link
Copy Markdown
Contributor

What

First code step for #1437. Makes WebRTC-Direct actually connect end to end (it did not):

  • Listener inbound path was a stub (_complete_inbound logged and returned; handler never called). Now: listener-owned trio nursery (system task, like TCP), non-blocking asyncio→trio hop, Noise XX as initiator (spec: server initiates, dialer responds), authenticated WebRTCConnection handed to the handler. In-flight unauthenticated inbounds bounded by max_in_flight_connections.
  • dial() was broken: get_remote_fingerprint read pc._dtlsTransport._remote_certificate, which aiortc doesn't have. Now reads the DTLS peer cert from pc.sctp.transport._ssl. Dialer is the Noise responder and verifies the authenticated ID against /p2p/.
  • Noise prologue is role-ordered (dialer_fp ‖ server_fp, per spec) instead of local/remote.
  • PatternXX.handshake_outbound(remote_peer: ID | None) — skips only the equality check when None (server doesn't know the dialer's ID); signature verification unchanged. Existing callers unaffected.
  • DataChannelReadWriter.read(n) now honours n — the Noise packet reader asks for the 2-byte length prefix alone; returning a whole channel message broke framing.
  • Noise send waits for data channel 0 to open (initiator sends right after DTLS, before SCTP opens the channel → InvalidStateError).

Tests

  • New test_dial_listen_open_stream_echo: dial → ICE/DTLS → Noise → handler → stream echo, both sides see the other's peer ID. Plus wrong-/p2p/ rejection, e2e Noise role test, buffered-read test, fingerprint test, remote_peer=None pattern test.
  • tests/core/transport/webrtc + tests/core/security/noise: 319 passed locally; loopback run 5× no flake. mypy/pyrefly clean.

Stacked on #1446 (CI needs aiortc installed for these to run); will rebase once that merges.

Refs #1437

tox commands_pre installed no extras, so aiortc was absent in CI and every
test under tests/core/transport/webrtc was skipped on GitHub. aiortc 1.15 is
pure Python and its native deps (av, pylibsrtp) ship manylinux/win wheels, so
no system packages are needed.

Refs libp2p#1437
…oles

The listener's inbound completion was a stub: after ICE it logged and
returned, so no inbound connection ever reached the handler. dial() was
broken too - get_remote_fingerprint read attributes aiortc does not have.

- listener: own a trio nursery (system task, like TCP), hop asyncio->trio
  without blocking the loop, run Noise XX as *initiator* (spec: server
  initiates, dialer responds), then hand the authenticated connection to
  the handler; bound in-flight unauthenticated inbounds.
- transport.dial(): Noise responder; verify authenticated peer ID against
  /p2p/ after the handshake.
- noise: role-ordered prologue (dialer fingerprint, then server);
  PatternXX.handshake_outbound(remote_peer=None) skips only the ID
  equality check; DataChannelReadWriter.read(n) honours n (the Noise
  packet reader asks for the 2-byte prefix alone).
- helpers: get_remote_fingerprint reads pc.sctp.transport._ssl peer cert;
  noise send waits for data channel 0 to open.
- tests: transport-level dial->listen->stream echo loopback, wrong /p2p/
  rejection, e2e Noise role test, buffered read test, fingerprint test.

Refs libp2p#1437
- Frame Noise handshake bytes as uvarint-prefixed webrtc.pb.Message
  stream frames on channel 0 (spec 'Multiplexing'; what go/js do) instead
  of raw bytes - required for interop; py<->py was symmetric so tests did
  not notice.
- Listener: guard the handler call - the nursery lives in a trio system
  task, so an escaping exception aborted the whole trio run.
- dial(): bound the Noise phase (responder's first step is a read) with
  handshake_timeout; close the PC on every failure path, not just the two
  explicit mismatches; do not pass config.ice_servers (previously no STUN
  servers were used; the default Google STUN added ~5s to offline dials).
- Tests: framing round-trip/chunking/FIN/malformed, handler-exception
  regression.

Refs libp2p#1437
@yashksaini-coder
yashksaini-coder force-pushed the fix/webrtc-direct-inbound-wiring branch from 104ae51 to fb35ba6 Compare August 19, 2026 17:56
@yashksaini-coder

Copy link
Copy Markdown
Contributor Author

@seetadev @acul71 Rebased on latest main; CI fully green (incl. Windows). This PR makes WebRTC-Direct actually connect end-to-end: the inbound path was a stub and dial() read non-existent aiortc attributes — now the listener runs Noise as initiator per spec, hands the authenticated connection to the handler, and a real dial→listen→stream-echo test covers it. Stacked on #1446; follow-ups #1448#1449; refs #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.

1 participant