TURN over TCP and TLS
Tip
PJSUA-LIB readers — symbol equivalents are listed at the bottom of this page.
Overview
By default a TURN client reaches the TURN server over UDP and the allocated relay also runs over UDP (RFC 5766, updated by RFC 8656). On networks that block outbound UDP — restrictive corporate firewalls, some hotel/airport networks, certain mobile carriers — the TURN allocation never completes and TURN candidates are unavailable. The remedy is to reach the TURN server over TCP or TLS instead. Connection-oriented transports traverse most firewalls that block UDP outright, and TLS additionally encrypts the client-server leg.
PJSIP supports all three client-server transports via the
pj_turn_tp_type enum:
|
Client-to-server transport |
|---|---|
|
UDP datagrams (default; lowest latency) |
|
TCP connection |
|
TLS over TCP |
All three are part of base TURN (RFC 5766 / RFC 8656). The
same enum is reused for the allocation transport requested from
the server, but PJ_TURN_TP_TLS is only valid as a client-server
transport, never as an allocation transport — see Allocations vs
the data path below.
Client-server TCP and TLS support is long-standing in PJNATH (the TURN integration originally landed via ticket #485 and TURN TLS via #1017). #2754 is a separate, later addition that implements RFC 6062 TCP allocations, i.e. TCP between the TURN server and a peer — useful for application protocols that need a reliable byte stream end-to-end. SIP media does not use this.
Choosing a connection type
UDP — start here. Lowest latency, fewest moving parts. Use unless you have evidence UDP is being blocked.
TCP — fall back when UDP is dropped. Slightly higher latency (head-of-line blocking when the network is lossy) but reliable through almost every firewall.
TLS — use when network policy specifically requires encrypted traffic to leave the network (e.g. corporate compliance, DPI bypass), or when the TURN server only exposes a TLS listener on port 5349.
Some deployments configure two account-level TURN entries — one UDP primary, one TLS backup on a different port — and let the application decide which to use based on local probing. PJSIP itself does not do automatic UDP→TCP→TLS fallback; the application picks one.
PJSUA2 — TURN over UDP or TCP
Set pj::AccountNatConfig::turnConnType to one of
PJ_TURN_TP_UDP or PJ_TURN_TP_TCP:
AccountConfig acfg;
// ... existing config ...
acfg.natConfig.iceEnabled = true;
acfg.natConfig.turnEnabled = true;
acfg.natConfig.turnServer = "turn.example.com:3478";
acfg.natConfig.turnConnType = PJ_TURN_TP_TCP;
acfg.natConfig.turnUserName = "user";
acfg.natConfig.turnPassword = "secret";
try {
account.create(acfg);
} catch(Error& err) {
}
Warning
The PJSUA2 AccountNatConfig does not currently surface the TURN
TLS settings struct (pj_turn_sock_tls_cfg). The doxygen
on pj::AccountNatConfig::turnConnType lists only
PJ_TURN_TP_UDP and PJ_TURN_TP_TCP. Setting turnConnType
to PJ_TURN_TP_TLS in PJSUA2 will attempt a TLS connection, but
with the TLS configuration left at its zero-initialised default
the verification behaviour is whatever the SSL backend defaults
to — typically no CA verification, which is insecure. To configure
TURN TLS properly — CA file, client cert, ciphers — drop down to
the PJSUA-LIB pjsua_turn_config struct (next section).
PJSUA-LIB — TURN over UDP, TCP, or TLS
The PJSUA-LIB pjsua_turn_config struct exposes the full
TURN configuration including TLS:
pjsua_acc_config acc_cfg;
pjsua_acc_config_default(&acc_cfg);
acc_cfg.turn_cfg_use = PJSUA_TURN_CONFIG_USE_CUSTOM;
pjsua_turn_config *t = &acc_cfg.turn_cfg;
t->enable_turn = PJ_TRUE;
t->turn_server = pj_str("turn.example.com:5349");
t->turn_conn_type = PJ_TURN_TP_TLS;
t->turn_auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
t->turn_auth_cred.data.static_cred.username = pj_str("user");
t->turn_auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
t->turn_auth_cred.data.static_cred.data = pj_str("secret");
t->turn_auth_cred.data.static_cred.realm = pj_str("example.com");
/* TLS-specific bits — only applicable when turn_conn_type is TLS */
t->turn_tls_setting.ca_list_file = pj_str("/etc/ssl/certs/turn-ca.pem");
t->turn_tls_setting.cert_file = pj_str("/etc/pjsip/turn-client.pem");
t->turn_tls_setting.privkey_file = pj_str("/etc/pjsip/turn-client.key");
pjsua_acc_id acc_id;
pjsua_acc_add(&acc_cfg, PJ_TRUE, &acc_id);
For the global default (applied to accounts that keep
turn_cfg_use = PJSUA_TURN_CONFIG_USE_DEFAULT) the same fields
exist on pjsua_media_config directly (enable_turn,
turn_server, turn_conn_type, turn_auth_cred,
turn_tls_setting).
The pj_turn_sock_tls_cfg struct in
pjnath/include/pjnath/turn_sock.h includes additional
fields:
ca_buf/cert_buf/privkey_buf— for in-memory credentials instead of file paths (useful on platforms without a writable filesystem)cert_lookup/cert_direct— backend-specific options (Windows Schannel certificate store; OpenSSL direct credentials)password— passphrase for an encrypted private keyssock_param— fullpj_ssl_sock_param(protocols, ciphers, ECDH curves, signature algorithms, renegotiation, socket options, timeout). Default protocol isPJ_TURN_TLS_DEFAULT_PROTO(TLS 1.0 + 1.1 + 1.2).
pjsua CLI
The pjsua sample app exposes the full set:
$ ./pjsua --use-ice --use-turn \
--turn-srv turn.example.com:5349 \
--turn-tls \
--turn-tls-ca-file /etc/ssl/certs/turn-ca.pem \
--turn-tls-cert-file /etc/pjsip/turn-client.pem \
--turn-tls-privkey-file /etc/pjsip/turn-client.key \
--turn-user user --turn-passwd secret
Relevant flags:
--turn-srv NAME:PORT— TURN server (mandatory)--turn-tcp— use TCP connection to TURN--turn-tls— use TLS connection to TURN--turn-tls-ca-file— CA bundle for server verification--turn-tls-cert-file/--turn-tls-privkey-file/--turn-tls-privkey-pwd— client cert (when mutual TLS is required)--turn-tls-cipher— preferred cipher list--turn-tls-neg-timeout— TLS handshake timeout
Without --turn-tcp or --turn-tls the default is UDP.
Allocations vs the data path
TURN has two layers of transport:
Client-to-server (control + data tunnel) — set by
turn_conn_type. Carries TURN messages and (via channel data / Send indications) the media itself.Server-to-peer (the actual relayed media) — set by
pj_turn_alloc_param::peer_conn_type. UDP by default.
For typical SIP-over-ICE applications, leaving peer_conn_type at
its UDP default is correct — the peer reaches the relay over UDP
even if you reach the server over TCP/TLS. RFC 6062 TCP allocations
(where the peer-side transport is also TCP) require an extra
pj_turn_sock_connect() call per peer and are rarely useful
in SIP/SDP media; they are intended for application-specific
protocols that need reliable byte streams.
Note
PJSIP does not surface the TURN allocation’s
peer_conn_type through pjsua_turn_config —
PJSUA-LIB and PJSUA2 always allocate a UDP relay regardless of
turn_conn_type. Standalone PJNATH applications can set
pj_turn_alloc_param::peer_conn_type when needed.
Defaults and ports
Default UDP/TCP port — 3478 (IANA-registered)
Default TLS port — 5349 (IANA-registered)
PJ_TURN_KEEP_ALIVE_SEC— 15 s (TURN keep-alive interval)PJ_TURN_PERM_TIMEOUT— 300 s (permission timeout)PJ_TURN_CHANNEL_TIMEOUT— 600 s (channel binding lifetime)PJ_TURN_SSL_SOCK_DEFAULT_TIMEOUT— 10 s (TLS handshake)
These are compile-time settings, overridable in
pjlib/include/pj/config_site.h.
Interaction with ICE
The TURN connection type only affects how the relayed candidate is allocated. Once the allocation succeeds, the relayed candidate is just another entry in the ICE candidate list and pairs against the peer’s candidates by normal ICE rules. Whether ICE picks the relayed candidate or some other (cheaper) candidate depends on the connectivity-check outcome — TURN is the path of last resort.
Trickle ICE (Using Trickle ICE) and TURN TCP/TLS combine well — the TURN allocation handshake is the largest single source of pre-trickle ICE setup latency, so the speedup from trickling is most visible on calls that actually use the relayed candidate.
PJSUA-LIB equivalents
PJSUA2 |
PJSUA-LIB |
|---|---|
|
|
|
|
(not exposed) |