Skip to content
Menu

Networking7 min read

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

RangeCIDRAddressesCommon Usage
10.0.0.010.255.255.255 10.0.0.0/8 16.7M Cloud VPCs, large enterprise networks, K8s pod CIDRs
172.16.0.0172.31.255.255 172.16.0.0/12 1M Docker default bridge (172.17.0.0/16), some VPCs
192.168.0.0192.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

💻
Host A
192.168.1.10
💻
Host B
192.168.1.11
📨
NAT Router
LAN: 192.168.1.1
WAN: 203.0.113.5
🌐
Internet
Public
🖥
Web Server
93.184.216.34
  1. Host A sends a packet:
    src=192.168.1.10:44820 → dst=93.184.216.34:443
  2. 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
  3. Web server replies to the public IP:
    src=93.184.216.34:443 → dst=203.0.113.5:61234
  4. NAT router looks up conntrack, rewrites destination:
    src=93.184.216.34:443 → dst=192.168.1.10:44820

Packet Transformation

Before NAT (LAN side)
Src: 192.168.1.10 Private IP
Dst: 93.184.216.34 Public IP
Sport: 44820 Ephemeral
Dport: 443 HTTPS
After NAT (WAN side)
Src: 203.0.113.5 Public IP (rewritten!)
Dst: 93.184.216.34 Unchanged
Sport: 61234 Remapped
Dport: 443 Unchanged

MASQUERADE vs SNAT

FeatureSNATMASQUERADE
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

console
# 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=1

Note

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.

🌐
Internet Client
Requests 203.0.113.5:80
🛡
NAT Router
DNAT 80 → 192.168.1.10:80
🖥
Web Server
192.168.1.10:80
console
# 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:443

Tip

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.

console
console

    $ 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

💻
Your Device
192.168.1.10
📨
Home Router
WAN: 100.64.1.17
(not a real public IP!)
📨
ISP CGNAT
Public: 203.0.113.1
(shared by hundreds)
🌐
Internet

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.x through 100.127.x.x, you're behind CGNAT
  • Your ISP assigns you a 100.64.x.x address, then NATs that to a shared public IP
  • Hundreds or thousands of customers share the same public IP

Warning

What CGNAT breaks:
  • 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.

Without NAT (End-to-End)
  • 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
With NAT (Broken End-to-End)
  • 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

  1. Outbound: Works

    192.168.1.10 → NAT → Internet
    NAT creates conntrack entry
    Return traffic follows the mapping

  2. Inbound: Blocked

    Internet → NAT → ???
    No conntrack entry exists
    NAT doesn't know which device to forward to

Note

This is THE key problem. Devices behind NAT can initiate connections outward, but cannot receive unsolicited inbound connections. This is why:
  • 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

TechniqueHow It WorksReliability
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

OperationWhat Changesiptables Chainiptables TargetUse 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
Solidnines — solidnines.com