Answer relayed WebRTC offers from the gateway's own go2rtc
Adds src/uplink/webrtc.rs: on a webrtc_offer from the cloud, POST the offer to go2rtc on 127.0.0.1 and send the answer back as webrtc_answer, or the reason as webrtc_error. No credential is involved, because the request goes to loopback and go2rtc exempts loopback from its API password. That is measured, and it is also why a probe from the gateway cannot be used to check the password is working - test from another host for that. Spawned rather than awaited in the select! loop: go2rtc gathers ICE candidates before answering, which includes a STUN round trip, so awaiting inline would stall telemetry and heartbeats for a whole farm while one person opened one camera. It rides the outbound queue added earlier for exactly this kind of out-of-band reply. The bind address is turned into a loopback URL by taking its port and refusing anything it cannot read a port out of - guessing 1984 would produce a gateway that silently signals to the wrong place when someone has moved it. Stream names are validated rather than percent-encoded, since a real one is a printer id and anything else is a bug or an attempt at one. camera.rs now states why there are no ice_servers and no TURN credentials on this side: the viewer's browser is pinned to relay-only, so it allocates the relay and go2rtc just sends to it. Only one side of a session needs one. That asymmetry is load-bearing, not tidy - go2rtc reads its config once at startup, so rotating short-lived credentials here would mean restarting it and dropping every viewer on a timer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+20
-8
@@ -256,15 +256,27 @@ pub fn render_config(api_listen: &str, sources: &[CameraSource], auth: &ApiAuth)
|
||||
// listener above. An empty `listen` is how go2rtc is told not to bind.
|
||||
out.push_str("rtsp:\n listen: \"\"\n");
|
||||
|
||||
// `webrtc:` is deliberately left alone, so go2rtc keeps its default
|
||||
// media port. It is not a second open stream server - a WebRTC session
|
||||
// only exists after an SDP offer through the API - and the browser needs
|
||||
// those candidates to receive anything.
|
||||
// `webrtc:` is deliberately left alone, and with TURN in the picture that
|
||||
// is now a decision worth stating rather than an omission.
|
||||
//
|
||||
// The API listener itself is unauthenticated, which is a real limit
|
||||
// rather than an oversight: the browser reaches it directly, there is no
|
||||
// relay, and the frontend is handed only a URL and a stream name. So
|
||||
// anyone who can reach the gateway's LAN can watch. See PLAN.md.
|
||||
// go2rtc keeps its default media port and its default candidate
|
||||
// gathering, which produces a host candidate and - measured against
|
||||
// 1.9.14 - a server-reflexive one it discovers by STUN on its own. That
|
||||
// is everything this end needs. No `ice_servers` here, and in particular
|
||||
// **no TURN credentials on the gateway**: the viewer's browser is pinned
|
||||
// to relay-only, so the browser allocates the relay and go2rtc simply
|
||||
// sends to it from its own candidate. Only one side of a WebRTC session
|
||||
// has to hold a relay for the media to traverse one.
|
||||
//
|
||||
// That asymmetry is what makes this tractable at all, because go2rtc
|
||||
// reads its config exactly once at startup: TURN credentials are
|
||||
// short-lived, so rotating them here would mean restarting go2rtc and
|
||||
// dropping every viewer on a timer. Keeping them browser-side moves the
|
||||
// rotation to a request that is already per-viewer.
|
||||
//
|
||||
// This is not a second open stream server either way: a WebRTC session
|
||||
// exists only after an SDP offer through the API, and that API now
|
||||
// requires a password.
|
||||
out
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user