How network address translation shares one IP across many devices.
NAT extended the life of IPv4 by decades. It rewrites headers and tracks state so that hosts on a private network can reach the public internet without owning public addresses.
What is NAT?
Network Address Translation (NAT) rewrites packet headers so many private hosts can share one public IP. RFC 1631 (1994) described it; today home routers and most enterprise gateways do NAT. STUN, TURN, and ICE are the protocols WebRTC uses to traverse NATs; CGNAT is what mobile carriers run.
The IPv4 address space holds about 4.3 billion addresses, and there are far more devices than that. NAT (formalised in RFC 3022) is the workaround. A router rewrites packet source addresses on the way out, records the original mapping and reverses it on the reply, so a home with fifteen devices looks like one host to the public internet.
NAT has costs. It breaks end-to-end addressing, which the IP layer was designed around. It complicates protocols that embed addresses in payloads (FTP, SIP). It also adds state to a protocol designed to be stateless. It has nonetheless worked since the mid-1990s.
How NAT rewrites a packet
One private host opens a connection to a public service. Step through the frames to see the source and destination rewritten on the way out and the mapping looked up on the way in.
| internal | external port | peer |
|---|---|---|
| 10.0.1.4:50122 | 41010 | 8.8.8.8:443 |
Source NAT vs Destination NAT: two directions, two purposes
SNAT (source NAT) rewrites the source address on the way out. This is the classic case: many private hosts share a public IP for outbound traffic. Cloud NAT GWs, home routers, and cellular CGNAT all do SNAT.
DNAT (destination NAT) rewrites the destination on the way in. This is how you publish a service: external traffic arrives at the public IP, the router rewrites the destination to a private host. Port forwarding, AWS load balancer health checks, Kubernetes Service IPs all rely on DNAT.
Hide the inside
Many sources collapse to one or a few public IPs. State is keyed by (src, dst, src-port, dst-port). On a reply, the router looks up the external port and restores the original address.
Expose one inside host
Public IP:port maps to a specific internal host:port. The mapping is configured in advance rather than created dynamically. It is often paired with SNAT so internal hosts see the load balancer rather than the client.
PAT (port-based NAT): one IP, tens of thousands of conversations
If many hosts share one public IP, how does the router know which reply belongs to which inside host? It uses the source port. NAT rewrites the source port to a unique value (typically picked from the ephemeral range, 1024–65535) and remembers the mapping in something close to a hash table. The reply, addressed to that external port, gets translated back.
This caps a single public IP at roughly 64,000 simultaneous flows to any single destination port pair. AWS NAT Gateway gets around this with multiple ENIs and 55,000 ports per IP; CGNAT carriers shard ranges; busy egress fleets need pools of EIPs. Run out of ports and connections start failing in confusing ways.
# A single Linux box doing PAT with iptables MASQUERADE iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE # Conntrack table, one row per active flow conntrack -L -p tcp --dport 443 | head -3 tcp 6 src=10.0.1.4 sport=50122 dst=8.8.8.8 dport=443 [ASSURED] src=8.8.8.8 sport=443 dst=203.0.113.5 dport=41010
NAT is stateful: the connection table
Conntrack stores one entry per active flow with timers: TCP idle, UDP unreplied, ICMP ping. Linux's net.netfilter.nf_conntrack_max bounds the table. Once the limit is hit, new connections fail, often with unexplained resets. Long-lived TCP idles are kept alive by an ESTABLISHED timer (5 days by default, 350s in many cloud NATs): TCP keepalives are required if you want to span that.
UDP is harder because the router has no FIN to learn from. It uses a short timer (30s default) and expects either side to send something periodically. VoIP, gaming and DTLS protocols send keepalives to keep NAT mappings alive.
NAT traversal: reaching behind a NAT with STUN, TURN, and ICE
NAT blocks unsolicited inbound traffic, which is a serious problem for peer-to-peer apps (long-lived sockets, WebRTC video, BitTorrent, gaming, mesh VPNs). There are three workarounds, from least to most costly:
Discover your public address
Ask a public server "what address do I look like to you?" This helps when both peers are behind cone NATs, because each can learn the other's external (IP, port) and connect directly.
Relay traffic when direct fails
Both peers connect outbound to a relay server. It always works but doubles the bandwidth on the relay, so it is the fallback when STUN cannot punch through.
Negotiate the best path
Gathers all candidates (host, srflx, relay), exchanges them via signaling, runs connectivity checks and picks the best working path. WebRTC uses it for every connection.
Carrier-grade NAT (CGNAT): two layers of translation
Mobile carriers and cable ISPs ran out of public IPv4 long ago. Their solution is to put another NAT in front of yours. Your home router NATs your devices to a 100.64.0.0/10 (RFC 6598) carrier-private address; the carrier's CGNAT rewrites that to a real public IP. Two translations, two state tables.
CGNAT blocks most inbound traffic. Port forwarding does not work, peer-to-peer punches through inconsistently, and game console NAT types degrade. IPv6 is the real fix, since every device has a public address again and NAT becomes optional. IPv6 carries about half of Google's traffic, with large differences by region, so NAT will remain part of the internet for a long time.
NAT types: Full Cone, Restricted, Port-Restricted, Symmetric
RFC 3489 classified NAT behaviour into four types. The classification matters because peer-to-peer protocols (WebRTC, BitTorrent, multiplayer games) succeed or fail based on which combination two endpoints have:
- Full Cone
- Once an internal host sends out, any external host can send back to that mapped public IP/port. This is the most permissive type and the easiest for peer-to-peer, but it is rare in practice.
- Restricted Cone
- Inbound is allowed only from external IPs the internal host previously sent to. Different external port from the same IP is OK.
- Port-Restricted Cone
- Inbound is allowed only from the exact (external IP, external port) pair the internal host previously sent to. Most home routers default here.
- Symmetric NAT
- The mapped public port is unique per destination. Sending to two different external hosts produces two different public ports for the same internal socket. Simple STUN usually cannot traverse it, so it needs TURN relays. It is common on mobile carrier networks (CGNAT) and corporate firewalls.
Effect on WebRTC. Two Symmetric NATs cannot connect peer-to-peer. WebRTC's ICE protocol falls back to a TURN relay (a server in the middle that both peers can reach), at the cost of adding ~30-100ms of latency and consuming server bandwidth. Public TURN services (Twilio, Xirsys) charge per GB. Many production WebRTC deployments plan for around 10–15% of connections to fall back to TURN.
Two phones, two NATs, one video call
Phone A sits at 192.168.1.7 behind a home router whose public address is 198.51.100.20. Phone B sits at 192.168.0.12 behind a different router at 203.0.113.74. They want to send video directly to each other, but neither can dial in. Each phone's address is private and means nothing outside its own LAN, and each router drops unsolicited inbound packets. A packet from B aimed at 198.51.100.20 arrives at router A, which checks its connection table, finds no mapping and discards it.
STUN addresses the first half of the problem, which is that neither phone knows its address as seen from outside. Phone A sends a UDP packet to a public STUN server. The server reads the source address off the packet as it arrived (say 198.51.100.20:62004, the mapping router A just created) and writes it back in the reply. Now A knows its public address. Both phones do this, swap the results through the call's signaling server (an ordinary outbound connection, so NAT permits it), then fire UDP packets at each other's discovered address. A's outbound packet toward B's public address opens a mapping in router A that admits B's incoming packets, and B's does the same on its side. Within a few round trips both directions are open, which is called hole punching.
Symmetric NAT breaks this. The mapping A learned from STUN was created for the path to the STUN server. A symmetric NAT allocates a fresh external port per destination, so when A sends to phone B it gets a different port, and the address B is aiming at is not valid for this conversation. B's packets arrive at a port with no mapping behind it and are dropped. Port-prediction tricks sometimes work against a NAT that allocates sequentially, but against most symmetric NATs (common on mobile carriers and corporate firewalls) a direct connection is not possible.
TURN is the fallback. Both phones open outbound connections to a relay server on the public internet, and the relay forwards media between them. To each router this is ordinary outbound traffic, so it always works, at the cost of every video frame passing through a server someone has to pay for, plus the extra hop's latency. ICE runs the whole ladder automatically: gather candidates, try direct paths first, fall back to the relay only when punching fails.
Most calls still connect directly because most home routers are cone NATs, where one socket gets one public mapping regardless of destination. The address STUN discovers is therefore the address that works, and hole punching succeeds.
NAT in production: three case studies
Tailscale: punching holes through NAT. Tailscale's mesh VPN connects user devices that all sit behind different NATs. Their "How NAT Traversal Works" blog post (2020) is a good modern reference. It reports that most connections succeed peer-to-peer using STUN-style discovery, while the rest use Tailscale's DERP relay servers (their TURN equivalent), trading latency for connectivity.
AWS NAT Gateway: managed CGNAT. AWS's NAT Gateway service costs ~$0.045/hour plus $0.045/GB processed (us-east-1, 2024): roughly $32/month per gateway plus traffic. At Pinterest scale (terabytes per day egress), NAT Gateway charges run into hundreds of thousands of dollars annually; many shops route private-subnet egress through their own EC2-based NAT instances or use VPC endpoints to avoid NAT entirely for AWS-API traffic.
Mobile carriers: CGNAT shaping the modern internet. Most US mobile carriers (Verizon, T-Mobile, AT&T) put subscribers behind CGNAT. The IPv4 address you see your phone use is shared with hundreds or thousands of other subscribers. This is why home-server access from mobile is brittle, why some game-console NAT types are stuck on "Strict," and why IPv6 deployment was driven hardest by mobile networks (T-Mobile reached >95% IPv6 traffic by 2020).
NAT started as a workaround and became infrastructure. It tracks per-flow state, it blocks unsolicited inbound traffic, and protocols like ICE and WebRTC exist to work around it.
Further reading on NAT
- RFC 3022Traditional IP Network Address TranslatorThe NAT spec. Short and still readable.
- RFC 8445ICE: Interactive Connectivity EstablishmentHow WebRTC traverses NATs. The procedure is complex, and the spec is the complete reference.
- Semicolony guideVPC networkingWhere the cloud NAT GW lives, and what it costs.
- Semicolony guideTCP in detailThe state machine NAT has to track.