Skip to content
Germán Luis Aracil Boned edited this page Jun 27, 2026 · 10 revisions

WebRTC

GABPBX 1.1.2 makes chan_sofia a WebRTC endpoint. A browser running a SIP-over-WebSocket library (for example SIP.js or JsSIP) can register, place a call and receive a call, with two-way audio, two-way video, and relayed DataChannels, secured by DTLS-SRTP. The implementation is self-contained: there is no pjproject and no libnice in the tree. The same forked RTP engine that carries plain SIP calls carries WebRTC, so non-WebRTC media is unchanged.

This page covers how it fits together and how to turn it on. The per-feature catalogue is Features; the security model is Security.

What works

  • Two WSS browsers can hold a real two-way audio call end to end.
  • Two-way video (VP8 or H.264) rides on top of the audio call.
  • Opus passes through both-Opus calls without transcoding.
  • Two browsers can open a DataChannel and exchange text or binary messages relayed back-to-back through GABPBX.
  • Setup, teardown and NAT survival all route correctly back over the accepted WebSocket.

The standards in play

Concern Mechanism RFC Where
SIP transport SIP over secure WebSocket RFC 7118 channels/chan_sofia.c:11568-11660
Media keying DTLS-SRTP RFC 5763/5764 res/res_rtp_gabpbx.c:1001
Connectivity ICE-lite STUN responder RFC 8445 res/res_rtp_gabpbx.c:1265
One socket per stream rtcp-mux RFC 5761 channels/sofia/sofia_sdp.c:268
Multiplexed media BUNDLE + a=mid RFC 8843 channels/sofia/sofia_sdp.c:359
Packet demux STUN / DTLS / SRTP by first byte RFC 7983 res/res_rtp_gabpbx.c:1732
Audio codec Opus RFC 7587 channels/sofia/sofia_sdp.c:172
DataChannel transport SCTP over DTLS RFC 8831 channels/sofia/sofia_datachannel.c
DataChannel setup DCEP OPEN/ACK RFC 8832 channels/sofia/sofia_datachannel.c
DataChannel SDP m=application UDP/DTLS/SCTP RFC 8841 channels/sofia/sofia_sdp.c

How a WebRTC leg is built

1. SIP over WSS

The browser registers and signals over a secure WebSocket. chan_sofia opens a dedicated WSS listener from wssbindport. sofia-sip looks for fixed certificate filenames inside a cert directory (agent.pem/cafile.pem for TLS, wss.pem/ca-bundle.crt for WSS), so chan_sofia bridges the operator's real files to those names automatically:

  • tlscertfile may be the cert directory or the real cert+key file. When it names a file, the directory is derived from it and agent.pem is soft-linked to that file — so you can point straight at an externally managed certificate without renaming it. tlscafile is likewise soft-linked to cafile.pem.
  • The WSS-named files are then soft-linked from the TLS ones (wss.pem from agent.pem, ca-bundle.crt from cafile.pem). All links are created only when missing and never overwrite existing files.

Certificate immunity: if a configured TLS or WSS listener has no usable certificate, chan_sofia logs an error and disables only that listener — UDP, TCP and WS keep serving and the module still loads, instead of the whole driver failing. tlsenable/wsenable/wssenable (default on) explicitly turn a listener off even when its bind port is set.

A browser's Contact host is always a placeholder, so in-dialog ACK/BYE are routed back over the exact accepted WebSocket connection rather than to the Contact (channels/chan_sofia.c:542-589), and ;transport=wss is appended to the request-URI (mandatory for WSS per RFC 7118).

2. The media proto

A WebRTC offer/answer uses the UDP/TLS/RTP/SAVPF media transport (channels/sofia/sofia_sdp.c:380) instead of plain RTP/AVP. The SDP carries a=ice-lite, ICE ufrag/pwd/candidate, a=fingerprint:sha-256, a=setup, a=rtcp-mux, a=mid, and a session-level a=group:BUNDLE.

3. ICE-lite

GABPBX is permanently the controlled, lite ICE agent (res/res_rtp_gabpbx.c:1265, role AST_RTP_ICE_ROLE_CONTROLLED). It does not gather candidates or send checks; it answers the browser's STUN binding requests, learns the peer transport address from the first authenticated check, and latches the nominated pair on USE-CANDIDATE. The STUN responder is armed in sofia_webrtc_provision_offer before the offer leaves the wire, so the browser's earliest connectivity checks are never dropped into a dead window.

4. DTLS-SRTP

Keys are not in the SDP. After ICE connectivity, a DTLS handshake runs over the media path; SRTP keys are exported from it. The cert is ephemeral, self-signed, EC P-256, CN gabpbx. a=setup advertises the engine's real negotiated role so the two sides never both try to be the DTLS client. A dedicated retransmit scheduler honors DTLS exponential backoff. gabpbx_dtls engine at res/res_rtp_gabpbx.c:1001; key install at :1015-1071.

The trust anchor is the SDP a=fingerprint (RFC 8827/5763): the self-signed cert cannot chain-verify, so on handshake completion the engine demands a peer cert, requires a stored remote fingerprint, and does a hash-matched X509_digest + memcmp against the SDP fingerprint. Any absence or mismatch fails the call closed as a possible MITM (res/res_rtp_gabpbx.c:1097-1120).

5. One socket, RFC 7983 demux

With rtcp-mux, a single socket per stream carries STUN, DTLS and (S)RTP/RTCP interleaved. __rtp_recvfrom demuxes by the first byte (res/res_rtp_gabpbx.c:1732-1786): STUN to the ICE responder, DTLS records to the handshake, RTP to SRTP-unprotect, with muxed SRTCP dropped and a flood guard. This path is dormant unless DTLS is active, so plain RTP is untouched.

6. BUNDLE and m-line handling

Audio is the tagged BUNDLE transport. A browser typically offers audio + video

  • datachannel; the datachannel m-line is accepted and bundled on the audio DTLS (see §9 below), while any genuinely unsupported m-line is reflected back as a port-0 rejection in order (RFC 3264 §6) rather than 488-ing the whole session (channels/sofia/sofia_sdp.c). This is what lets a browser's audio+video+datachannel offer be answered cleanly.

7. Video on its own transport

Video does not ride the audio BUNDLE. The accepted/offered m=video is emitted on its own port with its own ICE/fingerprint/setup/candidate, a=rtcp-mux, and a distinct a=mid — a separate DTLS association from the audio (channels/sofia/sofia_sdp.c:386-491). GABPBX both accepts a browser's offered video (answerer) and offers video itself (offerer), so two browsers can see each other.

8. Opus passthrough

A both-Opus call passes through with no transcoding. The three fixes that made browser-to-browser Opus clean:

  • codec selection now knows Opus,
  • the format rate returns 48 kHz, so the RTP TX timestamp advances +960 per 20 ms frame (not +160),
  • the RTP smoother no longer re-chunks Opus's variable-size frames (res/res_rtp_gabpbx.c:2840-2845).

Opus is offered with useinbandfec=1;usedtx=0 and channels=2 (channels/sofia/sofia_sdp.c:172-174).

9. DataChannel relay

GABPBX bridges WebRTC DataChannels back-to-back between two WebRTC legs. When a browser offers m=application UDP/DTLS/SCTP webrtc-datachannel, the channel is negotiated on the same BUNDLE'd DTLS transport as the audio (no separate association), and the SCTP association is carried by an optional usrsctp build.

  • Offer and answer, both ways. GABPBX accepts an inbound DataChannel offer and offers one to the bridged far leg, so a createDataChannel between two browsers reaches end to end. The far leg negotiates its own SCTP; GABPBX proxies the DCEP OPEN/ACK (RFC 8832) and relays each message preserving its PPID, so both text and binary channels cross.
  • One worker lane. The receive path runs on a single dedicated worker off the RTP instance lock; the relay reaches the far leg with a non-blocking channel trylock + reference and releases all channel locks before the SCTP send, so the two legs can never deadlock against each other.
  • Bounded. a=max-message-size is honored per RFC 8841 §6 (absent → the 64 KiB default; explicit 0 → unbounded, hard-capped); the negotiated stream count is capped to bound memory.
  • Clean teardown. When a leg ends, its SCTP socket is abortively closed (SO_LINGER 0) so no library timer outlives the channel.

It is build-gated by usrsctp (HAVE_USRSCTP) and opt-in per peer with datachannel=yes (inherited from [general], requires webrtc=yes). Without the usrsctp build, or with datachannel=no, an offered datachannel m-line is simply port-0 rejected and audio/video are unaffected. Implementation: channels/sofia/sofia_datachannel.c plus the engine DTLS-appdata seam in main/rtp_engine.c / res/res_rtp_gabpbx.c.

Turning it on

WebRTC is opt-in per peer with webrtc=yes, which is inherited from [general] and can be overridden per peer (channels/chan_sofia.c:2181, :2591, :13173).

[general]
; cert DIR, or the real cert+key FILE (agent.pem is soft-linked to it).
; WSS names (wss.pem / ca-bundle.crt) are auto-linked from the TLS ones.
tlscertfile=/etc/gabpbx/keys/gabpbx.pem
tlscafile=/etc/gabpbx/keys/ca.crt

; secure WebSocket listener for browsers
wssbindaddr=0.0.0.0
wssbindport=8089
; tlsenable / wsenable / wssenable default to yes; set to no to disable a
; listener even when its bind port is set (e.g. a node with no certificate).

; default new peers to WebRTC (or set per peer)
webrtc=yes
; relay browser-to-browser DataChannels (needs a usrsctp build)
datachannel=yes

[browser-200]
type=peer
host=dynamic
defaultuser=browser-200
secret=change-this-secret
context=internal
webrtc=yes
disallow=all
allow=opus
allow=ulaw

A webrtc=yes peer fails the call rather than ever falling back to insecure media, and direct media is force-disabled on any DTLS/SRTP leg so keys are never bridged or disclosed (channels/chan_sofia.c:3129-3134).

Operational notes

  • sip show peer <name> and sip show settings report the WebRTC state of a peer and the global default.
  • A reverse proxy (for example nginx) terminating browser HTTPS/WSS in front of chan_sofia is a common deployment; route the WebSocket to the wssbindport listener. When debugging the WSS leg, capture on the loopback the proxy forwards to.
  • After editing sofia.conf as root, restore ownership so GABPBX can read it (chown gabpbx:gabpbx /etc/gabpbx/sofia.conf).

Not in scope (yet)

  • Full (non-lite) ICE with candidate gathering and TURN — GABPBX is the ICE-lite controlled agent by design.

Clone this wiki locally