Expand description
A Selective Forwarding Unit (SFU) for audio/video calls.
A SFU receives each participant’s media once and selectively forwards it to
the others, so an N-party call costs one upload per sender rather than one
per listener and no stream is transcoded or mixed. o-sfu runs this model as
a dedicated server that handles room admission, routing topology, media
policy, packet forwarding, signaling and telemetry. Applications provision
rooms over HTTP, browsers connect over WebSocket and media travels over UDP
with str0m terminating ICE, DTLS and SRTP.
§Core Concepts
o-sfu separates the control plane for admission and room policy, the
routing plane for user-to-connection placement and the packet plane
for RTP forwarding on worker loops.
Runtime: Owns the process lifecycle, the HTTP/WebSocket servers and graceful shutdown.core::server::room::Room: The control plane boundary for a set of participants. It commits membership and media relationships.o_sfu_router::Router: The routing plane, a pure, sans-I/O engine owning the placement graph that maps users to connections.core::prelude::MediaSession: Orchestrates a user’s connection, bridging room intent to transport effects.core::server::transport::MediaTransport: Owns the media workers and hides their threading model. Each worker holds a packet loop and applies projected routes to incoming datagrams.
§Architecture
Control and routing decisions happen above the packet loops, which apply the resulting transport state to UDP datagrams.
HTTP control API ----------------------> RoomManager
|
WebSocket session -> SfuCore -> MediaSession |
\ /
+----> Room <----+
|
+<----> Router
|
v
MediaTransport
| | |
v v v
RTC workers / packet loops
| | |
UDP:40001 UDP:40002 UDP:40003
| | |
v v v
fanout fanout fanout§Admission Edge
Applications provision rooms through
RoomManager::serve_room.
WebSocket clients join through
SfuCore::admit_user. Both paths require
JWT authentication before admission.
+----------------------------------------+
| Incoming HTTP / WebSocket I/O |
+----------------------------------------+
|
v
+----------------------------------------+
| Axum Router |
+----------------------------------------+
|
+--------------------+-----------+-----------+--------------------+
| | | |
v v v v
[ GET /v1/noop ] [ GET /v1/channel ] [ WebSocket / ] [ Operator Routes ] <-- Routes
(Public Liveness) [ POST /v1/disconnect ] (Signaling Path) - GET /v1/stats
| | | - GET /metrics
| | | - GET /internal/...
| v v |
| +---------------+ +---------------+ +---------------+
| | VerifiedRoom | | Upgrade | | OperatorAccess| <-- Extractors
| | VerifiedClaims| | ConnectInfo | | (Route Layer) |
| | (JWT Header) | | 1st-Frame JWT | | (Bearer/Local)|
| +---------------+ +---------------+ +---------------+
| | | |
v v v v
+---------------+ +---------------+ +---------------+ +---------------+
| noop | | room | | upgrade | | stats | <-- Handlers
| | | disconnect | | -> admit_user | | metrics |
| -> 200 JSON | | -> Room State | | -> WS Session | | diagnostics_* |
+---------------+ +---------------+ +---------------+ +---------------+- HTTP: Parses server-to-server requests using
http::CreateRoomQuery. Verifiesauth::HttpRoomClaims. The request that creates the current room fixes its signing key. - WebSocket: Client connection frames are decoded by
websocket::decode_auth_payload_text. A hint selects a candidate key. Claims are verified, normalized intoauth::WebSocketConnectClaimsand trusted for access.
§Security Model
o-sfu secures two planes independently. Application-layer JWTs gate room
admission on the control plane. str0m encrypts media on the packet plane
with DTLS-SRTP. Signaling transport confidentiality is terminated at the
deployment edge rather than in process.
control plane JWT HS256 admission trust
packet plane DTLS-SRTP (str0m) media confidentiality
signaling wire TLS at edge transport confidentiality§JWT Admission
Tokens are HS256 only. auth::verify rejects any other alg, checks the
HMAC in constant time and enforces exp, nbf plus an iat future-skew
bound. It caps token size at auth::MAX_JWT_TOKEN_BYTES. Two keys scope
trust:
- Server-to-server key:
AUTH_KEY(base64, at least 32 bytes) verifies the HTTPhttp::CreateRoomQuerypath throughauth::HttpRoomClaimsandauth::HttpDisconnectClaims. Seeconfig. - Per-room key: the request that creates the current room pins the signing
key from the
keyorkeySeedclaim inauth::HttpRoomClaims. For more security, prefer thekeySeedclaim, which derives a per-room key with theAUTH_KEYand provided seed using the following KDF:WebSocketroom_key = Base64StdPad(HMAC-SHA256( key = Base64Decode(AUTH_KEY), message = Base64Decode(keySeed) ))auth::WebSocketConnectClaimsverify against that room key, never againstAUTH_KEY.
Token carriage differs per surface: HTTP room creation uses the
Authorization header, HTTP disconnect uses the request body and the
WebSocket client sends a first-frame auth envelope decoded by
websocket::decode_auth_payload_text. An unverified room id selects only a
candidate key, then the same token is re-verified against it. Modern
auth::WebSocketConnectClaims must name the selected room. Legacy Odoo
tokens select it through the auth envelope’s channel and are normalized
only after verification with that room’s key.
Admission establishes identity and room scope. It does not enforce the
per-user permissions claim, which room state collapses to a marker.
§Signaling Ingress
Every authenticated client frame passes through the same decoder as the auth
frame, websocket::decode_client_batch, which bounds parser work with
static caps: websocket::MAX_CLIENT_FRAME_BYTES per frame and
websocket::MAX_CLIENT_BATCH_ENVELOPES per batch. The Axum upgrade applies
the frame cap at the socket and the decoder re-checks it. Oversized frames,
oversized batches, malformed JSON, ambiguous routing metadata and unknown
protocol tags reject as websocket::ClientBatchDecodeError and close the
socket with a protocol-error code.
Two further bounds guard against resource exhaustion:
- Pre-auth admission: global and per-origin permits cap concurrent
unauthenticated sockets and return
503once exhausted. Seeconfig. - Outbound backpressure: per-user fanout is a bounded queue by message count and by bytes. A consumer that falls behind is closed rather than buffered without limit.
A first-frame auth timeout rejects clients that never authenticate. A ping/pong health loop closes clients that stop responding. There is no per-session request-rate budget: the size, count and backpressure caps bound the work rather than metering a rate.
§Media Transport
str0m terminates ICE, DTLS and SRTP over UDP with the aws-lc-rs crypto
backend. o-sfu builds and drives the str0m session but implements no DTLS
or SRTP itself: it forwards already-decrypted RTP between sessions and hands
outbound RTP back to str0m for SRTP protection.
- Keying: the DTLS handshake derives SRTP keys per RFC 5764 DTLS-SRTP.
- Certificate:
str0mgenerates a self-signed certificate when each RTC session is built and advertises its SHA-256 fingerprint in the SDP offer. Accepting the answer stores the expected remote fingerprint. The DTLS handshake verifies it against the peer certificate. - ICE:
o-sfuruns ICE-lite witha=setup:actpassand advertisesANNOUNCED_IP, so media UDP must reach the host directly.
§Signaling Transport
HTTP and WebSocket are served in plaintext in process. HTTPS and WSS are
terminated by an external reverse proxy, so a forwarded scheme and client
address are trusted only when the proxy is trusted through config
(PROXY). Operator route access is documented by http.
§Room and Router Ownership
Room transitions produce typed commits while holding short exclusive state
locks. RoomEffects consumes deferred transport, source-policy and WebSocket
output work after the lock is released.
room state lock held lock released
+--------------------------------+ +------------------------------+
| validate user and connection | | MediaTransport commands |
| commit room topology | | source-policy turn |
| capture transition commit |--->| websocket output |
| | | idempotent teardown |
+--------------------------------+ +------------------------------+o_sfu_router::Router owns exact user-to-connection placement. Receiver shadows are foreign local sessions derived from active consumer dependencies, disappearing with their final consumer.
§Signaling and Client Bundle
Browsers use SfuClient for connection, publication, subscription and room
control. Signaling state stays in o_sfu_protocol::host::ProtocolCore and
yields ordered o_sfu_protocol::host::Command values. The WASM bridge
serializes those commands then maps protocol events to Odoo bundle updates
that drive browser WebSocket, RTCPeerConnection and timer APIs.
SfuClient (public API)
|
v
BrowserRuntime
|
v
ProtocolCore (sans-I/O) -> Vec<Command>
|
v
WASM serialization -> TypeScript command union
|
v
BrowserRuntime -> WebSocket, RTCPeerConnection, timers
^ |
+--------------- browser events ---------+§Packet Path
core::server::transport::MediaTransport owns the media workers, which hold the packet loops. These loops receive UDP datagrams, drive WebRTC state (str0m), apply route tables and forward RTP.
UDP datagram
|
v
worker ingress and `str0m` drain
|
v
packet facts (source, RID and codec)
|
+-> origin packet sinks
|
+-> source and destination packet gates
|
+-> relay fanout
+-> local RTC -> RTP identity and codec rewritestr0m handles ICE, DTLS and SRTP. The private rtc::codec boundary keeps
codec branching out of route planning. Origin packet sinks precede route
gates so recording can observe a publisher without active receivers. Source,
relay and receiver gates then narrow routed fanout. Same-process relays share
payload data with another worker for local delivery.
Worker BWE and audio observations feed into room source policy, which updates route gates for later packets.
worker BWE and audio observations
|
v
room source policy
|
v
route gates for later packets§Shutdown and Teardown
Teardown is explicit async work. Runtime::serve_listener stops listener acceptance, drains tracked web sockets and stops background tasks within config::HttpConfig::shutdown_timeout_ms.
Runtime::serve_listener
|
+-> stop listener
+-> close tracker and cancel sessions
+-> wait for tracker emptiness
+-> stop source-policy sync and media workersMissing worker-local sessions or media during teardown are successful no-ops. Unavailable workers or ownership mismatches are terminal.
§Observability
Monitored through the o_sfu_telemetry sub-crate. See http::telemetry for the HTTP contracts.
- Metrics:
http::telemetry::metricsexposes Prometheus text exposition. - Diagnostics:
http::telemetry::diagnosticsexposes JSON state summaries.
§Scaling
Rooms use one o_sfu_router::Router facade and can opt into additional same-process local routers through config::RoomWorkerPolicy.
Joins stay on an assigned healthy packet loop. When no assigned worker has a
known delay below the configured threshold, a join can attach a healthy
worker not yet assigned to the room.
§Feature Flags
Core media behavior is configured at runtime through config, not Cargo features.
The default feature otel-tracing enables OpenTelemetry tracing support through o_sfu_telemetry::TraceExportConfig. Other features are used strictly for tests and benchmarking.
§Sub-crates
| Crate | Role |
|---|---|
o_sfu_rfc | RFC-backed JWT, RTP, RTCP, SDP and WebRTC consts/types |
o-sfu-model | Shared call data (o_sfu_protocol::wire::UserId, etc.) |
o_sfu_router | Sans-I/O o_sfu_router::Router facade for room placement and routed media lifetimes |
o_sfu_core | Room engine, core::prelude::SourcePolicy, recording taps and core::server::transport::MediaTransport projection |
o_sfu_protocol | Sans-I/O o_sfu_protocol::host::ProtocolCore and typed commands |
o_sfu_telemetry | Tracing setup, metrics, diagnostics response types and graph payloads |
§Reading Map
runandRuntimeown boot, serving, background tasks and shutdown.configis the environment-to-runtime boundary.auth,httpandwebsocketform the admission edge.crate::coreturns accepted control-plane intent into room mutations and transport effects.o_sfu_protocol::host::ProtocolCorekeeps browser signaling sans-I/O.core::server::transport::MediaTransportowns the media workers and hides their threading model.
Modules§
- application 🔒
- auth
- config
- core
- http
- HTTP route and payload contracts.
- runtime 🔒
- Wires process services and drains them during shutdown.
- websocket
Structs§
- Runtime
- Process services and lifecycle configuration.
Enums§
- Serve
Error - Failure to serve or fully drain a
Runtime.
Functions§
- run
- Errors