Repository navigation
Add rtcprobe, a pion WebRTC control test outside Unbounded - #476
Conversation
A probe and a responder that connect with plain pion: no freddie, no volunteers. Each attempt reports how long gathering, signaling, ICE, DTLS and the data channels took, where it stalled, the probe's srflx addresses, and the throughput and loss of a short transfer. The probe picks the data channel mode, message size, direction, DTLS fingerprint variant and the responder's DTLS role, so runs from one vantage point can A/B a censor fingerprinting pion's DTLS, and measure pion's own ceiling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019DnqgLJf7p4sK87dDvktjx
- Bound signaling connections with read, write and idle deadlines. - Accept one data and one ctl channel per session, so a peer that opens two ctl channels can't make the responder send on a nil channel and crash. - Serve signaling over TLS with a self-signed certificate the probe pins, and refuse plain HTTP to a remote responder, so the token isn't sent in the clear from the networks we probe. - Wait for a transfer's stragglers before counting: every message on a reliable channel, or a quiet period on an unreliable one, and flag a drain that ran out of time. - Rate the whole transfer over the sender's span, so a connection that stalls isn't credited with its pre-stall burst, and report the burst rate separately. - Fail on result write errors, and release the session when the answer can't be written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019DnqgLJf7p4sK87dDvktjx
- send returned a copy of its result before the deferred SendSecs update, so every transfer reported a zero send span and the whole-transfer rate fell back to the burst rate. Use named results, report send_secs, and check it in the end-to-end test. - An unreliable drain counted quiet time from the last message even when that was before done arrived, so it could stop at once and miss stragglers. Count from settling's start at the earliest. - Don't follow signaling redirects, which would carry the token with them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019DnqgLJf7p4sK87dDvktjx
common/covertdtls disguises native producers' ClientHello, because Russia DPI-filters pion's (net4people/bbs#603), but Unbounded's consumer is the DTLS server and sends pion's ServerHello unshaped. With -dtls covert and the responder role switch, one vantage point can compare pion's ServerHello (production's consumer), pion's ClientHello, and covert-dtls's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019DnqgLJf7p4sK87dDvktjx
|
Warning Review limit reachedOnly developers with an assigned seat can use this organization's usage-based review budget, and seats here are assigned manually. Ask an admin to assign a seat, or change the review continuation mode in Billing. Next included review available in 19 minutes. View limit details
Comment |
There was a problem hiding this comment.
🔵 Needs a closer look
The public-facing responder and DTLS diagnostic controls warrant final human review beyond the reported loopback validation.
No open issues 🎉
What changed in this PR
Adds rtcprobe, a standalone WebRTC diagnostic tool that isolates connection failures and throughput from Unbounded’s signaling, matchmaking, and volunteer infrastructure.
Changes:
- Adds probe and responder commands with connection timings, failure-stage reporting, and transfer measurements.
- Supports configurable DTLS variants, roles, and data-channel modes, with authenticated signaling, certificate pinning, and responder limits.
- Adds integration and regression tests for connections, signaling protections, duplicate channels, and measurement logic.
| File | Description |
|---|---|
rtcprobe/serve.go |
Implements the responder, session limits, signaling deadlines, and optional TLS. |
rtcprobe/rtcprobe_test.go |
Tests probe/responder interoperability, signaling protections, and transfer accounting. |
rtcprobe/probe.go |
Implements probe attempts, JSON results, summaries, and secure signaling. |
rtcprobe/main.go |
Adds command dispatch and usage documentation. |
rtcprobe/common.go |
Provides DTLS configuration, channel management, and transfer measurement helpers. |
go.mod |
Promotes the existing Pion DTLS dependency from indirect to direct. |
Review effort: Balanced
Adds
rtcprobe, a probe and responder for testing plain pion WebRTC connections with all of Unbounded's own machinery removed: no freddie, no matchmaking, no volunteers.Why
freddie.producer.outcomesshows that most volunteer attempts fail, about 14% of them after ICE connects. Production data alone can't separate three causes:This tool is the control. The responder runs somewhere with an open NAT, and the probe runs from the network under test.
There's a specific reason to suspect the third cause.
common/covertdtlssays Russia has DPI-filtered pion's DTLS fingerprint since 2026-03-30 (net4people/bbs#603), and covert-dtls shapes the ClientHello of native volunteers. But Unbounded's consumer makes the offer, so it's the DTLS server: it builds pion throughnewWebRTCAPIwith no covert-dtls and sends pion's default ServerHello. The probe can test exactly that case.What it does
gather,signal,ice,dtlsorsctp);unreliable(unordered, no retransmits, as Unbounded) orreliable;default,alt(different cipher suites and curves), orcovert(covert-dtls's randomize+mimic);server, the probe sends the ClientHello instead.Findings so far (loopback)
Every arm connects within about 260 ms. Throughput on loopback, with no loss and no latency, is about 43 Mbps with 1,200-byte messages and about 85 Mbps with 16 KB messages. Unbounded sends one QUIC packet per data-channel message, so pion's per-message cost caps a single connection before the network does.
Testing
go test -race ./rtcprobe/: ok. It covers:altandcovertvariants, and both roles;rtcprobe serve/rtcprobe probeacross the matrix: all connected; numbers above.go build ./..., andGOOS=js GOARCH=wasm go build ./cmd: ok.rtcprobeisn't part of the widget.Risk
None to production: this is a new standalone command, and nothing imports it.
go.modmovespion/dtls/v3from indirect to direct.How to revert
Revert the merge commit.
Pre-PR review (local Codex gate)
CODEX GATE: verdict=approve critical=0 important=0 minor=0 preexisting=0 reviewed=14f27b319approvertcprobe/serve.go— signaling had no read, write or idle deadlines, so slow clients could tie up connections. (r1)rtcprobe/common.go— duplicatectlchannels made the responder send on a nil channel and panic. (r1)rtcprobe/probe.go— the token was sent over plain HTTP. Now TLS with a pinned certificate. (r1)rtcprobe/serve.go,probe.go— counts were finalized 500 ms afterdone, though data can arrive later. Now it waits for every message (reliable) or for quiet (unreliable), and flags a drain that ran out of time. (r1)rtcprobe/common.go— throughput counted only the burst. Now it's over the sender's whole span. (r1)rtcprobe/probe.go— result write errors were ignored, and a failed answer write didn't release the session slot. (r1)rtcprobe/common.go—sendreturned its result before the deferred send-duration update, so the r1 throughput fix had no effect. (r2)rtcprobe/common.go— the unreliable drain could stop at once whendoneovertook the data. (r2)rtcprobe/probe.go— redirects would have carried the token elsewhere. (r2)AGENTS.mdadditions: none🤖 Generated with Claude Code
https://claude.ai/code/session_019DnqgLJf7p4sK87dDvktjx