Skip to main content

Chapter 10: WebRTC over SIP (WSS)

A Sofia profile can accept SIP signaling carried over WebSocket (WS) and Secure WebSocket (WSS), enabling browser-based endpoints to register and place calls using standard SIP without a proprietary signaling protocol. This chapter covers the binding parameters, TLS certificate material, and the media configuration that browsers require for interoperability.

SIP over WebSocket vs. Verto

FreeSWITCH supports two distinct mechanisms for browser-based real-time communication: SIP transported over WebSocket (RFC 7118) via mod_sofia, and the proprietary Verto protocol via mod_verto. Both approaches serve browser clients; the difference lies in signaling.

SIP over WSS uses standard SIP messaging encapsulated inside a WebSocket connection. Any SIP client library that supports the WebSocket transport (such as JsSIP or SIP.js) can register and call against a Sofia profile without any FreeSWITCH-specific client code. The trade-off is that SIP-over-WebSocket carries the full complexity of SIP negotiation into the browser.

Verto (documented in Chapter 9: WebRTC with Verto) is a JSON-RPC protocol designed and implemented by the FreeSWITCH project. It is simpler for client developers but requires the mod_verto FreeSWITCH module and a Verto-aware JavaScript client.

If your browser client uses a standard SIP library, configure ws-binding or wss-binding on a Sofia profile as described in this chapter. If your browser client uses verto.js, refer to Chapter 9: WebRTC with Verto instead.


Enabling ws-binding and wss-binding

The ws-binding and wss-binding parameters are set inside the <settings> block of a Sofia profile. Each parameter accepts a value in the form [ip]:port. When the IP portion is omitted (e.g., :5066), the profile binds on all interfaces using the IP already configured for the profile.

The vanilla internal profile ships with both parameters present:

<!-- for sip over websocket support -->
<param name="ws-binding" value=":5066"/>

<!-- for sip over secure websocket support -->
<!-- You need wss.pem in $${certs_dir} for wss or one will be created for you -->
<param name="wss-binding" value=":7443"/>

Setting ws-binding adds an unencrypted WebSocket listener. Setting wss-binding adds a TLS-encrypted WebSocket listener. Both can be active simultaneously on the same profile. The profile continues to listen on its standard SIP UDP/TCP port; the WebSocket listeners are additive.

To bind to a specific interface rather than all interfaces:

<param name="ws-binding" value="192.0.2.10:5066"/>
<param name="wss-binding" value="192.0.2.10:7443"/>

TLS Certificate Material

wss-binding requires a TLS certificate. FreeSWITCH looks for certificate files in the directory specified by tls-cert-dir (defaults to the global certs_dir, typically /etc/freeswitch/tls).

Certificate file lookup order for WSS

FreeSWITCH first looks for the separate key and certificate files:

  1. wss.key (private key)
  2. wss.crt (certificate)

If either file is absent, FreeSWITCH falls back to a single combined PEM file:

  1. wss.pem (key + certificate + optional chain, concatenated)

If wss.pem is also absent, FreeSWITCH generates a self-signed certificate and writes it to wss.pem. Self-signed certificates will cause browser security warnings; browsers will refuse to connect to a WSS endpoint secured by an untrusted certificate unless the user manually trusts it. For production use, supply a certificate signed by a trusted CA.

/etc/freeswitch/tls/
wss.pem # combined key + cert (or use wss.key + wss.crt)

Or, using separate files:

/etc/freeswitch/tls/
wss.key # RSA or ECDSA private key, PEM format
wss.crt # signed certificate (and optional chain), PEM format

Relationship to SIP TLS certificate files

The SIP TLS transport (tls parameter) uses agent.pem (key + cert) and cafile.pem (CA bundle). These are distinct from the WSS certificate files (wss.pem or wss.key/wss.crt). A profile can operate SIP TLS and WSS with independent certificates.


TLS Parameters

The following parameters govern TLS behavior. They apply to the SIP TLS transport and, for tls-cert-dir, also determine where WSS certificate files are loaded from.

<!-- TLS for SIP (not required for WSS alone, but tls-cert-dir affects both) -->
<param name="tls" value="false"/>
<param name="tls-only" value="false"/>
<param name="tls-bind-params" value="transport=tls"/>
<param name="tls-sip-port" value="5061"/>
<param name="tls-cert-dir" value="/etc/freeswitch/tls"/>
<param name="tls-passphrase" value=""/>
<param name="tls-version" value="tlsv1,tlsv1.1,tlsv1.2,tlsv1.3"/>
<param name="tls-ciphers" value="ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"/>
<param name="tls-verify-date" value="true"/>
<param name="tls-verify-policy" value="none"/>
<param name="tls-verify-depth" value="2"/>
<param name="tls-verify-in-subjects" value=""/>
<param name="tls-timeout" value="300"/>

The tls-cert-dir value in the example above shows the conventional path; the compiled-in default is the global certs_dir determined at build time. The tls-timeout value of 300 seconds is the compiled-in default for outgoing TLS connection attempts; set to 0 to disable the timeout. The tls-version default includes all four TLS versions (tlsv1 through tlsv1.3); restrict this list to tlsv1.2,tlsv1.3 to drop support for deprecated protocol versions.


Media: SRTP, DTLS, and ICE

Browser SIP clients require encrypted media and ICE-based NAT traversal. This section covers how DTLS-SRTP key exchange works, how FreeSWITCH participates as an ICE peer, and how to configure the server-side candidate addresses. SRTP via SDES (inline SDP key exchange) is also supported but is not used by WebRTC browsers; browsers always use DTLS-SRTP.


DTLS-SRTP

WebRTC mandates DTLS-SRTP for media encryption. No profile parameter enables or disables it; FreeSWITCH activates DTLS-SRTP automatically when a browser offer contains an a=fingerprint attribute in the SDP.

How fingerprint exchange works

When FreeSWITCH receives an SDP offer containing a=fingerprint, the core media layer:

  1. Records the remote fingerprint (hash algorithm and hex digest) from the offer.
  2. Generates a local fingerprint from its own DTLS certificate (dtls-srtp.pem in $${certs_dir}). If that file does not exist, FreeSWITCH generates a self-signed certificate and saves it there.
  3. Includes a=fingerprint and a=setup in its SDP answer.

FreeSWITCH uses SHA-256 as the default fingerprint hash algorithm. If the remote side advertises a different hash type in its a=fingerprint attribute, FreeSWITCH mirrors that type for its local fingerprint.

The a=setup role negotiation

The a=setup attribute determines which side acts as DTLS client (initiates the handshake) and which acts as DTLS server (waits for it).

  • When FreeSWITCH sends the SDP offer, it uses a=setup:actpass, leaving the choice to the browser.
  • When FreeSWITCH sends the SDP answer and the browser offered passive or actpass, FreeSWITCH takes the DTLS client role (a=setup:active). If the browser offered active, FreeSWITCH becomes the DTLS server (a=setup:passive).

This behavior is automatic. No profile parameter controls the DTLS role.

DTLS certificate file

FreeSWITCH looks for its DTLS certificate in $${certs_dir}/dtls-srtp.pem. If that combined PEM file is absent, it falls back to dtls-srtp.key (private key) and dtls-srtp.crt (certificate) in the same directory. If none of these files exist, FreeSWITCH generates a self-signed certificate and writes it to dtls-srtp.pem at startup.

/etc/freeswitch/tls/
dtls-srtp.pem # combined key + cert (auto-generated if absent)

The DTLS certificate is independent of the WSS TLS certificate (wss.pem). Each serves a different layer: wss.pem secures the WebSocket signaling channel; dtls-srtp.pem secures the SRTP media channel.

The rtp_secure_media channel variable

For SDES-SRTP (non-DTLS key exchange), the rtp_secure_media channel variable controls FreeSWITCH behavior. Set it in the dialplan or user directory:

<action application="set" data="rtp_secure_media=mandatory"/>
ValueBehavior
true or mandatoryRequire SRTP; reject calls that do not offer crypto
optionalOffer SRTP but accept calls that do not
false or forbiddenDisable SRTP entirely

The variable also accepts a colon-suffix to restrict which SRTP cipher suites are offered, for example mandatory:AES_CM_128_HMAC_SHA1_80. The suite list is colon-delimited. The rtp_secure_media_inbound and rtp_secure_media_outbound variants apply the setting directionally; the bare rtp_secure_media variable applies to both directions when the directional variables are absent.

For WebRTC browser calls using DTLS-SRTP, rtp_secure_media does not need to be set. FreeSWITCH enables DTLS-SRTP automatically based on the presence of a=fingerprint in the browser's SDP.


ICE

FreeSWITCH participates in ICE (Interactive Connectivity Establishment) as a full ICE agent by default. It generates ICE credentials (a=ice-ufrag and a=ice-pwd) using random strings at session setup time and includes them in SDP. The ufrag is 16 characters; the password is 24 characters.

Candidate types FreeSWITCH advertises

FreeSWITCH advertises up to two candidate types per media component in its SDP:

  1. A host candidate using the address bound to rtp-ip.
  2. A srflx (server-reflexive) candidate using ext-rtp-ip as the address, with rtp-ip as the raddr (reflexive base address), when ext-rtp-ip is set and differs from rtp-ip.

FreeSWITCH does not generate relay candidates in its SDP. It does, however, accept relay candidates from the browser's SDP during candidate selection.

ICE candidate filtering with apply-candidate-acl

When processing a browser's SDP offer, FreeSWITCH evaluates each a=candidate line against the ACLs configured in apply-candidate-acl. A candidate is accepted if its IP address matches any listed ACL. If no apply-candidate-acl is configured, FreeSWITCH defaults to wan.auto and logs a warning.

<param name="apply-candidate-acl" value="localnet.auto"/>
<param name="apply-candidate-acl" value="wan_v4.auto"/>
<param name="apply-candidate-acl" value="rfc1918.auto"/>
<param name="apply-candidate-acl" value="any_v4.auto"/>

The parameter is repeatable. Candidates that do not match any ACL are silently discarded. When all non-relay candidates are discarded, FreeSWITCH falls back to evaluating relay candidates from the browser's SDP.

FreeSWITCH only processes UDP ICE candidates. TCP candidates in the browser's SDP are ignored.

The rtp-ip and ext-rtp-ip parameters

ParameterRole in ICE
rtp-ipLocal address FreeSWITCH binds its RTP sockets to. Becomes the host candidate address and the raddr of any srflx candidate.
ext-rtp-ipPublic address FreeSWITCH advertises. When set and different from rtp-ip, an additional srflx candidate is emitted with this as the candidate address. Also used as the c= connection address in SDP sent to browsers.
<param name="rtp-ip" value="$${local_ip_v4}"/>
<param name="ext-rtp-ip" value="$${external_rtp_ip}"/>

The rtp-ip parameter is repeatable. Values containing : are treated as IPv6 addresses and stored in a separate address pool from IPv4 values.

ICE mode: full agent vs. ICE-lite

By default FreeSWITCH operates as a full ICE agent. Setting the channel variable ice_lite to true switches it to ICE-lite mode, which causes it to take the controlled role and skip connectivity checks. ICE-lite is not the default; most browser interoperability is achieved in full ICE mode.


STUN and TURN

FreeSWITCH and STUN

FreeSWITCH uses STUN only for one purpose: resolving its own public IP address at profile load time when ext-rtp-ip is set to a stun: URI.

<param name="ext-rtp-ip" value="stun:stun.example.com"/>

When a stun: value is configured, FreeSWITCH performs a STUN binding request to the named server at startup (with up to 5 retries) and stores the returned public IP as ext-rtp-ip. After that, FreeSWITCH does not communicate with the STUN server again during calls. The resolved address is used to populate the srflx candidate and the SDP connection address.

A custom port may be included in the URI: stun:stun.example.com:3478.

Accepted ext-rtp-ip value forms:

FormBehavior
Literal IP, e.g., 203.0.113.10Used directly, no lookup
stun:host or stun:host:portSTUN binding request at profile load time
host:hostnameDNS A record lookup at profile load time
autoFreeSWITCH guesses from the system routing table
auto-natResolved from NAT-PMP or UPnP at profile load time

TURN relays

FreeSWITCH does not use TURN relays itself. FreeSWITCH has no TURN client configuration and does not request or generate relay candidates.

TURN relays are configured on the browser client, not on FreeSWITCH. The browser discovers a relay address via a TURN server and advertises it as a relay candidate in its SDP. FreeSWITCH receives that candidate, validates it against apply-candidate-acl, and uses it as the remote media destination if it is the best available candidate. From FreeSWITCH's perspective it is simply sending RTP to an IP and port; the relay is transparent.


NAT Traversal: Server-Side Parameters

The consolidated server-side NAT setup, including ext-sip-ip, ext-rtp-ip, and auto-nat, is documented in detail in the Media Handling chapter's NAT Traversal section. The configuration in this chapter refers to the same ext-rtp-ip parameter. For a full treatment of how these parameters interact with NAT-PMP, UPnP, and the Sofia profile's SIP signaling address, refer to that section rather than duplicating it here.


Browser Transport Requirement

Browsers served over HTTPS refuse to open unencrypted WebSocket connections (ws://) due to mixed-content policy. In practice this means:

  • A browser page loaded over HTTPS must use wss-binding (WSS, port 7443 by default).
  • A browser page loaded over plain HTTP may use ws-binding (WS, port 5066 by default), but HTTP is not used in production.

Always configure wss-binding and provide a CA-trusted certificate when deploying for browser clients. The plain ws-binding is useful only for local development or testing.


Parameter Reference

ParameterPurposeAccepted ValuesDefault
ws-bindingIP and port for the plain WebSocket (WS) SIP listener[ip]:port string, e.g., :5066None (disabled)
wss-bindingIP and port for the secure WebSocket (WSS) SIP listener[ip]:port string, e.g., :7443None (disabled)
tlsEnable SIP over TLS transporttrue, falsefalse
tls-onlyBind only on the TLS port; suppress the plain SIP UDP/TCP porttrue, falsefalse
tls-bind-paramsAdditional transport parameters appended to the TLS bind URLString; must include transport=tls (auto-appended if absent)transport=tls
tls-sip-portPort for the SIP TLS listenerPort number or auto5061
tls-cert-dirDirectory containing TLS and WSS certificate filesAbsolute filesystem pathGlobal certs_dir
tls-passphrasePassphrase for OpenSSL to decrypt an encrypted private keyStringEmpty (no passphrase)
tls-versionComma-separated list of TLS/SSL protocol versions to enabletlsv1, tlsv1.1, tlsv1.2, tlsv1.3, sslv2, sslv3, sslv23tlsv1,tlsv1.1,tlsv1.2,tlsv1.3
tls-ciphersOpenSSL cipher string for TLS connectionsOpenSSL cipher list stringALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH
tls-verify-dateReject certificates whose validity period has expiredtrue, falsetrue
tls-verify-policyCertificate verification policy for TLS peersnone, in, out, all, subjects_in, subjects_out, subjects_all; pipe-delimited combinations allowed, e.g., subjects_in|subjects_outnone
tls-verify-depthMaximum certificate chain depth when verifying peer certificatesInteger2
tls-verify-in-subjectsAllowed certificate subjects for inbound TLS connections (requires tls-verify-policy to include a subjects_in or subjects_all value)Pipe-delimited subject strings, e.g., example.com|*.example.comEmpty
tls-timeoutTimeout in seconds for outgoing TLS connection attempts; 0 disablesInteger300