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:
@@ -47,6 +47,15 @@ pub enum ClientMessage {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
error: Option<String>,
|
||||
},
|
||||
/// go2rtc's answer SDP for a `WebrtcOffer`, passed through verbatim.
|
||||
WebrtcAnswer { session_id: String, sdp: String },
|
||||
/// No answer was obtainable: no such camera here, go2rtc not running, or
|
||||
/// it refused the offer.
|
||||
///
|
||||
/// Its own variant rather than an empty answer, because the cloud is
|
||||
/// waiting on this and a viewer otherwise watches a video element that
|
||||
/// never loads with nothing anywhere saying why.
|
||||
WebrtcError { session_id: String, message: String },
|
||||
}
|
||||
|
||||
/// A flattened snapshot of one printer, in the terms an operator console
|
||||
@@ -149,6 +158,21 @@ pub enum ServerMessage {
|
||||
/// Do something to one printer. `printer_id` is our own id from
|
||||
/// printers.toml.
|
||||
Command { command_id: String, printer_id: String, command: PrinterCommand },
|
||||
/// Relay a browser's WebRTC offer to this gateway's go2rtc and answer
|
||||
/// with the SDP it returns. **Signalling only - no video on this
|
||||
/// socket.**
|
||||
///
|
||||
/// Media goes browser-to-go2rtc directly over ICE, which is the whole
|
||||
/// reason only the signalling is relayed: on a workshop LAN the video
|
||||
/// never leaves the building, and it costs the cloud no bandwidth at
|
||||
/// all. go2rtc's answer carries a server-reflexive candidate alongside
|
||||
/// its host ones, so even a remote viewer can often reach the gateway
|
||||
/// without a relay.
|
||||
///
|
||||
/// The browser cannot do this part itself. go2rtc sends no CORS headers
|
||||
/// and checks its password before CORS, so the unauthenticated preflight
|
||||
/// a browser always sends is refused - measured, not assumed.
|
||||
WebrtcOffer { session_id: String, camera_stream: String, sdp: String },
|
||||
}
|
||||
|
||||
/// The closed set of things the cloud is allowed to ask a printer to do.
|
||||
|
||||
Reference in New Issue
Block a user