Chapter 12 — Private addresses and NAT
In one sentence
There aren't enough IPv4 addresses in the world, so your home devices use "private" addresses that mean nothing on the internet, and your router rewrites them on the way out — that rewriting is called NAT.
The problem
IPv4 has ~4.3 billion addresses (Chapter 9). There are far more devices than that. Your house alone might have 20. Yet it works. How?
The idea, in plain language
Like phone extensions in an office.
The office has ONE public phone number: +44 20 7946 0000
Inside, everyone has an extension: 101, 102, 103...
Outbound: you call out; the other party sees the OFFICE number.
Inbound: the receptionist remembers who called out and connects
the return call to the right extension.
Your router is the receptionist. Your devices have extensions (private IPs).
The private address ranges (memorise these)
These three ranges (defined in RFC 1918) are reserved for internal use and are never routed on the public internet:
10.0.0.0 to 10.255.255.255 (10.0.0.0/8) ~16.7 million addresses
172.16.0.0 to 172.31.255.255 (172.16.0.0/12) ~1 million
192.168.0.0 to 192.168.255.255 (192.168.0.0/16) ~65 thousand
You'll also meet:
100.64.0.0/10 Carrier-Grade NAT (CGNAT) -- your ISP NATs you too
Everyone uses the same private addresses. There are millions of homes with a
192.168.1.1 router. That's fine, because those addresses never leave the house.
How NAT actually works
Say your laptop (192.168.1.50) opens a connection to a web server:
INSIDE YOUR HOUSE | THE INTERNET
|
laptop 192.168.1.50 |
| |
| from: 192.168.1.50 : 51000 |
| to: 93.184.216.34 : 443 |
v |
+---------+ |
| ROUTER | rewrites the source |
| (NAT) |----------------------->| from: 203.0.113.7 : 62000
+---------+ | to: 93.184.216.34 : 443
^ |
| and remembers: |
| 203.0.113.7:62000 <--> 192.168.1.50:51000
| |
| reply arrives for |
| 203.0.113.7:62000 |
| -> looks it up |
| -> rewrites back to |
| 192.168.1.50:51000 |
The router keeps a NAT table of these mappings. When several devices connect to the same server, the router gives each a different outside port number so it can tell the replies apart. (Ports are Chapter 19 — for now, just a number that distinguishes connections.)
Because it translates ports as well as addresses, this is properly called PAT (Port Address Translation) or NAT overload. Almost everyone just says "NAT".
What NAT gives you, and what it costs
Good:
- Many devices share one public address (the whole point).
- An accidental firewall: unsolicited traffic from the internet has no mapping to look up, so it's dropped. This is why home devices aren't immediately attacked.
Bad:
- Incoming connections don't work without explicit configuration. You can't run a server at home without port forwarding.
- Peer-to-peer is hard. Two devices both behind NAT can't easily connect — which is why video calls need extra machinery (STUN/TURN servers).
- The internet stopped being end-to-end. Any protocol that puts an IP address inside its data (old FTP, SIP) breaks unless the router specially understands it.
- CGNAT makes it worse. Many ISPs now NAT you as well, so you're behind two layers and your public IP is shared with strangers. Port forwarding becomes impossible.
Practice (20 min)
A. See both of your addresses:
# your PRIVATE address (what your device thinks it is)
ip -4 addr show | grep inet # Linux
ipconfig getifaddr en0 # macOS
# your PUBLIC address (what the internet sees)
curl -s ifconfig.me ; echo
They're different. Everyone in your house shows the same public one. Try it on your phone (on Wi-Fi) to confirm.
B. Find your router's NAT table (if it's a Linux box or you can log into your home router's web UI — look for "NAT", "connections", or "active sessions"):
# On a Linux machine acting as a router:
sudo conntrack -L | head
C. Prove NAT blocks inbound. Start a simple server on your laptop:
python3 -m http.server 8000
- From another device in your house:
http://<your-private-ip>:8000→ works. - From outside (mobile data, or ask a friend):
http://<your-public-ip>:8000→ fails, because no NAT mapping exists. That's the accidental firewall.
D. Check whether you're behind CGNAT: compare curl ifconfig.me with the WAN
address shown in your router's admin page. If your router's WAN address is in
100.64.x.x–100.127.x.x, you are behind carrier-grade NAT.
Build it in Go (15 min) — see your own NAT
Two questions decide whether you're behind NAT: what address does my machine think it has? and what address does the internet see? This program asks both.
// whoami: show the NAT in front of you by comparing the address your machine
// THINKS it has with the address the internet SEES.
//
// go run ./whoami
package main
import (
"context"
"fmt"
"io"
"net"
"net/http"
"strings"
"time"
)
func main() {
// 1. Which local address would the kernel use to reach the internet?
// "Dialing" UDP sends no packets -- it only asks the kernel to pick a
// route and a source address, which we can then read back.
c, err := net.Dial("udp", "1.1.1.1:53")
if err != nil {
fmt.Println("no route to the internet:", err)
return
}
local := c.LocalAddr().(*net.UDPAddr).IP
c.Close()
fmt.Println("private (what you think you are): ", local)
// 2. Ask a server on the internet which address our packets arrived from.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.ipify.org", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("could not reach api.ipify.org:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(io.LimitReader(resp.Body, 64))
public := net.ParseIP(strings.TrimSpace(string(body)))
fmt.Println("public (what the internet sees): ", public)
// 3. Interpret.
switch {
case public == nil:
fmt.Println("unexpected answer:", string(body))
case local.Equal(public):
fmt.Println("=> no NAT: your machine holds a public address directly")
case isCGNAT(local):
fmt.Println("=> carrier-grade NAT: even your 'router' address is shared (100.64.0.0/10)")
default:
fmt.Println("=> NAT: a router rewrote your source address on the way out")
}
}
func isCGNAT(ip net.IP) bool {
_, cgnat, _ := net.ParseCIDR("100.64.0.0/10")
return cgnat.Contains(ip)
}
$ go run ./whoami
private (what you think you are): 192.168.1.2
public (what the internet sees): 122.171.18.134
=> NAT: a router rewrote your source address on the way out
What to notice: net.Dial("udp", ...) sends no packet. UDP has no
handshake, so "connecting" a UDP socket only asks the kernel to pick a
route and a source address. Reading LocalAddr() afterwards is a standard
trick for finding "my outbound IP" without parsing routing tables.
Exercises:
- Run it on home Wi-Fi, then on a phone hotspot. On mobile networks you'll
often get the carrier-grade NAT result (
100.64.0.0/10), or a public address that thousands of other customers share at the same moment. - Run it inside a Docker container (
docker run --rm -v "$PWD":/w -w /w golang:1.26 go run ./whoami). You'll see an extra layer of NAT: container → host → router. - Why does this matter for software? Read the "rate limiting by IP" point in the OSI guide's Layer 3 chapter, then decide what your service should use as a client identity.
Common confusions
- "NAT is a firewall." It behaves like one for inbound traffic, but it isn't designed as a security control and shouldn't be your only one. Use an actual firewall (Chapter 38).
- "Why does my private IP change?" DHCP leases (Chapter 17). Set a reservation in your router if you need it stable.
- "Two networks both use 192.168.1.0/24 — can I VPN between them?" Not
easily; the addresses collide. This is a very common real-world headache, and
the reason experienced people pick unusual ranges like
10.37.129.0/24for home labs. - "Does IPv6 use NAT?" Generally no — there are enough addresses that every device can have a public one. Security comes from a stateful firewall instead (Chapter 16).
Check yourself
- Name the three private IPv4 ranges.
- What does the router store in its NAT table, and why does it need port numbers?
- Why can't someone connect to your laptop from the internet by default?
- What is CGNAT and why is it annoying?
Further reading
- Video (~12 min): "NAT Explained" — Practical Networking. Also their "What is CGNAT?"
- Article: "What is NAT?" — Cloudflare Learning Center.
- RFC 1918 — two pages, defines the private ranges. Worth skimming.
- Article: "The trouble with CGNAT" — search APNIC blog; good context on why IPv6 matters.