STUN, TURN and ICE, Explained Without Jargon
Every explanation of WebRTC eventually drops three acronyms (STUN, TURN, ICE), and most readers' eyes glaze over. The ideas behind them are actually simple, and knowing them tells you exactly why a transfer is fast, slow, or impossible on a given network. This article goes one level deeper than the usual summary, because ShareDrop.org operates its own STUN and TURN servers, and the details are where the behaviour you see comes from.
The problem: your device does not know its own address
At home, your laptop's address is something like 192.168.1.23 — valid only inside your Wi-Fi. The wider internet sees your router's public address instead, and the router only forwards incoming traffic that it recognizes as a reply to something you sent. Two devices in this situation cannot simply dial each other: neither knows a number the other could call, and neither would answer an unexpected call if it did (how NAT decides what to let in).
STUN: a mirror
A STUN server does one tiny job: you send it a packet, and it replies “here is the public address and port your packet appeared to come from.” Now your browser knows what it looks like from the outside. If both sides learn their public addresses and start sending to each other more or less simultaneously, many routers accept the traffic as replies — the classic “hole punching” trick. STUN servers carry almost no traffic and see no file data; they are address mirrors, nothing more.
Concretely, the exchange is a Binding request of about twenty bytes over UDP, usually to port 3478, and a Binding response carrying anXOR-MAPPED-ADDRESS attribute: the reflected address, XOR-ed with a magic cookie so that overzealous routers do not rewrite it in transit. Public STUN servers exist and browsers will happily use them, but every TURN server is also a STUN server, so ShareDrop.org points both roles at its own relay and avoids depending on a third party for the mirror.
TURN: a relay for when mirrors are not enough
Some networks defeat hole punching: symmetric NATs use a different public port for every destination, carrier-grade NAT stacks multiple layers, and strict firewalls block UDP outright (why direct connections fail). TURN is the fallback: a server that both sides can reach, which forwards traffic between them. It costs real bandwidth for the operator and adds a hop of latency, but critically, in WebRTC the traffic it forwards is already encrypted end-to-end. A TURN server cannot read what it relays; it only sees packet sizes, timing and addresses (direct vs relay).
How an allocation works
- Allocate. The browser authenticates to the TURN server and asks for a relayed address. The server reserves a UDP port on its public side, say
203.0.113.50:49200, and hands it back. That address becomes one of the browser's ICE candidates (typerelay). - Permission. The relay will not forward packets from just anyone to that port. The browser must first install a permission for the peer's address; permissions last five minutes and are refreshed while the connection is in use.
- Data. Early packets travel wrapped in STUN messages (Send and Data indications). Once the path is established, the browser binds a channel, a two-byte number, and each packet then costs only a four-byte header. This is why a relayed transfer is nearly as efficient on the wire as a direct one.
- Lifetime. An allocation expires after ten minutes unless refreshed; the browser refreshes it while the connection lives and lets it lapse afterwards. Nothing about the session survives on the server.
The browser talks to the relay over UDP, TCP, or TLS: the last two on port 443 specifically to survive networks that only allow “normal HTTPS” traffic. The relayed side toward the peer is always UDP. ShareDrop.org's relay currently offers UDP and TCP; when a network blocks UDP entirely, the transfer connects over TCP to the relay and still completes.
What a relay costs, and why free ones are rare
Every relayed byte enters the server and leaves it again, so a 1 GB transfer is 2 GB of server traffic. Multiply by users and the relay is the single largest running cost of a peer-to-peer service, which is why so many tools quietly have no relay at all and simply fail on networks that need one. ShareDrop.org runs its own (coturn) rather than a third-party service, so that the relay's logging policy stays in the service's own hands: IP addresses in rate-limiting logs for at most 24 hours, nothing else.
Credentials that expire in minutes
A relay that accepts anyone would be abused within hours. The standard answer is the TURN REST scheme: the signaling server hands each session a username of the form expiry-timestamp:session-id and a password that is an HMAC of that username under a secret shared with the relay. The relay can verify the password without a database lookup, and the credential is worthless after the timestamp — a few minutes later. Nobody stores a password; nobody can reuse a leaked one.
ICE: the tournament
ICE is the process that ties this together. Each browser gathers every address it might be reachable at, both sides exchange these candidate lists through the signaling channel, and then they systematically test candidate pairs with small probe packets until one works.
| Candidate type | Source | Type preference | Meaning if it wins |
|---|---|---|---|
| host | Local interface address | 126 (highest) | Same network, or public IPv6 — LAN direct |
| prflx (peer-reflexive) | Discovered during checks | 110 | Direct; the router revealed a port during testing |
| srflx (server-reflexive) | STUN mirror | 100 | Direct across the internet by hole punching — Internet direct |
| relay | TURN allocation | 0 (lowest) | Forwarded by the relay — Encrypted relay |
Each candidate gets a priority computed from its type preference, a local preference (which interface: Ethernet beats Wi-Fi beats cellular) and its component. Pairs inherit a combined priority from both sides, and the checks run in that order, so a host-to-host pair is tried before anything involving the relay, and the relay wins only when everything above it has failed.
Three candidates, annotated
Here is what a browser actually sends the other side — one line per candidate:
a=candidate:1 1 udp 2122260223 192.168.1.23 51234 typ host
a=candidate:2 1 udp 1686052607 203.0.113.7 41000 typ srflx raddr 192.168.1.23 rport 51234
a=candidate:3 1 udp 41885439 203.0.113.50 49200 typ relay raddr 203.0.113.7 rport 410001 1 udp: a foundation identifier, the component (1 = the only one for a data channel), and the transport.2122260223,1686052607,41885439— the priorities. The huge gaps between them are the type preference multiplied by 224: host first, reflexive second, relay last, with room inside each band for interface preference.192.168.1.23 51234 typ host: the local address and port. Modern browsers replace the address with an mDNS name (see below).203.0.113.7 41000 typ srflx raddr … rport …: the public address STUN reported, and the local address it was derived from.203.0.113.50 49200 typ relay: the port the TURN server allocated on its own address.
The peer receives all three, pairs each with its own candidates, and the tournament begins with the host-to-host pair. On the same network that pair wins in tens of milliseconds; across the internet it fails and the reflexive pair takes over; behind a symmetric NAT the reflexive pair fails too and the relay pair carries the session.
The checks themselves
A connectivity check is a STUN Binding request sent from one candidate to the other, authenticated with the short-term credentials (ice-ufrag and ice-pwd) exchanged in the session description: a stray packet from the wrong session fails authentication and is ignored. A pair whose check is answered is succeeded; the controlling side then nominates the pair it wants, the other side agrees, and the data channel opens on it. Checks that never get an answer time out; the whole tournament typically takes a few hundred milliseconds on two home networks and a few seconds when only a relay works.
Trickle ICE and restarts
Modern implementations use trickle ICE: candidates are sent to the other side as they are discovered rather than all at once, and checks begin on the first pair before the last candidate exists. A host candidate is known instantly; a reflexive one needs a STUN round trip; a relay candidate needs an allocation. Trickle lets a LAN connection complete before the relay allocation even finishes, which is why LAN direct appears almost immediately.
When the network changes underneath a live connection, a phone leaving Wi-Fi for cellular, the browser performs an ICE restart: new credentials, a fresh gathering, a new tournament, while the DTLS session and data channel persist. In ShareDrop.org a restart that succeeds is invisible; one that fails becomes a reconnect through signaling, and the transfer resumes from the last verified chunk (where that fits in the whole flow).
A privacy detail: hidden host addresses
Browsers no longer put your raw local address (192.168.1.23) into host candidates, because web pages used it for fingerprinting. Instead the candidate carries a random name ending in .local, which the peer resolves with multicast DNS, something that only works when both devices are on the same network, which is the only case a host candidate is useful for anyway. The side effect is that a LAN-direct connection now depends on multicast DNS working: a network that blocks multicast, or a Mac that has not granted the browser local-network permission, falls back to a relay for two devices sitting side by side (managed networks).
How long things take, and why “connecting” sometimes hangs
ICE is fast when something works and slow when nothing does, because it cannot know the difference between a check that is still in flight and one that will never be answered. A single check is retransmitted with increasing gaps for several seconds before that pair is declared failed; with a dozen pairs to try, a connection where no path works can take tens of seconds to conclude that. This is the “connecting…” that seems to hang: the browser is still dutifully waiting for answers that a firewall is silently discarding. ShareDrop.org gathers relay candidates from the start so the fallback is tried early rather than after every direct pair has timed out, and it reports a failure once ICE does.
After a connection is up, the browsers keep checking. Every few seconds each side sends a small consent request (RFC 7675); if thirty seconds pass with no reply, the connection is declared failed rather than left to hang. In between, a brief loss of replies puts the connection in a disconnected state (a phone passing through a lift, a laptop switching access points) from which it usually recovers on its own. A transfer tool that treats disconnected as fatal restarts transfers that would have resumed by themselves two seconds later; ShareDrop.org waits.
Reading your own connection
In ShareDrop.org, the connection badge is derived from the candidate pair that actually won, read live from WebRTC statistics: two host candidates mean a LAN transfer, a reflexive pair means direct across the internet, and any relay candidate means TURN is in the path. The mode is never inferred from configuration, only from what the connection reports about itself.
To see the same thing yourself in Chrome, open chrome://webrtc-internals during a transfer, expand the connection, and look for the candidate-pair entry with state: succeeded and nominated: true. Its localCandidateId and remoteCandidateId point at two candidate entries whose candidateType fields are the answer; its bytesReceived counter, sampled a second apart, is your true throughput. Firefox exposes the same under about:webrtc.
Why three protocols instead of one
The split looks like committee design, but it follows the problem. STUN came first, in 2003, with the modest job of telling a device its own public address; its original version also tried to classify NATs into the four types, which turned out to be unreliable, and later revisions dropped the classification and kept the mirror. TURN followed as a separate protocol because relaying is a fundamentally different service with a fundamentally different cost: a mirror answers one packet; a relay carries every byte for the life of the session and must authenticate, meter and expire. ICE arrived to orchestrate the two: it is not a wire protocol of its own so much as a procedure for gathering, exchanging and testing addresses, and it would work with any mirror and any relay. Browsers hide the entire arrangement behind one object, RTCPeerConnection, which is why most web developers never meet the three names until something fails.
IPv6 and dual-stack networks
When a device has both an IPv4 and an IPv6 address, it gathers host candidates for each, and its reflexive and relay candidates come in both flavours where the servers support them. ICE interleaves the two families when it orders checks, so an IPv6 pair (which needs no NAT traversal at all, only a firewall that accepts the check) often succeeds before the IPv4 pairs have finished trying. Two phones on IPv6-capable mobile carriers can therefore connect Internet direct despite both sitting behind carrier-grade NAT on the IPv4 side. The catch is that both ends need working IPv6; one without forces the whole tournament onto IPv4 (where the NAT rules apply).
What the page itself can see
A web page driving WebRTC gets a small window into all of this. Three state values on the connection tell it where things stand: iceGatheringState (is address discovery still running), iceConnectionState (has a pair been found, and is it still alive) and connectionState (the combined picture including DTLS). Beyond those, getStats() returns the full table of candidates and pairs with their types, states and byte counters, updated as the connection runs. ShareDrop.org reads exactly two things from it: which pair is nominated, to label the badge, and the bytes counter, to show the rate. It writes nothing back: a page can observe the negotiation but cannot bend it, which is part of why the whole scheme is safe to expose to arbitrary websites.
Running your own relay: what matters
ShareDrop.org runs coturn, the open-source TURN server that most of the industry runs. A few decisions matter more than the rest.
- Never relay to private addresses. A TURN server that will forward packets to
10.xor192.168.xis a tunnel into whatever network it sits on. coturn'sdenied-peer-iprules for all private ranges are the first lines of the configuration, not an afterthought. - Short-lived credentials, issued per session. The REST scheme above means the relay holds one shared secret and no user database, and a credential scraped from a browser's memory is useless within minutes.
- Quotas per allocation. A bandwidth cap per session and a cap on concurrent allocations per address stop one misbehaving client from consuming the relay for everyone. The caps are well above what a single transfer needs.
- Listen on both UDP and TCP. UDP is the fast path; TCP on the same port catches networks that block UDP. TLS on 443 would catch a few more locked-down networks and is on the list.
- Log the minimum. The relay's logs contain client addresses for rate-limiting and abuse defence and are rotated within 24 hours. They cannot contain anything about the files, because the relay never sees plaintext, but logging less also means having less to protect.
- Measure the ratio. The fraction of sessions that end up relayed is the single most useful number about the service's health: a rise means a network somewhere started blocking direct paths, or that ICE is failing to try them. Commonly cited estimates for WebRTC services fall between one in ten and one in five sessions, with mobile-to-mobile transfers over-represented.
Field notes
- TURN servers double as STUN servers, so a single relay deployment provides the mirror function too; one coturn instance can serve both.
- On developer machines, macOS's local-network permission can prevent mDNS host candidates from resolving between browsers on the same computer — a relay on loopback makes an excellent workaround for local testing.
- Relay allocations are gathered in parallel with everything else, not after direct attempts fail. The cost is one allocation per session that often goes unused; the benefit is that the fallback is ready the moment it is needed instead of adding seconds to a connection that was already struggling.
- When a corporate network allows only TCP on 443, the relay connection over TCP works but throughput drops, because SCTP's own congestion control is now running on top of TCP's — two loss-recovery mechanisms fighting each other. Expect it to work; do not expect it to be fast.
Standards referenced
- RFC 8489: Session Traversal Utilities for NAT (STUN): the Binding request, XOR-MAPPED-ADDRESS, and short-term credentials used by connectivity checks.
- RFC 8656: Traversal Using Relays around NAT (TURN): allocations, permissions, channels and lifetimes.
- RFC 8445: Interactive Connectivity Establishment (ICE): candidate priorities, pairing, checks and nomination.
- RFC 8838: Trickle ICE.
- RFC 8828: WebRTC IP address handling requirements: why host candidates are hidden behind mDNS names.
- TURN REST API draft: the short-lived credential scheme the ShareDrop.org coturn deployment uses (and most production TURN services with it).