Networking

STUN, TURN and ICE for VoIP: How NAT Traversal Actually Works

Himanshu Pal

Himanshu Pal

Why NAT breaks VoIP in the first place

SIP was designed on the assumption that endpoints have real, routable addresses. NAT broke that assumption. A phone behind a router has a private address like 192.168.1.50 that means nothing on the public internet, and SIP has a habit of writing that private address into places where a public one is required.

Specifically, two things go wrong. The phone advertises its private address in the SIP Contact header, so return signalling has nowhere to go. And it advertises the same private address in the SDP c= line, so the far end sends the audio into a void. The call sets up, both parties see "connected", and one or both hear silence. This is the origin of almost every one-way audio complaint.

STUN, TURN and ICE are three escalating answers to that problem.

STUN — find out your own public address

STUN (Session Traversal Utilities for NAT) is the simplest and the most commonly used. The client sends a small request to a public STUN server, which replies with the source address it saw. The client now knows its own public IP and port, and can advertise that instead of its private address.

It costs almost nothing: one tiny exchange with a public server, and the media then flows directly between the two endpoints. When STUN works, it is the best outcome.

Its limitation is the kind of NAT in front of the client. With a symmetric NAT, the router allocates a different external port for every destination. The address STUN discovered was the mapping for the STUN server's traffic — and it will not accept media from the other party arriving on that port. STUN reports an address that turns out to be useless.

TURN — relay the media when direct fails

TURN (Traversal Using Relays around NAT) is the fallback. Instead of trying to get the two endpoints talking directly, both send their media to a publicly reachable relay server, which forwards it on.

This works essentially always, because both sides are making outbound connections to a public host — something virtually every NAT and firewall permits. The trade-offs are real, though: every packet of every call traverses your relay, so it consumes bandwidth and adds latency, and the relay becomes infrastructure you must run and scale. TURN is a last resort, not a default.

ICE — try everything, pick what works

ICE (Interactive Connectivity Establishment) is the framework that ties the two together. Rather than committing to one strategy in advance, an ICE-capable endpoint gathers every address it might be reachable on — its local address, its STUN-discovered public address, and a TURN relay address — and calls each one a candidate.

The two endpoints exchange their candidate lists in the SDP and then systematically test pairs of them with connectivity checks, selecting the best path that actually works. Direct local connection is preferred, STUN-discovered next, TURN relay last.

ICE is what makes WebRTC reliable across arbitrary networks, and it is why a browser video call generally just works while a badly configured SIP phone does not. The cost is complexity and a small setup delay while the checks run.

Configuring NAT traversal in Asterisk

For a server behind NAT, the important thing is that Asterisk knows its own public address and which networks are local. In PJSIP, on the transport:

[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060
external_media_address=203.0.113.10
external_signaling_address=203.0.113.10
local_net=192.168.0.0/16
local_net=10.0.0.0/8

Those external_* settings tell Asterisk to rewrite its SDP and Contact with the public address for anything outside local_net, while leaving internal calls alone.

On the endpoint side, two settings matter for NATed phones:

[1001]
type=endpoint
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes

rtp_symmetric tells Asterisk to send audio back to the address the audio actually came from, rather than the address the phone claimed — which fixes the case where the phone lied about its address. rewrite_contact does the equivalent for signalling. In practice these two settings resolve the majority of NAT audio problems without any STUN at all.

The chan_sip equivalents are externip, localnet, nat=force_rport,comedia.

Do not forget the firewall

No amount of NAT traversal configuration helps if the RTP ports are closed. Asterisk uses a UDP range defined in rtp.conf, by default roughly 10000-20000. Every port in that range needs to be open and, if you are behind NAT, forwarded to the server. Forwarding only port 5060 gives you a call that connects and then sits in silence.

You can safely narrow the range if you want a tighter firewall — each concurrent call needs about two ports, so a few hundred is ample for most systems.

A note on SIP ALG

Many consumer and small-business routers include a "SIP ALG" feature that inspects SIP packets and rewrites addresses in an attempt to help with exactly this problem. In practice it is implemented badly more often than not, and it corrupts headers in ways that produce bizarre, hard-to-diagnose failures — malformed requests, calls that drop after a fixed interval, registration that works then stops.

Disable SIP ALG. Configure NAT properly on the PBX instead. This is one of the most reliable pieces of VoIP advice there is.

Frequently asked questions

Do I need a TURN server for SIP?

Usually not. Server-side NAT configuration with rtp_symmetric and a correct external address handles most SIP deployments, because the PBX itself has a public address and can anchor the media. TURN matters most for peer-to-peer WebRTC where neither end is reachable.

Why does my call have audio in one direction only?

One side is sending RTP to an address that cannot receive it — nearly always a private address advertised in the SDP. Check the c= line in a packet capture, and enable symmetric RTP so Asterisk replies to the observed source.

Is STUN enough on its own?

For most home and office NATs, yes. Behind a symmetric NAT it is not, and you need a relay or a media-anchoring server in the path.

What ports should I open?

UDP 5060 (or 5061 for TLS) for signalling, plus the whole RTP range from rtp.conf — commonly UDP 10000-20000. Both are required.