Have you ever sat in a Zoom call stupor and thought to yourself “how does this actually work?” Of course you have!
Not the codecs or the UI. The actual network connection. You’re at home on your laptop. Your colleague is on their laptop in their home. Neither of you has a public IP address because you’re both behind routers that guard your private networks. And somehow, video data streams directly between you with low enough latency to have a conversation. How does this miracle of modern corporate life actually work?
If you’ve read about how the Internet works, you know that NAT (Network Address Translation) lets multiple devices share a single public IP address. Your home router has one public IP, and all your devices use private addresses like 192.168.1.x internally. NAT solved the IPv4 address shortage beautifully. But it also broke something fundamental: the ability for two computers to just directly address each other.
NAT traversal is the collection of techniques that make peer-to-peer connections possible despite NAT. It powers video calls, online gaming, file sharing, and WebRTC applications.
The Problem: Two Locked Doors
To understand NAT traversal, we need to understand why NAT breaks things.
When your computer makes an outgoing connection to a server, your router remembers the mapping. It notes “traffic from internal device 192.168.1.50:34521 should be translated to public address 98.76.54.32:34521.” When the server responds to that public address and port, your router forwards the response to your computer. Outgoing connections work fine.
The problem is incoming connections. If some random computer on the Internet tries to send a packet to your router’s public IP, the router doesn’t know what to do with it. No existing mapping. The router has no idea which internal device should receive the packet, so it drops it.
Network Address Translation concept. Source: Wikimedia Commons by Michel Bakni, CC BY-SA 4.0.
Imagine two people, Alice and Bob, both behind NAT. Alice wants to establish a direct connection to Bob for a video call. Alice doesn’t know Bob’s private IP but it wouldn’t help anyway since private IPs aren’t routable on the public Internet. Alice only knows Bob’s router’s public IP. But when Alice sends a packet to Bob’s router, it gets dropped.
Discovering Your Public Address with STUN
The first step in NAT traversal is figuring out what the outside world sees when you send packets. Your computer knows its private IP address, but not what public IP and port your router uses for outgoing traffic. That’s where STUN comes in.
STUN stands for Session Traversal Utilities for NAT. A STUN server is simple. It’s just a server with a public IP address that does one thing: when you send it a packet, it tells you what source address it saw.
The conversation:
- Your computer sends a UDP packet to the STUN server
- Your router translates your private address to a public address
- The STUN server receives the packet and sees your public IP:port
- The STUN server sends a response containing that public IP:port back to you
Now you know your “server reflexive” address — what the outside world sees when you send packets. You can share this with a peer who wants to connect to you.
Google and other companies run free public STUN servers. The whole interaction takes one round trip. Fast and free because STUN servers handle very little traffic. They just answer simple questions and get out of the way.
But knowing your public address is only half the battle. Just because Alice knows Bob’s public address doesn’t mean she can send packets there successfully. Remember that Bob’s router will still drop incoming packets that don’t match an existing mapping.
UDP Hole Punching: Opening Doors Simultaneously
UDP hole punching makes direct peer-to-peer connections possible. Surprising, since hole punching is usually not conducive to making good peer connections.
NAT routers create mappings when they see outgoing traffic. So what if both Alice and Bob send packets to each other at roughly the same time? Alice’s router creates a mapping for her outbound traffic to Bob. Bob’s router creates a mapping for his outbound traffic to Alice. Now both routers have mappings, and subsequent packets flow through.
Step by step:
- Alice and Bob both contact a STUN server to learn their public addresses
- They exchange these addresses through some other channel. We’ll call this “signalling” and typically your application server handles this. Remember both Alice and Bob can make outbound connections to your application server.
- Alice sends a UDP packet to Bob’s public address. Bob’s router drops it (no mapping exists yet), but Alice’s router now has a mapping for this communication
- Bob sends a UDP packet to Alice’s public address. Alice’s router might drop it, but Bob’s router now has a mapping too
- Alice sends another packet. This time, Bob’s router has a mapping from step 4, so the packet gets through
- Bob’s packets now also get through because Alice’s router has had a mapping since step 3
This works with most NAT types, but not all.
NAT Types: Why Some Networks Are Harder to Traverse
Not all NAT implementations behave the same way. The differences matter for NAT traversal and affect poor Alice and Bob’s hopes of communication.
Full Cone NAT is the most permissive. Once an internal host sends a packet through the NAT, any external host can send packets back to that mapping. Hole punching works easily.
Restricted Cone NAT is pickier. The NAT only accepts incoming packets from IP addresses that the internal host has previously sent packets to. Hole punching still works because both peers send packets to each other.
Port Restricted Cone NAT is stricter. The NAT only accepts incoming packets from an IP:port combination that the internal host has previously contacted. Hole punching still works, just barely.
Symmetric NAT is the troublemaker. Symmetric NAT uses a different external port for every destination. When Alice sends to the STUN server, she might get mapped to 98.76.54.32:5000. When she sends to Bob, she gets mapped to 98.76.54.32:5001. Note the different ports. The address she learned from STUN is useless for connecting to Bob.
With symmetric NAT on both sides, traditional hole punching fails. The peers can’t predict what port mapping the NAT will use for their direct communication. Some advanced techniques involving port prediction exist, but they’re unreliable. So when symmetric NAT is involved, we need to turn to a fallback.
TURN: The Reliable Fallback
Forgive the pun. TURN (Traversal Using Relays around NAT) is that fallback. When direct peer-to-peer connections fail, traffic routes through a relay server.
The TURN server has a public IP address. Both Alice and Bob can establish connections to it (outgoing connections always work with NAT). The TURN server then relays packets between them. Alice sends to the TURN server, which forwards to Bob, and vice versa.
TURN always works regardless of NAT type. But it has significant downsides:
Increased latency. Every packet detours through the relay server. If the TURN server is geographically distant from either peer, they’ll probably notice the latency hit.
Bandwidth costs. Unlike STUN, TURN servers handle actual media traffic. Video calls use a lot of bandwidth. Running TURN servers costs real money.
Dependency. If your TURN server goes down, relayed calls fail. You need reliable infrastructure.
TURN is the technique of last resort. It’s only used when direct connections are impossible. But you absolutely need it as a fallback because symmetric NAT is sadly common in corporate and mobile networks.
ICE: Putting It All Together
In practice, you don’t manually choose between STUN and TURN. You use ICE. Not that ICE. I mean Interactive Connectivity Establishment. ICE systematically tries different connection methods and picks the best one that works.
What ICE does:
-
Gather candidates. ICE collects all possible ways a peer could be reached:
- Host candidates: Your local IP addresses (useful if peers are on the same network)
- Server reflexive candidates: Your public address discovered via STUN
- Relay candidates: Addresses allocated on TURN servers
-
Exchange candidates. Both peers share their candidate lists through the signalling channel
-
Connectivity checks. ICE tries to establish connections using each pair of candidates. It sends test packets and waits for responses. This is where the actual hole punching happens.
-
Select the best path. ICE prioritises direct connections over relayed ones. It picks the working path with the best performance characteristics.
ICE handles all the complexity automatically. It’ll use your local network if both peers are in the same office. It’ll use hole-punched connections when possible. It’ll fall back to TURN only when necessary. All this happens while you’re just waiting for your video call to connect.
WebRTC: NAT Traversal for the Browser
What does it actually mean to “use ICE”? If you’ve done browser-based real-time communication, you’ve used WebRTC. And if you’ve used WebRTC, you’ve used ICE, STUN, and TURN whether you knew it or not.
WebRTC builds NAT traversal into the browser. When you create a peer connection in JavaScript, the browser handles ICE negotiation automatically. You provide STUN and TURN server configurations, implement signalling to exchange ICE candidates between peers, and the browser does the rest.
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:your-turn-server.com',
username: 'user',
credential: 'pass'
}
]
};
const peerConnection = new RTCPeerConnection(config);
Google’s public STUN server is free and handles millions of requests. For TURN, you’ll need your own server or a paid service. If you’re building a WebRTC service, don’t skip the TURN server because without it a meaningful percentage of your users (especially on corporate networks) won’t be able to connect.
Why UDP?
Everything we’ve discussed uses UDP. That’s not an accident.
TCP requires a handshake to establish a connection. That handshake involves multiple round trips with specific sequence numbers. Getting TCP hole punching to work is significantly harder because both peers need to attempt connections simultaneously, and TCP’s connection state machine doesn’t handle this gracefully.
UDP is connectionless. A packet is just a packet. If it arrives, great. This makes hole punching much simpler because we’re just trying to get packets through, not establish a stateful connection.
For real-time communication, UDP is the right choice anyway. If a video frame packet gets lost, you don’t want to wait for retransmission. By the time it arrives, it’s too late. Better to move on to the next frame. Protocols like WebRTC use UDP for media and implement their own congestion control and packet loss handling on top.
What This Means for Distributed Systems
NAT traversal is really a distributed systems problem in disguise. Multiple independent nodes trying to establish communication without a central coordinator. The peers can’t directly observe each other’s state. They have to make assumptions and coordinate through side channels.
The signalling server that exchanges ICE candidates plays a role similar to a coordination service in distributed systems. It’s the reliable channel that bootstraps the unreliable peer-to-peer connection. Without some way to exchange information out-of-band, the peers would have no way to find each other.
There’s also a consistency problem lurking in here. Both peers need to agree on which connection path to use. ICE handles this by having both sides run the same algorithm and arrive at the same conclusion. A simple form of distributed consensus.
Key Takeaways
NAT traversal is one of those arcane network plumbing things that you can happily ignore until you find your entire project or service depends on understanding it.
NAT helped us avoid the IPv4 apocalypse but it breaks incoming connections because routers don’t know which internal host should receive unsolicited packets. Using STUN servers you can find your public IP:port mapping and UDP hole punching creates the necessary NAT mappings on both peers’ routers. But on locked-down corporate networks Symmetric NAT renders our holes useless by always using different port mappings. TURN servers can be used to relay traffic between the peers but it’s only ever a fallback because it adds latency and cost. WebRTC handles all of this in the browser, using ICE to always find the best connection path for real-time connections.
The next time you’re sitting through a video call with talking heads from around the world, take a moment to admire the complex, distributed systems that make your day possible.