skip to content

In WebRTC, how do you list STUN and TURN servers in an RTCPeerConnection's iceServers, and why must a TURN entry carry a username and credential?

level: juniorimportance: must knowfreq 32%

answer

  1. urls plus two optional strings
  2. RFC 7064 and RFC 7065 schemes
  3. turns means TLS over TCP
  4. an Allocate spends relay capacity
  5. rejected before any packet leaves

basics

~20 s

Each RTCIceServer entry in RTCConfiguration.iceServers names a server by a stun:, turn: or turns: URI. A STUN entry needs nothing more; a TURN entry must also carry username and credential, because the TURN server authenticates every Allocate before it spends relay capacity.

solid answer

~40 s

`iceServers` is a list of `RTCIceServer` dictionaries, each with a required `urls` (one URI or several) and optional `username` and `credential`. The schemes come from RFC 7064 (`stun:`, `stuns:`) and RFC 7065 (`turn:`, `turns:`): a `turn:` URI may add `?transport=udp` or `?transport=tcp`, `turns:` means TLS over TCP, and the default ports are 3478, or 5349 for TLS. A public STUN server answering Binding requests normally runs without authentication, so a STUN entry has no credential. A TURN server reserves a relayed address and forwards traffic on the client's behalf, so RFC 8656 has it authenticate requests with STUN's long-term credential mechanism, and the W3C API throws `InvalidAccessError` when a `turn:` or `turns:` entry omits `username` or `credential`.

code

json · 15 lines
json
{
  "iceServers": [
    { "urls": "stun:stun.example.org" },
    {
      "urls": [
        "turn:turn.example.org?transport=udp",
        "turn:turn.example.org?transport=tcp",
        "turns:turn.example.org:443?transport=tcp"
      ],
      "username": "1759345200:agent-42",
      "credential": "kJ3pR9vT2xQ8mN4wL6zY1aB5cD0="
    }
  ],
  "iceTransportPolicy": "all"
}

go deeper

for a junior

Recall the three members of an RTCIceServer entry, the four URI schemes, and that only TURN entries carry a username and credential.

for a middle

Explain why TURN authenticates and STUN usually does not: an Allocate reserves relay capacity the provider pays for, while a Binding only reports an address.

for a senior

Show how a bad TURN credential hides: no exception, no relay candidate, and an icecandidateerror event the application must log to notice.

for a principal

Weigh offering several TURN transports in one entry against the extra connections each gathering opens, and decide which ones your users' networks justify.

## What `iceServers` is A WebRTC `RTCPeerConnection` contains an **ICE agent**: the component that collects possible network addresses (candidates) for this endpoint and tests them against the far peer's. Some candidates need help from servers on the public Internet, and the application names those servers in the `iceServers` member of the `RTCConfiguration` it passes to the constructor or to `setConfiguration()`. Each element is an `RTCIceServer` dictionary with three members in the W3C WebRTC 1.0 specification: - `urls` (required): one URI string or a list of them, all for the same server and credential. - `username`: the user name to present to a **TURN** server. - `credential`: the matching password, which the W3C text describes as a long-term authentication password. `iceServers` defaults to an empty list, and an implementation may ignore entries beyond its own limit, which must be at least 32. ## The URI schemes The URI syntax is not WebRTC's own: **RFC 7064** defines `stun:` and `stuns:`, and **RFC 7065** defines `turn:` and `turns:`. | URI | Server role | Client-to-server transport | Default port | |---|---|---|---| | `stun:stun.example.org` | STUN | UDP or TCP | 3478 | | `stuns:stun.example.org` | STUN | TLS over TCP | 5349 | | `turn:turn.example.org?transport=udp` | TURN | UDP | 3478 | | `turn:turn.example.org?transport=tcp` | TURN | TCP | 3478 | | `turns:turn.example.org?transport=tcp` | TURN | TLS over TCP | 5349 | A `turn:` URI without a `transport` query leaves the choice to the client's resolution procedure. RFC 7065 says `turns:` **MUST** be used whenever TURN runs over TLS, and `turn:` otherwise. Any explicit port, such as `:443`, overrides the default. ## Why only the TURN entry needs a credential The two kinds of server do very different amounts of work for a stranger: - A **STUN** server answers a Binding request by reporting the source address it saw, which gives the client a server-reflexive candidate. RFC 8489 notes that a STUN server on the public Internet supporting ICE would have no authentication. - A **TURN** server answers an Allocate request by reserving a **relayed transport address** and forwarding every packet between the client and its peers. That is bandwidth and state the provider pays for, and RFC 8656 warns that an open relay is attractive both to freeloaders and to attackers who want to hide their own address. So RFC 8656 says the server **MUST** require authentication, using the long-term credential mechanism of RFC 8489 or the third-party authorization extension (RFC 7635), unless it is a server offered by the local or access network, which **MAY** accept unauthenticated requests. In the long-term mechanism the first Allocate is rejected with a 401 error carrying a realm and a nonce; the client retries with its user name and a message-integrity value computed from the password. The password itself never crosses the wire, but the browser must hold it, which is why the application supplies it in `credential`. ## What the API checks before any packet leaves When the configuration is set, the W3C algorithm validates every URI: 1. The scheme must be `stun`, `stuns`, `turn` or `turns`, or the call throws `SyntaxError`. 2. A `stun:` or `stuns:` URI with a query, or a TURN query other than `transport=udp` or `transport=tcp`, throws `SyntaxError`. 3. A host part carrying a user name or password (`user:pass@host`) throws `SyntaxError`; credentials belong in the dictionary, not the URI. 4. A `turn:` or `turns:` URI whose entry lacks `username` or `credential` throws `InvalidAccessError`. ## What goes wrong in practice - **Listing only STUN.** Calls connect on open networks and fail wherever no direct path exists, because a STUN server never relays. - **A wrong credential.** The configuration is accepted, the TURN server answers 401, and gathering simply produces no relay candidate. The `icecandidateerror` event reports the server's `url` and the STUN `errorCode`, or 701 when no local address could reach the server at all. - **A fixed password in page code.** Anyone can read it, which is why credentials are usually issued per user and expire. - **Expecting `turns:` to encrypt the call.** WebRTC media is always protected end to end by DTLS-SRTP; TLS to the relay only protects the leg to the relay and its control messages.

  • Why can the TURN user name and password not be written into the URI as user:pass@host?
    RFC 7065's `turn:` and `turns:` URIs have no user-information part, and the W3C parsing steps throw `SyntaxError` when the host part carries a user name or password. Credentials travel only in the `RTCIceServer`'s `username` and `credential` members, which keeps the URI a pure address and lets one credential cover several URIs of the same server.
  • If the TURN credential is wrong, how does the application find out?
    Nothing throws: the configuration is valid, so the TURN server answers the Allocate with a 401 error and the agent gathers no relay candidate from it. The `RTCPeerConnection` fires `icecandidateerror` with the server's `url`, the STUN `errorCode` and `errorText`. Calls on open networks still connect, so the fault stays hidden until a user needs the relay; logging that event is how it is caught.

saying these in an interview costs you the question

  • A STUN server relays the media when a direct connection fails.
  • A TURN entry works without credentials; the server relays anonymously.
  • Using turns: instead of turn: is what encrypts WebRTC media.
  • The TURN password goes in the URI as user:password@host.
  • Port 443 is the default port for TURN over TLS.