How NAT Traversal Actually Works
Try the interactive lab for this articleTake the quiz (6 questions · ~5 min)Most internet traffic works from behind NAT without drama. A phone in Madrid opens HTTPS to a public server, packets come back, and nobody thinks about it. The trouble starts when both endpoints are behind NAT and want to talk directly to each other.
That is the entire reason NAT traversal exists.
A peer-to-peer call, game session, file transfer, or remote-control channel has a different problem from ordinary client-server traffic. Each endpoint may know only its private address. Each NAT may allocate public ports dynamically. Each gateway may drop unsolicited inbound packets unless prior outbound state exists. Some NATs reuse the same external mapping for multiple destinations. Some create a different external port for every destination. Some firewalls expire idle UDP state aggressively enough to kill a call in silence unless keepalives keep it open.
If you flatten all that into "STUN gets your public IP" you miss the actual mechanism. STUN is only one piece. NAT traversal is really an exercise in coordinating three things at once:
- learning which public mappings exist right now
- creating matching state in both NATs quickly enough that packets can cross
- falling back to relays when the direct path is impossible or too fragile
This article walks through that machinery in detail. We will look at mapping and filtering behaviour, STUN message mechanics, UDP hole punching, ICE candidate gathering and checks, TURN relaying, why TCP traversal is harder, and the operational realities that make relay percentage, timeout tuning, and logging matter in production.
NAT traversal starts with NAT state, not with signalling servers
The key fact from the underlying NAT problem is simple: return traffic works because outbound traffic created state first.
Suppose a laptop on 192.168.1.23 sends a UDP packet to a public STUN server. The home router in Vienna might translate it like this:
inside tuple: 192.168.1.23:52344 -> 198.51.100.20:3478
outside tuple: 203.0.113.18:62001 -> 198.51.100.20:3478The router now has a state entry saying that packets arriving for 203.0.113.18:62001 from that remote side should be sent back to 192.168.1.23:52344.
That sounds ordinary. It is the whole story.
A direct inbound packet from some unrelated peer in Athens does not match that state unless the NAT's filtering policy allows it. Even if the peer knows the public tuple 203.0.113.18:62001, the packet may still be dropped because the NAT only trusts replies from the specific remote endpoint that created the mapping.
This is why NAT traversal has to care about two distinct behaviours:
- mapping behaviour: how the NAT chooses the public IP and port for outbound traffic
- filtering behaviour: which remote sources are allowed to send packets back to that public mapping
These behaviours are often described with RFC 4787 terminology or older cone-NAT language. The names vary a bit across products and papers, but the operational idea is stable.
Endpoint-independent behaviour is the easiest case
If a NAT keeps the same public mapping for a given internal socket regardless of destination, and allows inbound traffic from any remote host once that mapping exists, traversal is straightforward.
Example:
192.168.1.23:50000 -> STUN server becomes 203.0.113.18:62001
192.168.1.23:50000 -> peer in Athens also uses 203.0.113.18:62001The STUN-discovered address is genuinely useful, because it matches what the peer will see.
Endpoint-dependent mapping is the hard case
A symmetric NAT creates different public ports for different destinations.
Example:
192.168.1.23:50000 -> STUN 198.51.100.20:3478 becomes 203.0.113.18:62001
192.168.1.23:50000 -> peer 203.0.113.77:50040 becomes 203.0.113.18:62144Now the address learned from STUN is not the address the peer will actually observe for peer traffic. That breaks the assumption behind simple UDP hole punching.
This is why symmetric NAT remains the classic "use TURN" case.
Timeouts matter as much as address rewriting
NAT state expires. UDP state can be short lived, especially on cheap consumer hardware, enterprise firewalls, or carrier-grade NAT. One box might keep an idle UDP binding for 30 seconds. Another for 120 seconds. Another might reset the timer only on outbound packets.
That means a successful direct path is not enough. The application often needs keepalives to keep the path alive.
A silent WebRTC call is therefore not actually silent at the network level. Tiny STUN or consent freshness messages keep the NAT bindings and permission state from expiring underneath the media path.
STUN is a public mirror for your current mapping, not a magic bypass
Session Traversal Utilities for NAT, STUN, is the first tool most traversal systems use. Its core question is humble: "What source IP and port do I appear to have from the public internet?"
A STUN binding request is small. The modern specification is RFC 8489. The request contains a header with a magic cookie and transaction ID. The server replies with attributes, usually including XOR-MAPPED-ADDRESS, which encodes the observed public address and port.
A simplified exchange:
client 192.168.1.23:52344
-> NAT rewrites to 203.0.113.18:62001
-> STUN server 198.51.100.20:3478
STUN reply says:
your source looked like 203.0.113.18:62001A stripped-down packet shape looks like this:
Binding Request
type: 0x0001
magic cookie: 0x2112A442
transaction ID: 96 bits random
Binding Success Response
type: 0x0101
magic cookie: 0x2112A442
transaction ID: copied from request
XOR-MAPPED-ADDRESS: 203.0.113.18:62001The XOR encoding exists to make the attribute slightly less trivial for middleboxes to tamper with accidentally and to keep the address from appearing in plain form in some broken parsers.
What STUN tells you correctly
STUN is good at three things:
- telling the client its observed public address and port from a specific server's point of view
- proving that outbound UDP to the server works
- contributing a server-reflexive candidate to ICE
What STUN does not solve
STUN does not:
- force inbound packets through a restrictive filter
- make a symmetric NAT behave predictably
- guarantee that a second destination will see the same mapping
- relay media when direct connectivity fails
That last point is important. STUN is not a fallback data path. It is discovery.
Binding discovery often runs against more than one STUN server
ICE implementations commonly query several STUN servers, not because they expect different answers in the easy case, but because redundancy and path diversity matter. If one STUN server is unreachable, discovery should still proceed. If the network path to one server is broken, a second can still provide a candidate.
A service might configure:
stun:stun1.example.net:3478
stun:stun2.example.net:3478
stun:stun3.example.net:3478The goal is not mystical NAT classification. The goal is robust discovery under normal internet messiness.
UDP hole punching works by creating state on both sides at nearly the same time
The direct traversal trick most people mean is UDP hole punching. The phrase sounds more magical than it is.
Assume two peers:
- Alice in Barcelona behind NAT A
- Nikos in Athens behind NAT B
Each peer first talks to a rendezvous service or signalling server on the public internet. That service helps exchange candidate addresses, usually discovered via STUN. Then both peers send UDP packets toward each other's public candidate at roughly the same time.
The crucial effect is this:
- Alice's outbound packet to Nikos creates state in NAT A for traffic from Nikos.
- Nikos's outbound packet to Alice creates state in NAT B for traffic from Alice.
- If the mapping and filtering rules are permissive enough, the packets cross in flight and each NAT accepts the incoming traffic because matching outbound state now exists.
A timeline:
Alice private: 10.0.0.23:50000
NAT A public: 198.51.100.14:62001
Nikos private: 192.168.1.44:40000
NAT B public: 203.0.113.77:54022
1. Alice learns 198.51.100.14:62001 via STUN
2. Nikos learns 203.0.113.77:54022 via STUN
3. signalling service exchanges these candidates
4. Alice sends UDP to 203.0.113.77:54022
5. Nikos sends UDP to 198.51.100.14:62001
6. both NATs now have outbound state for the other side
7. packets arrive and the direct path formsNothing was literally punched through a firewall. Each NAT was persuaded, by outbound traffic, to expect inbound traffic from the peer.
Timing matters, but not in the cinematic way people imagine
The packets do not have to cross at the exact same microsecond. The requirement is looser: the relevant state in both NATs must exist when the reciprocal packet arrives, and the filtering rules must allow it.
This is why ICE runs repeated connectivity checks rather than a single heroic attempt. One peer may send several STUN checks to the candidate pair. The other does the same. The system keeps trying until one pair works or the candidate checklist is exhausted.
Why hole punching fails on symmetric NAT
If Alice's NAT gives her one public port when she talks to the STUN server and another when she talks to Nikos, the candidate exchanged through signalling is stale for the real peer path. Nikos sends toward 198.51.100.14:62001, but Alice's NAT chose 198.51.100.14:62144 for packets to Nikos. The inbound packet lands on the wrong public port and dies.
This is the textbook reason relay fallback exists.
TCP hole punching exists, but it is far less pleasant
TCP is connection-oriented and far more sensitive to sequence of events. Simultaneous open is possible in the protocol, and some systems have used TCP hole punching, but it is much less robust than UDP traversal. NAT behaviour for TCP is often less friendly, application stacks are less uniform, and failure recovery is clumsier.
This is one reason WebRTC chose UDP for media and built rich machinery around it. If you want low latency, packet-tolerant, peer-to-peer communication through consumer NATs, UDP gives you far more room to manoeuvre.
ICE is the orchestration layer that turns several candidate guesses into one working path
Interactive Connectivity Establishment, ICE, defined in RFC 8445, is the system that organises candidate gathering, exchange, checking, and final selection.
STUN and TURN provide raw capabilities. ICE decides how to use them.
ICE gathers several kinds of candidates
Each endpoint typically collects:
- host candidates: direct local interface addresses such as
192.168.1.23:50000 - server-reflexive candidates: public mappings learned via STUN such as
203.0.113.18:62001 - relay candidates: TURN-assigned public relay addresses
A single browser may therefore advertise multiple possibilities.
Example SDP-style candidate lines:
candidate:1 1 udp 2130706431 192.168.1.23 50000 typ host
candidate:2 1 udp 1694498815 203.0.113.18 62001 typ srflx raddr 192.168.1.23 rport 50000
candidate:3 1 udp 1677729535 198.51.100.90 43000 typ relay raddr 0.0.0.0 rport 0The numbers look cryptic, but they encode real ranking information. Host candidates usually get highest priority, then server-reflexive, then relay, because direct local or direct public paths are preferred over sending everything through a relay.
ICE forms candidate pairs and tests them systematically
Once peers exchange candidates through signalling, ICE builds candidate pairs such as:
Alice host <-> Nikos host
Alice srflx <-> Nikos srflx
Alice srflx <-> Nikos relay
Alice relay <-> Nikos srflx
Alice relay <-> Nikos relayMost of these pairs will fail in real internet conditions. That is expected.
The agent then sends STUN connectivity checks over each plausible pair. These are not generic application packets. They are authenticated or integrity-protected STUN transactions carrying ICE-specific credentials such as username fragments and passwords exchanged out-of-band in the signalling layer.
The checks answer practical questions:
- can I send from this local candidate to that remote candidate?
- does the peer receive and respond?
- is this pair better than the others?
The checklist is ordered because trying everything blindly is wasteful
ICE assigns priorities and foundations so that the most promising pairs are checked first. Two details matter here.
Priority influences the order of checking and the eventual winner.
Foundation groups candidates that are likely to share the same underlying network path characteristics, which avoids wasting work on pairs that are effectively duplicates.
The result is that the direct server-reflexive pair often gets tried before relay pairs, but the relay is available when direct paths fail.
Controlling and controlled roles prevent both peers from making contradictory final decisions
One ICE side becomes controlling and the other controlled. This matters at nomination time. The controlling agent ultimately nominates the candidate pair that should carry the session.
Without this role separation, both peers could discover several viable pairs and choose different winners. ICE role resolution keeps the decision convergent.
Nomination is the end of the search, not the start of media
Once a candidate pair succeeds and is nominated, media or data flows start on that path. Later checks may still monitor liveness, but the heavy path search ends.
The practical point is that NAT traversal is not "pick one STUN result and hope". It is a small distributed search problem with ranking, retries, and a defined winner.
TURN is the expensive but reliable answer when direct traversal fails
Traversal systems need a path that works even when the NATs are hostile. That path is TURN, Traversal Using Relays around NAT, currently specified in RFC 8656.
TURN gives the client a relay allocation on a public server. Instead of the peer sending packets to the client's own public NAT mapping, both peers can send to relay addresses on the TURN server. The relay forwards traffic between them.
A simplified flow:
Alice -> TURN allocates 198.51.100.90:43000
Nikos -> TURN allocates 198.51.100.90:43088
Alice sends media to Nikos via TURN
Nikos sends media to Alice via TURN
TURN forwards both waysTURN works because both sides are now doing normal outbound client-server traffic
Each peer only needs to reach the TURN server, which is a public server accepting ordinary outbound traffic. The difficult peer-to-peer inbound problem disappears. The server becomes the middle hop.
That reliability is why TURN is non-negotiable for serious real-time products. If you promise that a call should work from hotel Wi-Fi in Prague, from a mobile network behind CGNAT, and from an enterprise office with a strict firewall, direct traversal alone will not meet the promise.
Relaying has real cost
TURN is not conceptually expensive. It is financially and operationally expensive.
If a one-to-one video call consumes 2 Mbit/s in each direction and both sides relay through TURN, the relay handles roughly 4 Mbit/s of forwarding for that session. Scale that to 10,000 concurrent calls and you are now pushing about 40 Gbit/s through relay infrastructure, before accounting for bursts, packet overhead, or multiple media tracks.
That infrastructure costs real money. In a European cloud region, the egress bill can move from background noise to several thousands of euros per month very quickly, and at larger scale much more than that.
This is why relay ratio is a first-class business metric for WebRTC operators.
TURN has its own machinery: allocations, permissions, and channels
A TURN client does more than ask for a relay address.
Typical pieces include:
- allocation: reserve relay resources on the server
- permission: authorise traffic exchange with a given peer IP
- channel binding: create a compact mapping to reduce per-packet overhead
- refresh: keep the allocation alive before expiry
These are not decorative features. They exist to stop TURN from becoming an open UDP forwarding service and to reduce protocol overhead once the session is live.
A relay is still subject to path quality and placement
TURN solves reachability, not geography. If two users are in Milan and the relay sits only in Dublin, the media now bounces through Dublin. The call works, but latency and jitter worsen.
Well-run systems therefore place TURN close to users, often in several European metros and a few broader global regions, then use DNS or anycast to steer clients toward a nearby relay cluster.
The signalling plane is separate from the media plane, and that separation matters
ICE, STUN, and TURN do not define your application's signalling transport. They assume some other channel exists to exchange metadata such as SDP offers, answers, and ICE candidates.
That signalling path might be:
- HTTPS to your backend
- WebSocket between browser and signalling service
- SIP over WebSocket in some voice deployments
- a game backend's matchmaking channel
The separation matters because people often blame NAT traversal for signalling bugs and vice versa.
A session can fail because:
- signalling never exchanged the candidates
- signalling exchanged them too late
- ICE credentials mismatched
- media path checks failed even though signalling succeeded
- a relay allocation existed, but permission refresh failed later
From the user's point of view, "the call failed" is one symptom. Underneath, several subsystems are involved.
A useful debugging mental model is:
signalling tells the peers where they might talk
ICE tests whether they actually can talk
TURN carries the traffic when direct talking is impossibleA real ICE exchange is mostly repetitive checking, not one dramatic switch
The clean conceptual story is candidate gathering, exchange, checks, nomination. On the wire it looks more repetitive because the agents are probing several possibilities and refreshing state as they go.
Take a typical browser-to-browser call where both users are on home broadband and both NATs are merely port-restricted rather than symmetric.
The sequence may look like this:
1. browser A gathers host candidate 192.168.1.23:50000
2. browser A sends STUN to a public server and learns srflx 203.0.113.18:62001
3. browser A allocates a TURN relay as insurance
4. browser B does the same on its side
5. signalling exchanges SDP plus ICE username fragments and passwords
6. A forms candidate pairs and starts STUN checks
7. B receives checks, authenticates them, and answers
8. both sides learn which pair really passes packets
9. the controlling side nominates the best successful pair
10. media starts on the nominated pairThe checks themselves are ordinary STUN messages with ICE-specific attributes layered in. A simplified request may contain things like:
USERNAME: remote_ufrag:local_ufrag
PRIORITY: 1845494271
ICE-CONTROLLING: 64-bit tie-breaker
MESSAGE-INTEGRITY: HMAC over the message
FINGERPRINT: CRC-based trailerThose fields do real work.
USERNAME and the integrity check stop random public packets from being mistaken for valid ICE traffic. The priority tells the remote agent how attractive the sending candidate is. The controlling attribute helps resolve which side is allowed to nominate the final pair.
What matters operationally is that the direct path is not accepted merely because a packet arrived once. The peers are verifying that bidirectional traffic is possible using the credentials for this session.
This also explains why ICE logs can feel noisy. A healthy session often includes:
- multiple host checks that were always doomed because private addresses were not mutually reachable
- server-reflexive checks that race slightly with each other
- relay checks prepared in case direct paths fail
- repeated retransmissions because UDP is best effort
From the outside, the call "just connected". Underneath, the agents may have exchanged dozens of STUN packets to prove which candidate pair was real.
That repetition is a feature. It gives the system resilience against packet loss, timer skew, and network jitter during setup. A one-shot traversal protocol would fail far more often on ordinary consumer links.
Real-world networks add more pain than the clean textbook case
Textbook traversal examples assume a home NAT on each side and UDP allowed outbound. Reality is less cooperative.
Carrier-grade NAT reduces direct success rates
Mobile operators and some broadband providers put users behind CGNAT. The customer device may already sit behind a home router, so the path becomes double NAT:
phone or laptop -> home NAT -> ISP CGNAT -> public internetEach layer adds state, timeout policies, and potential endpoint-dependent behaviour. Traversal still often works, but direct success rates drop and TURN usage rises.
Enterprise firewalls may allow only a narrow UDP profile
Office networks often allow outbound UDP in principle but inspect or rate-limit unfamiliar traffic patterns. Some permit STUN to a known set of addresses and little else. Some expire idle UDP quickly. Some force almost all traffic through HTTP proxies and make generic UDP impossible.
In those environments, TURN over TCP or TLS can become necessary even though it is worse for real-time media. The operator chooses "works slowly" over "fails cleanly".
NAT64 and IPv6-only access networks complicate assumptions
An endpoint on an IPv6-only mobile network with NAT64 is not using the same address family path as a legacy IPv4 peer. WebRTC stacks handle mixed-family candidates, but the matrix of possible candidate pairs becomes richer.
This is one of the quiet reasons ICE exists as a general candidate pairing framework rather than as a NAT-only gadget. It also helps solve address-family and interface diversity.
Roaming and handover can invalidate a good path mid-session
A user starts a call on Wi-Fi in Brussels, walks outside, and the phone switches to LTE or 5G. The local address changes. The NAT mapping changes. The path may need ICE restarts or at least fresh checks.
NAT traversal is not only about session establishment. Mobility turns it into a session maintenance problem too.
Keepalives and consent freshness keep a good path from dying quietly
A successful candidate pair can still fail a minute later if the NAT binding expires. This is why real traversal stacks keep sending small control packets during the session.
There are two related jobs here.
Binding refresh
The first job is simple survival. If the NAT drops idle UDP state after 30 seconds and the application happens to be quiet for 35 seconds, the next media packet or data packet may vanish because the public mapping no longer exists.
Periodic traffic keeps the state alive. The exact mechanism varies by stack, but the principle is steady:
every N seconds:
send a small STUN packet or equivalent keepalive
refresh NAT mapping timeoutThis is why a muted call can continue working. Even if the user is not speaking, the session is still maintaining network state in the background.
Consent freshness
The second job is safety. A peer should not keep blasting UDP at an address forever just because it once worked. WebRTC therefore uses consent freshness checks. If the remote side stops responding to periodic STUN requests, the sender must eventually stop transmitting.
This protects against situations such as:
- the peer vanished from the network
- the device changed address during handover
- a NAT remapped the path elsewhere
- the remote application closed without a clean teardown
Without consent freshness, a sender could continue spraying traffic at a stale public tuple that now belongs to nothing useful, or worse, to a different host after a NAT recycled the port.
Consent freshness is therefore both a liveness signal and a correctness safeguard.
ICE restart exists because path identity can change
If a laptop moves from office Wi-Fi in Munich to tethering on a phone, the old candidates are no longer valid. The local addresses changed, the public mapping changed, and maybe the address family changed too.
ICE restart handles this by generating new credentials and candidates, exchanging them over signalling, and running a fresh set of checks without pretending the old path is still authoritative.
That is important for mobile and laptop workflows because network identity is no longer stable over the lifespan of a session. Traversal is not a one-time ceremony at the beginning. It is a path management system.
Debugging traversal failures means following candidate pairs, not just reading one error string
When a real-time session fails, the visible symptom is usually bland: "connecting" spins for too long, or the call connects with no audio, or video falls back to relay and becomes choppy. The useful answer is buried in candidate and check state.
An effective debugging workflow asks:
- Did both peers gather host, server-reflexive, and relay candidates as expected?
- Did signalling actually exchange them?
- Which candidate pairs were formed?
- Which checks were attempted?
- Which checks got responses?
- Which pair was nominated, if any?
- If relay was selected, was that because direct pairs failed or because relay simply won on timing or priority?
A practical checklist from browser logs or app telemetry often looks like:
local candidate gathered: host 192.168.1.23:50000
local candidate gathered: srflx 203.0.113.18:62001
local candidate gathered: relay 198.51.100.90:43000
remote candidate received: srflx 203.0.113.77:54022
check started: local srflx -> remote srflx
check failed: timeout
check started: local relay -> remote srflx
check succeeded
nominated pair: relay/srflxThat short trace already says a lot. Candidate gathering worked. Signalling worked. Direct traversal did not. Relay fallback saved the session.
If you never see a relay candidate, the problem may be TURN credentialing or allocation, not NAT behaviour. If you never see a remote candidate, the problem is signalling. If checks succeed and media still fails, the problem may be codec negotiation, SRTP setup, firewall treatment of larger packets, or consent freshness expiry later on.
Packet captures can still help, but only if you know what phase you are in
A capture on the client or relay can answer concrete questions:
- did STUN requests leave the host?
- did the STUN response return?
- did checks get retransmitted because of loss?
- did media start on the nominated tuple?
- did keepalives continue after apparent success?
The common mistake is to start reading random UDP without knowing whether you are looking at discovery, connectivity checks, relay allocation, or actual media. NAT traversal traffic is easier to debug once you map each packet to the phase that produced it.
Relay percentage is often the first clue of a regional issue
Operators sometimes discover a traversal regression not from support tickets, but from a sudden relay ratio increase in one region. If direct success collapses for users on one Spanish mobile network or one university Wi-Fi in the Netherlands, TURN usage spikes before customers can explain what changed.
That kind of metric is more actionable than a generic "session failure rate" because it shows where the system fell back rather than only where it died.
Traversal infrastructure must be designed defensively because it is public-facing UDP machinery
STUN and TURN infrastructure lives on the public internet and handles unauthenticated or semi-authenticated traffic early in the exchange. That carries security and abuse implications.
Open relays are an operational and abuse disaster
A TURN service must not become a free general-purpose packet forwarder. Authentication, permission rules, quotas, and rate limits exist because otherwise the relay can be abused for traffic laundering or reflection-style nuisance.
Even benign misuse hurts. A popular but misconfigured mobile app can accidentally pin large amounts of ordinary traffic through TURN because it always prefers relay. The cost lands on the operator immediately.
Reflection and amplification concerns are real enough to shape defaults
STUN responses are not giant, but any UDP service reachable from the public internet can be probed, spoofed against, or folded into scanning workflows. Implementations therefore care about message integrity, sane rate limits, and avoiding unnecessary amplification behaviour.
This is another reason modern TURN deployments often insist on authenticated allocations and short-lived credentials derived from backend-issued secrets rather than static public usernames floating around forever.
Logging needs a privacy boundary
Traversal logs may include public reflexive addresses, relay addresses, candidate types, network fingerprints, and timing data. Those logs are useful for debugging and abuse response, but they still describe user connectivity patterns.
That means retention, access control, and data minimisation matter. There is no reason for every engineer to have indefinite access to full candidate traces tied to user identity. Good operations practice here looks a lot like any other internet edge service: keep what is needed to run and secure the system, and no more.
Operating a traversal service means measuring path quality, not only success or failure
A production system should know more than whether calls connected. It should know how they connected.
Useful metrics include:
- percentage of sessions using direct host pairs
- percentage using server-reflexive pairs
- percentage using TURN relay
- median and p95 ICE completion time
- STUN transaction loss rate
- TURN allocation failures by region
- relay bandwidth by PoP and protocol
- consent freshness failures during active sessions
- session failure split by network type, browser, and region
Those metrics tell you where the pain really sits.
A relay ratio jump in Spain but not in France might point to a mobile operator CGNAT change. Rising ICE setup time only on one browser version may point to a client bug rather than a network issue. A TURN cluster in Frankfurt showing packet loss may hurt all of Central Europe even if signalling is healthy everywhere.
TURN deployment details matter more than teams expect
A basic coturn-style configuration idea might include:
listening-port=3478
tls-listening-port=5349
fingerprint
use-auth-secret
realm=turn.example.eu
total-quota=0
bps-capacity=0
no-tcp-relay=falseThe exact settings vary, but several choices matter:
- whether long-term credentials or ephemeral auth tokens are used
- whether UDP, TCP, and TLS relay are all supported
- how close the relay fleet is to major user populations
- how credentials are rotated
- how abuse is limited so the relay cannot be used as a general reflector
TURN servers are exposed public infrastructure. They need the same seriousness as any other internet-facing service.
IPv6 helps, but it did not make traversal logic disappear
It is tempting to think NAT traversal is a temporary IPv4 problem that IPv6 should have deleted. IPv6 does remove one major cause of the mess: there is far less pressure to hide many hosts behind one shared public address.
That helps, but it does not eliminate the need for ICE-style path selection.
Several things remain true even on dual-stack or IPv6-first networks:
- endpoints may still be behind firewalls that block unsolicited inbound traffic
- one side may be IPv4-only while the other is dual-stack
- the "best" path may still depend on interface choice, relay availability, or policy
- mobile and enterprise networks still create path asymmetry and timeout behaviour that direct addressing alone does not solve
In other words, globally routable addressing removes one obstacle, not the full coordination problem.
This is why modern real-time stacks still gather multiple candidate types in IPv6-capable environments. A host candidate on IPv6 may win immediately and make the session beautifully direct. On the next network, the same application may need a server-reflexive IPv4 path or a TURN relay. The orchestration framework stays useful because network diversity did not go away when address space got bigger.
NAT traversal is really a controlled search for the least-worst path
That description is not poetic. It is accurate.
The system is not trying to prove one elegant theorem. It is trying to answer a practical question under imperfect conditions:
- can the peers talk directly on local addresses?
- if not, can they talk through server-reflexive mappings?
- if not, can they talk through a relay?
- if they can talk directly now, will the path stay alive long enough?
- if the network changes, can the session recover?
This is why the full stack looks heavier than people expect. STUN alone would be too weak. TURN alone would be too expensive. Hole punching alone would be too fragile. ICE ties them together into a process that usually finds the cheapest path that actually works.
Once you view the problem that way, several common design choices make immediate sense:
- direct candidates are preferred because they avoid relay cost and latency
- relay candidates still exist because some networks will never allow direct success
- repeated checks and keepalives exist because NAT state expires and paths drift
- metrics focus on relay ratio and setup time because those numbers tell you whether the network reality is getting better or worse
NAT traversal is not a loophole. It is a negotiation with stateful middleboxes. The negotiation succeeds when both sides learn enough about their current public shape, create the right state at the right time, and fall back to a relay when the middleboxes refuse to cooperate.