NAT
Network Address Translation — the IPv4 survival hack
Why NAT Exists
IPv4 has ~4.3 billion addresses. There are ~20+ billion connected devices. NAT is the hack that made this work — it lets thousands of devices share a single public IP address. Without NAT, IPv4 would have been exhausted in the late 1990s.
RFC 1918 — Private Address Ranges
| Range | CIDR | Addresses | Common Usage |
|---|---|---|---|
10.0.0.0 – 10.255.255.255 |
10.0.0.0/8 |
16.7M | Cloud VPCs, large enterprise networks, K8s pod CIDRs |
172.16.0.0 – 172.31.255.255 |
172.16.0.0/12 |
1M | Docker default bridge (172.17.0.0/16), some VPCs |
192.168.0.0 – 192.168.255.255 |
192.168.0.0/16 |
65K | Home routers, small office networks |
Note
These addresses are not routable on the public internet. Any router on the internet will drop packets with RFC 1918 source/destination addresses. NAT is the translation layer that maps these private addresses to public ones.
SNAT (Source NAT) / Masquerading
SNAT rewrites the source IP of outbound packets. This is how many private hosts share one public IP for internet access — the most common form of NAT.
How SNAT Works — Step by Step
WAN: 203.0.113.5
- Host A sends a packet:
src=192.168.1.10:44820 → dst=93.184.216.34:443 - NAT router rewrites source and records mapping:
src=203.0.113.5:61234 → dst=93.184.216.34:443
Conntrack entry:192.168.1.10:44820 ↔ 203.0.113.5:61234 - Web server replies to the public IP:
src=93.184.216.34:443 → dst=203.0.113.5:61234 - NAT router looks up conntrack, rewrites destination:
src=93.184.216.34:443 → dst=192.168.1.10:44820
Packet Transformation
MASQUERADE vs SNAT
| Feature | SNAT | MASQUERADE |
|---|---|---|
| Source IP | Explicitly specified (--to-source 203.0.113.5) |
Auto-detected from outbound interface |
| Use case | Static public IP (servers, VPN gateways) | Dynamic IP (DHCP, PPPoE, home routers) |
| Performance | Slightly faster (no interface lookup) | Slightly slower (checks interface IP per packet) |
| Conntrack behavior | Entries survive interface IP changes (stale!) | Entries cleared when interface IP changes |
iptables SNAT / MASQUERADE Rules
# SNAT with a static public IP
iptables -t nat -A POSTROUTING \
-s 192.168.1.0/24 \
-o eth0 \
-j SNAT --to-source 203.0.113.5
# MASQUERADE with dynamic IP (typical for home/VPN setups)
iptables -t nat -A POSTROUTING \
-s 192.168.1.0/24 \
-o eth0 \
-j MASQUERADE
# Don't forget: enable IP forwarding!
sysctl -w net.ipv4.ip_forward=1Note
Docker uses MASQUERADE. When a container sends traffic to the internet, Docker's iptables rules MASQUERADE the container's IP (172.17.x.x) behind the host's IP. Check with iptables -t nat -L POSTROUTING — you'll see the Docker chain.
DNAT (Destination NAT) / Port Forwarding
DNAT rewrites the destination IP/port of inbound packets. This is how you expose an internal service on a public IP — the reverse of SNAT.
# Port forward: public :80 → internal 192.168.1.10:80
iptables -t nat -A PREROUTING \
-p tcp --dport 80 \
-j DNAT --to-destination 192.168.1.10:80
# You also need to allow the forwarded traffic through the filter table
iptables -A FORWARD \
-p tcp -d 192.168.1.10 --dport 80 \
-m conntrack --ctstate NEW,ESTABLISHED \
-j ACCEPT
# Port remapping: public :8443 → internal :443 (different ports)
iptables -t nat -A PREROUTING \
-p tcp --dport 8443 \
-j DNAT --to-destination 192.168.1.10:443Tip
This is exactly what kubectl port-forward does NOT use. Port-forward creates a userspace TCP proxy, not iptables DNAT. But Kubernetes Services (ClusterIP/NodePort) do use DNAT in the nat table's PREROUTING/OUTPUT chains. That's why iptables -t nat -L on a K8s node shows hundreds of DNAT rules — one per Service endpoint.
Conntrack Entries for NAT
Every NAT translation creates a conntrack entry that stores the bidirectional mapping. This is how the kernel knows to reverse the translation for return traffic.
$ conntrack -L -n
tcp 6 117 ESTABLISHED src=192.168.1.10 dst=93.184.216.34 sport=44820 dport=443
src=93.184.216.34 dst=203.0.113.5 sport=443 dport=61234 [ASSURED] mark=0
# ↑ Two tuples per entry:
# Tuple 1 (original): what the LAN host sent
# Tuple 2 (reply): what the reply should look like BEFORE un-NATing
Reading Conntrack Entries
Original direction src=192.168.1.10 dst=93.184.216.34 ↔ Reply direction src=93.184.216.34 dst=203.0.113.5
Notice dst differs between tuples — that's the SNAT mapping. The kernel uses the reply tuple to match incoming packets and reverse the translation.
CGNAT (Carrier-Grade NAT)
CGNAT is NAT applied by your ISP, putting your entire home network behind yet another layer of NAT. Your home router does NAT (private → home public IP), then the ISP does NAT again (home public IP → shared ISP IP).
Double NAT Topology
(not a real public IP!)
(shared by hundreds)
CGNAT Details
- RFC 6598 range:
100.64.0.0/10— reserved for CGNAT (the "shared address space") - If your home router's WAN IP starts with
100.64.x.xthrough100.127.x.x, you're behind CGNAT - Your ISP assigns you a
100.64.x.xaddress, then NATs that to a shared public IP - Hundreds or thousands of customers share the same public IP
Warning
- Port forwarding — you can configure it on your router, but the ISP's NAT won't forward to you
- Self-hosting — no way to receive inbound connections without workarounds
- P2P protocols — BitTorrent, direct VoIP, game hosting all suffer
- IP-based rate limiting — thousands of users share one IP, so IP bans and rate limits are useless
- Geolocation — the shared IP may geolocate to the ISP's CGNAT node, not your location
NAT Breaks End-to-End Connectivity
This is the fundamental architectural problem that NAT introduces, and understanding it is essential for grasping why VPNs, overlay networks, and NAT traversal techniques exist.
The End-to-End Principle (Pre-NAT Internet)
The original internet design: any host can send a packet directly to any other host. Every device has a globally unique IP. No translation needed. A web server can initiate a connection to your laptop just as easily as you can connect to it.
- Every device has a public IP
- Any device can connect to any other
- Inbound connections just work
- No state to track, no translations
- This is how IPv6 works
- Devices behind NAT have private IPs
- Outbound works (NAT creates mapping)
- Inbound fails — no mapping exists yet
- Must use port forwarding, STUN, relays
- This is why Tailscale needs DERP relays
The Inbound Connection Problem
Outbound: Works
192.168.1.10 → NAT → Internet
NAT creates conntrack entry
Return traffic follows the mappingInbound: Blocked
Internet → NAT → ???
No conntrack entry exists
NAT doesn't know which device to forward to
Note
- Tailscale/WireGuard need NAT traversal (UDP hole punching, STUN) or relay servers (DERP)
- WebRTC needs ICE/STUN/TURN for peer-to-peer browser connections
- SSH reverse tunnels exist — the device behind NAT connects out first, then the tunnel is used in reverse
- Kubernetes uses overlay networks partly to avoid NAT issues between pods on different nodes
NAT Traversal Techniques
| Technique | How It Works | Reliability |
|---|---|---|
| UDP Hole Punching | Both peers send UDP to each other simultaneously; NAT creates bidirectional mapping | Works with most NATs, fails with symmetric NAT |
| STUN | Public server tells you your NAT's external IP:port mapping | Discovery only — doesn't help with symmetric NAT |
| TURN / Relay | All traffic goes through a relay server (both peers connect outbound to it) | Always works, but adds latency and relay cost |
| Port Forwarding | Manually configure NAT to forward a port to an internal host | Works only if you control the NAT device (impossible with CGNAT) |
| UPnP / NAT-PMP | Automatically request port forwarding from the NAT device | Often disabled for security; doesn't help with CGNAT |
Tip
Tailscale's approach: Try UDP hole punching first (via coordination server + STUN-like discovery). If that fails (e.g., both peers behind symmetric NAT or CGNAT), fall back to DERP relay servers. The DERP protocol is just encrypted WireGuard packets tunneled over HTTPS — which works through any NAT/firewall that allows outbound HTTPS.
NAT Operations Summary
| Operation | What Changes | iptables Chain | iptables Target | Use Case |
|---|---|---|---|---|
| SNAT | Source IP | POSTROUTING |
SNAT |
Many-to-one outbound (static IP) |
| MASQUERADE | Source IP | POSTROUTING |
MASQUERADE |
Many-to-one outbound (dynamic IP) |
| DNAT | Destination IP/port | PREROUTING |
DNAT |
Port forwarding, K8s Services |
| REDIRECT | Destination to localhost | PREROUTING |
REDIRECT |
Transparent proxies, Istio sidecar |