Skip to content

Build on the GetStream pion forks and always offer WARP - #6

Merged
tbarbugli merged 4 commits into
feature/skip-hello-verifyfrom
warp/sdk-clean
Oct 1, 2026
Merged

tbarbugli merged 4 commits into
feature/skip-hello-verifyfrom
warp/sdk-clean

Conversation

@tbarbugli

@tbarbugli tbarbugli commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Offers WARP (draft-uberti-tsvwg-warp) on every peer connection: DTLS 1.2 to 1.3, and DTLS carried in the ICE connectivity checks (SPED). The SFU negotiates both in band. An SFU without WARP answers as before, and the connection falls back to DTLS 1.2 after ICE. There is no option: one code path, nothing for callers to configure.

Stacked on #3 (skip the DTLS HelloVerify), which targets factory/3rtt. This PR's base is #3's branch, so the diff shows only the WARP work. Retarget to factory/3rtt once #3 merges. It doesn't depend on #4.

Commits:

  1. Build on the GetStream pion forks and always offer WARP:
    • replace directives to the forks;
    • pion/dtls/v3 → v4 imports;
    • SetDTLSVersionRange(1.2, 1.3) and EnableSped(true) in pc/pc.go;
    • TestWARPNegotiation: a WARP SFU gets DTLS 1.3 and SPED, a legacy SFU gets DTLS 1.2 without SPED.
  2. Read the selected pair's RTT only once ICE is connected (a race the join trace hit).
  3. pion-ice v4.4.4-warp.3: nominate in the first check toward the ICE-lite SFU, so ICE takes one round trip instead of two.
  4. Answer active to an offer with SPED. pion answers an ICE-lite offerer passive, which made the SFU the DTLS client on the subscriber peer connection. Its ClientHello could then only ride the responses to our checks. Answering active puts our ClientHello in our first check, as browsers do, so the subscriber is connected when ICE is. TestWARPSubscriberAnswersActive fails without the change.

Forks (all public, remote tags):

replace github.com/pion/webrtc/v4 => github.com/GetStream/pion-webrtc/v4 v4.2.22-warp.3
replace github.com/pion/ice/v4 => github.com/GetStream/pion-ice/v4 v4.4.4-warp.3
replace github.com/pion/dtls/v4 => github.com/GetStream/pion-dtls/v4 v4.0.0-rc.1-warp.1
replace github.com/pion/sctp => github.com/GetStream/pion-sctp v1.11.3-warp.1

Go doesn't apply replace directives from a dependency's go.mod. Every consumer of this SDK (the Vision-Agents router, video-sfu test/e2e) must add the same four to its own go.mod. Without them it builds against upstream pion and gets no WARP, or fails to build on the dtls/v4 imports.

Measured with cmd/joinbench: warm pubsub join, from Join to first media, medians of 10 runs, publish / subscribe in ms. Before is the T02 baseline SDK against an SFU without WARP. After is this PR, integrated with the other factory/3rtt work as 3rtt/t12-warp 9dd287d, against a WARP SFU (video-sfu #1622):

Runner RTT Before After
Local stack, injected latency 100 ms 1251 / 1278 758 / 878
Laptop (Amsterdam) → staging us-east1 ~104 ms 1433 / 1480 1093 / 1036
GKE us-east5 → staging us-east1 ~21 ms 446 / 616 404 / 373

DTLS after ICE went from 2 RTT to 0 on both peer connections.

Tested: GitHub Actions is blocked by org billing, so CI doesn't run on this PR. make ci (build, vet, gofumpt check, go test -race ./...) passes locally on the branch and on the 3rtt/t12-warp integration.

Replace pion/webrtc, pion/ice, pion/dtls/v4 and pion/sctp with the
GetStream forks that implement WARP (draft-uberti-tsvwg-warp), and move
to dtls/v4. Every peer connection now offers DTLS 1.2 to 1.3 and DTLS in
the ICE checks (SPED); the SFU decides in band. An SFU without WARP
answers as before, so there is one code path and nothing for callers to
configure.

The ICE fork also nominates on the first successful check against the
ICE-lite SFU instead of waiting for the next check tick.

The local network delay layer (internal/netdelay) moves to
pion/transport/v5 with pion/webrtc v4.2.22.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@tbarbugli
tbarbugli changed the base branch from main to feature/skip-hello-verify September 30, 2026 20:20
pion's ICETransport.GetSelectedCandidatePairStats reads the transport's
gatherer without its lock, and ICETransport.Start sets it. With SPED,
Start runs inside the DTLS start, concurrently with the join trace
polling the RTT, and the race detector caught it in video-sfu's
end-to-end suite. ICE connected is reported after Start, so reading from
then on is ordered after the write.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
tbarbugli added a commit that referenced this pull request Sep 30, 2026
tbarbugli and others added 2 commits September 30, 2026 23:02
…E-lite SFU

ICE on each peer connection connects one round trip after the first check
instead of two.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
pion answers an ICE-lite offerer passive, which made the SFU the DTLS
client on the subscriber peer connection. Its ClientHello could then only
ride the responses to our checks, one datagram each. Answering active to a
SPED offer puts our one-datagram ClientHello in our first check, as
browsers do, so the subscriber is connected when ICE is.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@tbarbugli
tbarbugli marked this pull request as ready for review September 30, 2026 21:37
@tbarbugli
tbarbugli merged commit 88630ab into feature/skip-hello-verify Oct 1, 2026
1 check passed
tbarbugli added a commit that referenced this pull request Oct 1, 2026
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