NAT — Network Address Translation — is the trick that’s been keeping IPv4 alive for thirty years past its expiry date. The premise sounds illegal: take dozens of devices on a private network, point them at the internet through a single public IP address, and have everything come back to the right machine. The internet was not designed for this. NAT was bolted on, the standard was reverse-engineered from what the boxes were already doing, and yet it’s now so fundamental that most people don’t realise their home router is doing it.

This is lesson 15 of Networking from Scratch, the second deep-dive following the foundation tier. Lesson 2 mentioned NAT in passing as “why your laptop says 192.168.1.x but the internet sees your-public-ip.” This article unpacks how that actually works, the variants you’ll meet, why it makes some applications miserable, and the pile of workarounds the industry has built around it.
The problem NAT was invented to solve
RFC 1918 set aside three address ranges for private use: 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16. Anyone can use those addresses on their own network, as many times as they like. The catch: routers on the public internet don’t route them. A packet with a 10.x.x.x source or destination is dropped at the first ISP boundary.
So if you give all your internal hosts RFC 1918 addresses, they can talk to each other but they can’t talk to anything outside. The original design assumption was that you’d apply for a public block from an ISP for the hosts that needed external access. By the late 1990s that was no longer practical — ISPs were running out of public IPs to hand out, and a typical office didn’t need 256 of them anyway, just one shared one.
NAT solved this by making one router the gateway between the private and public worlds, rewriting addresses (and later ports) as packets crossed the boundary. From the outside, all your hosts looked like a single IP. From the inside, they each had their own private address. The router maintained a table that remembered which inside host owned which outside conversation, and unwound the translation on the way back.
The three flavours of NAT
Three NAT variants are described in RFC 3022 and almost every textbook. Modern home and SMB routers do exactly one of them; service-provider gear sometimes does a mix.
| Flavour | What it does | Where you see it |
|---|---|---|
| Static NAT (1:1) | One private address mapped to one public address, in both directions | Servers that need a stable public face |
| Dynamic NAT (pool) | N private addresses share a pool of M public addresses; first-come, first-served | Older corporate gateways with a small block of public IPs |
| PAT / NAPT / NAT overload | N private addresses share one public address by also rewriting ports | Every home router, SOHO firewall, mobile carrier (CGNAT) |
PAT — Port Address Translation, a.k.a. NAPT, a.k.a. NAT overload — is the variant that matters in practice. Static and dynamic NAT are still used, but PAT is what 99% of internet-edge translation looks like today, because it lets a single public IP serve hundreds of internal hosts.
How PAT actually works
The trick is that every TCP and UDP packet has not just a source IP and destination IP, but also a source port and destination port. The source port is essentially arbitrary — the kernel picks a number from the “ephemeral” range (typically 32768–60999 on Linux, 49152–65535 on Windows) when an outbound connection is made. The destination port is meaningful (80 for HTTP, 443 for HTTPS, etc.). PAT exploits the source port to multiplex.
Walk through one outbound flow:
- Inside host
10.0.0.5opens an HTTPS connection to198.51.100.10. The kernel picks ephemeral port51214. The packet leaving the host is10.0.0.5:51214 → 198.51.100.10:443. - The packet hits the NAT router. The router rewrites the source:
203.0.113.42:32100 → 198.51.100.10:443. The new source port (32100) is allocated by the router from its own pool. The router records the mapping in its connection-tracking table: “outbound TCP, public side203.0.113.42:32100↔ private side10.0.0.5:51214.” - The destination server replies. Its packet, addressed to
203.0.113.42:32100, arrives at the NAT router. - The router looks up
203.0.113.42:32100in the table, finds the mapping back to10.0.0.5:51214, rewrites the destination, and forwards the packet to the inside host.
From 10.0.0.5’s perspective, the connection is to 198.51.100.10:443 using its own port 51214 — identical to a direct connection. From 198.51.100.10’s perspective, the connection is from 203.0.113.42:32100. Neither end sees the rewriting; only the router knows.
The same router can be doing this for hundreds of inside hosts at once. Each one gets its own (public_ip, port) tuple. The 16-bit port space gives the router up to ~65,000 simultaneous outbound flows per public IP, which is plenty for a household but a real constraint for service-provider gear at scale.
The conntrack table
The data structure that makes all this work is the connection-tracking table — conntrack on Linux, the “NAT table” on most consumer routers. Each entry records:
- Inside (5-tuple): private source IP, source port, destination IP, destination port, protocol
- Outside (5-tuple): public source IP, allocated source port, destination IP, destination port, protocol
- Protocol-specific state (for TCP: SYN_SENT, ESTABLISHED, FIN_WAIT, etc.)
- Idle timer (so dead flows can be reaped)
On Linux you can see it directly:
$ sudo conntrack -L | head
tcp 6 431999 ESTABLISHED src=10.0.0.5 dst=198.51.100.10 sport=51214 dport=443 \
src=198.51.100.10 dst=203.0.113.42 sport=443 dport=32100 \
[ASSURED] mark=0 use=1
udp 17 29 src=10.0.0.5 dst=8.8.8.8 sport=58112 dport=53 \
src=8.8.8.8 dst=203.0.113.42 sport=53 dport=58112 \
[UNREPLIED] mark=0 use=1
Two important properties of conntrack entries:
- They time out. A long-idle TCP connection (default ~5 days on Linux, much shorter on consumer routers, often 1–5 minutes for UDP) gets evicted. If the inside host then tries to send another packet on the same flow, the NAT no longer has the mapping — from the outside server’s perspective it looks like a new connection, which usually fails. This is why long-lived TCP connections (SSH, persistent HTTP) often need keepalives behind a NAT.
- They’re finite. A consumer router has space for maybe 4,000–16,000 concurrent flows. A torrent client, BitTorrent tracker, or chatty IoT device can fill the table and starve other applications. “The internet feels slow” on a small router is sometimes a full conntrack table.
SNAT vs DNAT
Two directions of translation, both implemented with conntrack:
- SNAT — Source NAT. Rewriting the source address/port. PAT is a flavour of SNAT. Used for outbound-from-private to make hosts look like the gateway.
- DNAT — Destination NAT. Rewriting the destination address/port. Used for inbound-to-private when you want an external request to reach an internal server. Port forwarding is just a DNAT rule plus the matching firewall opening.
A typical rule on a Linux router using nftables:
# SNAT: rewrite outbound packets from 10.0.0.0/24 so they look like the WAN IP
add rule ip nat postrouting oifname "eth0" ip saddr 10.0.0.0/24 masquerade
# DNAT: forward incoming TCP/443 to the internal web server at 10.0.0.20
add rule ip nat prerouting iifname "eth0" tcp dport 443 dnat to 10.0.0.20:443
The classic gotcha with port forwarding is the firewall: setting up the DNAT rewrites the destination, but if the router’s firewall is dropping inbound TCP/443, the rewrite never gets a chance to matter. Always check both layers.
The NAT problems we’ve been working around
NAT was never an architecturally clean solution. It violates the end-to-end principle that originally defined the internet. Three categories of side-effects keep generating workarounds:
Inbound connections don’t work
By default, a NAT only has a mapping for flows the inside host has initiated. If a peer on the internet wants to connect to your laptop, there’s no entry in the conntrack table for that connection — so the router has nowhere to send the packet, and drops it. This breaks any application that wants direct peer-to-peer connectivity: VoIP, video calling, gaming, file sharing.
Workarounds:
- Port forwarding (DNAT) — manual, fine for a single server, doesn’t scale
- UPnP / NAT-PMP — the application asks the router to open a forwarding rule on its behalf
- STUN — the application discovers what its public address looks like by asking a server
- TURN — if STUN doesn’t work because the NAT is too restrictive, fall back to relaying everything through a server
- ICE — combine STUN and TURN systematically; this is how WebRTC works
- Hole punching — both ends connect outbound to a rendezvous server simultaneously, in the hope the NATs each create a mapping that the other end can ride back through
WebRTC, the technology behind every modern video calling app, is essentially “a programmer’s answer to NAT.” It exists primarily because NAT exists.
Protocols that embed addresses inside the payload break
FTP’s active mode, SIP, H.323, and a few others put IP addresses inside the application-layer payload — that is, inside the HTTP-like protocol body. NAT rewrites the IP and port in the IP and TCP/UDP headers, but it doesn’t know to also rewrite copies of those addresses living inside the application data. So the payload says one thing and the headers say another and the conversation fails.
The hack: Application-Layer Gateways (ALGs). The router gets protocol-specific helpers (nf_conntrack_ftp, nf_conntrack_sip, etc.) that read the payload and rewrite the embedded addresses too. ALGs are notorious for breaking things in subtle ways, especially with VoIP, and many engineers reflexively disable them.
Source-port collisions on shared public IPs
Two inside hosts both pick ephemeral source port 51214 at the same moment. The NAT now needs two different outside-port mappings for what looks identical from the inside. The router invents new outside ports (32100 and 32101) and updates conntrack accordingly. Mostly transparent — until you’re behind CGNAT and tens of thousands of customers are sharing one /24, in which case some applications notice their outbound port keeps changing and behave badly.
CGNAT: NAT inside NAT
Carrier-Grade NAT is what your mobile carrier almost certainly does. Your phone gets an RFC 6598 address (100.64.0.0/10, the “shared address space” reserved specifically for CGNAT), the carrier’s NAT translates it to an actual public IP shared by thousands of customers, and only at the edge of the carrier network does the packet hit the public internet.
From the device’s perspective, this looks exactly like a home NAT — private addresses, no inbound connectivity. The new wrinkle: two layers of NAT mean your packets go through two conntrack tables, port forwarding requires the carrier’s cooperation (which you don’t have), and STUN/TURN often have to be more aggressive. CGNAT is why mobile peer-to-peer is harder than wired peer-to-peer.
NAT and IPv6
IPv6 designs NAT out of the protocol on purpose. With 2128 addresses, every device can have a real, globally routable address and there’s no need to multiplex. The end-to-end principle returns. Hole-punching, ALGs, and the rest of the NAT-traversal toolkit become unnecessary.
In practice, even on IPv6 networks people often deploy a stateful firewall that looks like NAT (it remembers outbound flows and blocks unsolicited inbound) without actually rewriting addresses. The firewalling part is good security hygiene; the address rewriting was always a workaround we’re glad to drop.
The official IPv6 NAT variant is NAT66, defined in RFC 6296. It exists for niche cases (multi-homing without provider-independent addresses, ISP renumbering, hiding internal topology) but most IPv6 deployments don’t use it. If you find yourself reaching for NAT66, double-check there isn’t a less painful solution.
What you can now answer
- Why does my home network use
192.168.1.x? — RFC 1918 private addresses; the router NATs you to its single public IP. - What’s the difference between NAT and PAT? — PAT (a.k.a. NAT overload) also rewrites the source port, which is what lets one public IP serve many inside hosts.
- What does the NAT table actually store? — A mapping for every active flow: private 5-tuple, public 5-tuple, protocol state, idle timer.
- Why is video calling so hard? — NAT blocks inbound by default; WebRTC’s STUN/TURN/ICE exist to work around it.
- What’s CGNAT? — The carrier doing NAT on top of yours, sharing one public IP across thousands of mobile subscribers.
- Why does IPv6 not need NAT? — Enough addresses to give every device a real one, so you don’t need to multiplex.
What’s next
Lesson 16 covers VLANs and 802.1Q trunking — the way one physical switch becomes many logical networks, and how trunk ports carry tagged frames between switches. Then come Wi-Fi tuning, network troubleshooting tools in earnest, and the three hands-on labs that close out the pathway.