VA-10: WebRTC data-channel tunnel upgrade #55

Merged
nrupard merged 2 commits from feat/VA-10-webrtc-datachannel into main 2026-06-12 23:27:02 +02:00
Owner

Summary

WebRTC data-channel tunnel upgrade (VA-10, Phase 5). The browser may offer to upgrade a relayed-WebSocket tunnel to a WebRTC data channel for lower-latency KVM traffic; the agent is the answerer. Falls back to the WebSocket when WebRTC is unavailable.

What changed

  • New vervain-agent-rtc crate wrapping webrtc-rs: the ctrlChannel:102938 signalling envelope (offer/answer/webrtc0/1/2), an RtcUpgrade answerer (offer SDP in, answer SDP out with candidates bundled non-trickle, data-channel transport + open/failure signals), and ICE config decoding. Multicast-DNS host-candidate obfuscation is disabled so the agent advertises real host IPs.
  • Loopback integration tests negotiate two real webrtc-rs peers over localhost: data-channel establishment (AC#1), bidirectional frame flow (AC#2), and fall-back-on-no-connect (AC#3).
  • Live wiring in host::tunnel::run_desktop (+ host::tunnel_rtc): on a browser offer the desktop tunnel negotiates, answers, runs the webrtc0/1/2 switchover, and routes KVM tile frames onto the data channel once open (inbound input read from it too). Control frames stay on the WS; the WS is the fallback.
  • ICE (STUN/TURN) config is server-pushed via a new webrtcconfig control-channel action (server side: matching vervain-server PR).

Dependency

webrtc-rs is heavyweight (~50 transitive crates) but the only pure-Rust WebRTC stack. It builds on the pinned 1.94 toolchain and cross-compiles clean for x86_64-pc-windows-gnu, so the CI Windows parity check stays green.

Validation

oci-build/check.Dockerfile: fmt, clippy --deny warnings, build, full test suite (incl. the rtc loopback tests), and the Windows cross-check all pass. The live browser/NAT path is type-checked here; end-to-end needs a real server + browser.

Refs VA-10.

## Summary WebRTC data-channel tunnel upgrade (VA-10, Phase 5). The browser may offer to upgrade a relayed-WebSocket tunnel to a WebRTC data channel for lower-latency KVM traffic; the agent is the answerer. Falls back to the WebSocket when WebRTC is unavailable. ## What changed - New `vervain-agent-rtc` crate wrapping `webrtc-rs`: the `ctrlChannel:102938` signalling envelope (offer/answer/webrtc0/1/2), an `RtcUpgrade` answerer (offer SDP in, answer SDP out with candidates bundled non-trickle, data-channel transport + open/failure signals), and ICE config decoding. Multicast-DNS host-candidate obfuscation is disabled so the agent advertises real host IPs. - Loopback integration tests negotiate two real `webrtc-rs` peers over localhost: data-channel establishment (AC#1), bidirectional frame flow (AC#2), and fall-back-on-no-connect (AC#3). - Live wiring in `host::tunnel::run_desktop` (+ `host::tunnel_rtc`): on a browser `offer` the desktop tunnel negotiates, answers, runs the webrtc0/1/2 switchover, and routes KVM tile frames onto the data channel once open (inbound input read from it too). Control frames stay on the WS; the WS is the fallback. - ICE (STUN/TURN) config is server-pushed via a new `webrtcconfig` control-channel action (server side: matching vervain-server PR). ## Dependency `webrtc-rs` is heavyweight (~50 transitive crates) but the only pure-Rust WebRTC stack. It builds on the pinned 1.94 toolchain and cross-compiles clean for `x86_64-pc-windows-gnu`, so the CI Windows parity check stays green. ## Validation `oci-build/check.Dockerfile`: fmt, clippy --deny warnings, build, full test suite (incl. the rtc loopback tests), and the Windows cross-check all pass. The live browser/NAT path is type-checked here; end-to-end needs a real server + browser. Refs VA-10.
feat(rtc): WebRTC data-channel tunnel upgrade (VA-10)
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 3m21s
993c3f04ad
Adds the WebRTC data-channel upgrade path (Phase 5). The browser may offer to upgrade a relayed-WebSocket tunnel to a WebRTC data channel for lower-latency KVM traffic; the agent is the answerer.

New `vervain-agent-rtc` crate wrapping webrtc-rs:
- The exact `ctrlChannel:102938` signalling envelope the browser speaks (offer/answer/webrtc0/webrtc1/webrtc2), accepting the browser's mixed string/number `ctrlChannel` JSON.
- `RtcUpgrade`: takes the browser's SDP offer, produces an SDP answer with our gathered candidates bundled in (non-trickle, matching the browser), exposes the data channel as an inbound receiver + send method, and signals open/failure for switchover and fallback. Multicast-DNS host-candidate obfuscation is disabled so the agent advertises real host IPs.
- Loopback integration tests run two real webrtc-rs peers over localhost: data-channel establishment (AC#1), bidirectional frame flow (AC#2), and fall-back-on-no-connect (AC#3). All pass in CI.

Live tunnel wiring (`host::tunnel::run_desktop` + `host::tunnel_rtc`):
- On a browser `offer` control frame the desktop tunnel negotiates an upgrade, answers, runs the webrtc0/webrtc1/webrtc2 switchover, and routes KVM tile frames onto the data channel once open. Inbound desktop input is read from the channel too. If negotiation fails or the channel never opens, the tunnel keeps using the WebSocket (control frames always stay on the WS).
- ICE (STUN/TURN) config is server-pushed: a new `webrtcconfig` control-channel action caches the config for tunnels to use (the matching server side lands in vervain-server). Absent config yields a host-candidate-only connection.

Dependency: webrtc-rs is heavyweight (~50 transitive crates) but is the only pure-Rust WebRTC stack; it compiles on the pinned 1.94 toolchain and cross-compiles clean for `x86_64-pc-windows-gnu`, so the CI Windows parity check stays green.

Verified via `oci-build/check.Dockerfile`: fmt, clippy --deny warnings, build, the full test suite (incl. the rtc loopback tests), and the Windows cross-check all pass. The live browser/NAT path is type-checked here and validated against a real server + browser.

#VA-10

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(rtc): address VA-10 review findings
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 2m29s
494731f49a
Follow-up to the code review on PR #55.

- Off-loop negotiation (the main one): the desktop tunnel awaited `RtcUpgrade::answer` (SDP answer + ICE gathering, seconds against STUN/TURN) inline in its `select!` loop, freezing KVM capture and input for the gathering duration. Negotiation now runs on a spawned task; the finished upgrade + answer SDP arrive back over a channel the loop selects on, so capture/input keep flowing while ICE gathers.
- notify_one over notify_waiters: the data-channel `on_open` and the peer-connection-state-change handlers used `Notify::notify_waiters()` (stores no permit), which could be missed by a late `opened()` / `wait_failed()` waiter. Switched to `notify_one()` so a wakeup that fires before the waiter registers is still delivered (the always-firing capture interval masked this, but the signal is now prompt and interval-independent; matches the VA-9 fix).
- Close on teardown: the upgrade's peer connection was only closed on the failure path. `DesktopRtc` now closes the live upgrade on `Drop` (tunnel loop exit) and when a re-offer replaces an existing upgrade, so webrtc-rs peer-connection tasks are not leaked per session/offer.
- Bounded send-failure fallback: a wedged data channel that errored without a connection-state change retried (and paid the WS fallback) on every frame. After 8 consecutive send failures the channel is dropped back to the WS rather than retried per frame.

Verified via `oci-build/check.Dockerfile`: fmt, clippy --deny warnings, build, full test suite (incl. the rtc loopback tests), and the Windows cross-check all pass.

#VA-10

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/VA-10-webrtc-datachannel 2026-06-12 23:27:02 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-agent!55
No description provided.