Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

wt_bridge — UDP→WebTransport relay

Receives RFC 9828 RTP packets on a UDP socket and fans them out to every connected WebTransport client. No HTJ2K parsing — the browser viewer's WASM decoder owns that.

Each connected viewer gets one server-initiated unidirectional WebTransport stream. Packets are written length-prefixed: [u16BE len][packet bytes]…. Streams (not datagrams) because Chromium's negotiated WebTransport datagram size caps at ~1170 B, below typical RFC 9828 packet sizes (~1400 B). On LAN the head-of-line cost vs datagrams is negligible.

Prerequisites

  • Go ≥ 1.22 — pinned by go.mod; older toolchains will refuse to build.
  • No cgo, no architecture-specific code — a Go toolchain is the only build-time requirement.
  • For the end-to-end LAN viewer stack (browser, static server, dev cert), see docs/wt_viewer.md.

Quick start (dev mode)

go build -o wt_bridge .
./wt_bridge --listen-udp 0.0.0.0:6000 --listen-quic 0.0.0.0:4433 --dev

--dev generates an ephemeral self-signed ECDSA P-256 certificate (13-day validity, key usage = digitalSignature, EKU = serverAuth) and prints its SHA-256 hash to stderr:

[wt_bridge] dev cert SHA-256:
[wt_bridge]   ab:cd:…
[wt_bridge] viewer URL hint: ?certHash=ab:cd:…

Paste that hash into the viewer's ?certHash=… URL parameter — Chromium's serverCertificateHashes API trusts it for that one connection without a public CA. Cert constraints come from the WebTransport spec: ECDSA-P256, validity strictly less than two weeks.

Producer side: point any RFC 9828 sender at the bridge's UDP port. Two examples:

# 1. The rpicam-apps fork on a Raspberry Pi:
rpicam-vid --rtp-host <bridge-ip> --rtp-port 6000 …

# 2. Replay a captured .rtp fixture (no live producer needed):
node scripts/udp_replay.mjs <fixture.rtp> --port 6000 --fps 30 --loop

Any other RFC 9828 sender works the same way — the bridge is payload-agnostic.

CLI

wt_bridge [flags]

  --listen-udp   <addr>   UDP bind for incoming RTP                  [0.0.0.0:6000]
  --listen-quic  <addr>   QUIC bind for outgoing WebTransport        [0.0.0.0:4433]
  --max-clients  <N>      Max concurrent WT sessions                 [8]
  --queue-depth  <N>      Per-session packet queue depth             [8192]
                          Drop-oldest on overrun.
  --cert <path>           PEM cert chain (use instead of --dev for a CA cert)
  --key  <path>           PEM private key matching --cert
  --dev                   Ephemeral self-signed cert + hash printout

Wire contract

  • UDP in: arbitrary RFC 9828 packets, one packet per UDP datagram. The bridge does not validate the payload.
  • WebTransport out, per session: one server-initiated unidirectional stream. Repeated framing of [len:u16BE][bytes×len]. Reading client must parse this framing.

Build

go build -o wt_bridge .                                       # native (linux/amd64)
GOOS=linux GOARCH=arm64 go build -o wt_bridge_arm64 .         # cross to Pi etc.

No cgo, no architecture-specific code. Single static binary; ~10 MB stripped.

Operational notes

  • Bump net.core.rmem_max to ~8 MiB on the bridge host so SO_RCVBUF isn't kernel-clamped (quic-go logs this if it can't apply the requested size). The relay is intended to share a host with the producer or sit on a small LAN node — host-level tuning is fine.
  • The bridge listens on QUIC + HTTP/3; CORS is wide open in dev mode. Tighten CheckOrigin and use a real CA-issued cert for any non-LAN deployment.