IPv6 and NAT
In the early 1980s, 2^{32} — 4.3 billion — IPv4 addresses felt like more than
the human race could ever need. Then came PCs, then phones, then a fridge with Wi-Fi. By the 2010s the
regional registries were literally handing out their last blocks; the free pool ran dry.
The Internet faced a genuine arithmetic crisis: more devices than addresses. Two responses emerged, and —
fascinatingly — both are still running side by side today. One is a clever, slightly grubby stopgap
that stretches the old addresses (NAT); the other is a clean, enormous
replacement that ends scarcity forever (IPv6). This page is about both, and
about why the "temporary" hack has outlived every prediction of its retirement.
NAT: making one address look like hundreds
Look at your home. A laptop, two phones, a games console, a TV, a doorbell — a dozen devices, each with a
private
RFC 1918
address like 192.168.1.x. Yet your ISP gave you exactly one public IPv4
address. How do a dozen devices share one public identity? Network Address Translation.
Your home router sits on the boundary. When an inside device opens a connection, the router
rewrites the packet: it swaps the private source IP and source port for its own
public IP and a freshly chosen port, and records the swap in a NAT translation table.
When the reply comes back to that public IP:port, the router looks up the table, rewrites the destination
back to the original private IP:port, and delivers it inside. The outside world sees only the router's
single public address; the multiplexing across dozens of devices is hidden entirely in that table. Because
it rewrites ports as well as addresses, this is often called NAPT (Network
Address Port Translation) or "PAT" — and the port is what makes many-to-one sharing possible.
| Inside (private) IP:port | Router public IP:port | Outside destination |
| 192.168.1.10:52001 | 203.0.113.5:40001 | 93.184.216.34:443 |
| 192.168.1.23:52001 | 203.0.113.5:40002 | 93.184.216.34:443 |
| 192.168.1.10:52014 | 203.0.113.5:40003 | 142.250.72.14:80 |
Notice the first two rows: two different inside devices both used source port 52001 and both
talked to the same server — a collision that would be ambiguous on the wire. NAT resolves it by giving
each a distinct public port (40001 vs 40002), so replies are never
confused. The demo below builds exactly this table.
// A minimal NAT (NAPT) box. It rewrites (privateIP, privatePort) to the
// router's single public IP with a unique public port, remembers the mapping,
// and can translate a reply back to the right inside host.
const PUBLIC_IP = "203.0.113.5";
let nextPort = 40001;
const table = new Map<string, string>(); // publicKey -> "privIP:privPort"
function outbound(privIP: string, privPort: number, dst: string): string {
const pubPort = nextPort++;
table.set(`${PUBLIC_IP}:${pubPort}`, `${privIP}:${privPort}`);
console.log(`OUT ${privIP}:${privPort} -> ${dst} rewritten as ${PUBLIC_IP}:${pubPort}`);
return `${PUBLIC_IP}:${pubPort}`;
}
function inbound(pubKey: string): void {
const inside = table.get(pubKey);
if (inside) console.log(`IN reply to ${pubKey} delivered inside to ${inside}`);
else console.log(`IN reply to ${pubKey} DROPPED (no table entry - unsolicited)`);
}
// Two inside hosts, both using source port 52001 to the same server:
const a = outbound("192.168.1.10", 52001, "93.184.216.34:443");
const b = outbound("192.168.1.23", 52001, "93.184.216.34:443");
outbound("192.168.1.10", 52014, "142.250.72.14:80");
console.log("--- replies come back ---");
inbound(a); // resolves to .10
inbound(b); // resolves to .23, NOT .10 - the ports disambiguate
// An UNSOLICITED packet from the outside (nobody asked for it):
inbound("203.0.113.5:55555"); // no mapping -> dropped
NAT solved the address crunch brilliantly — but it broke a founding principle of the Internet.
The cost: NAT breaks the end-to-end principle
The original Internet promised that any host could address any other host directly — the
end-to-end principle. NAT quietly revokes that promise. An inside device has no
publicly reachable address of its own; it exists, from the outside, only as a temporary row in a table
that appears when it initiates a connection and evaporates when idle. This has real consequences:
-
Unsolicited inbound connections fail. Nobody outside can start a connection to
an inside host, because there is no table entry until the inside host reaches out first (you saw the
"DROPPED" line above). Running a server at home therefore needs manual port forwarding.
-
Peer-to-peer is hard. Two devices, each behind its own NAT, cannot simply dial each
other — both are "inside-only". Getting a video call or game to connect directly requires
NAT traversal tricks (STUN, TURN, ICE, "hole punching"): the two peers rendezvous via a
public server, learn each other's public IP:port, and simultaneously send outbound packets to punch
matching holes in both NATs.
-
Some protocols embed addresses in their payload (classic FTP, SIP), which NAT then has
to reach in and rewrite too — a layering violation that requires special "helpers".
It is tempting to say "NAT protects me — outsiders can't reach my devices." There is a grain of truth:
because unsolicited inbound packets have no table entry, they are dropped, which incidentally
hides inside hosts. But that safety is a side effect, not a security design, and
leaning on it is dangerous:
-
NAT makes no security decisions — it doesn't inspect content, block malware, or filter by
policy. It rewrites addresses. Any inside host that initiates a connection opens a two-way hole for
the duration.
-
Its "protection" vanishes the moment you add port forwarding, UPnP (which lets apps open holes
automatically), or NAT traversal — all common.
-
IPv6 often has no NAT at all, so a real, configured stateful firewall — not
NAT — must be the thing providing security in both worlds.
The one-liner: NAT conserves addresses; a firewall enforces policy. Do not confuse an
accident of address translation for a deliberate security boundary.
IPv6: end scarcity, don't manage it
IPv6 takes the opposite tack: instead of squeezing more life out of 32 bits, it moves to
128 bits. The number of addresses becomes 2^{128} —
2^{128} \approx 3.4 \times 10^{38}, so vast that you could assign an address to
every atom on the surface of the Earth and barely dent it. Scarcity, and with it the reason for
NAT, simply disappears. Every device can once again have its own globally unique, directly reachable
address — the end-to-end principle restored.
128 bits are written as eight groups of four hex digits, separated by colons:
\texttt{2001:0db8:0000:0000:0000:ff00:0042:8329}
That is a mouthful, so IPv6 has two compression rules that shorten it dramatically:
-
Drop leading zeros in each group:
0db8 → db8, 0042 → 42,
0000 → 0.
-
Replace one run of all-zero groups with
:: — and you may do this
at most once per address (otherwise it would be ambiguous where the zeros go).
Applying both, the address above collapses to:
\texttt{2001:db8::ff00:42:8329}
The :: stands in for "however many all-zero groups it takes to make eight total". So
::1 is the loopback (seven zero groups then 1), and :: alone is the
all-zeros address. Because you may use :: only once, an address like
2001:0:0:1:0:0:0:1 must choose which zero-run to collapse (the longer one), giving
2001:0:0:1::1 — not 2001::1::1, which is illegal and un-parseable.
-
Simplified, fixed-size header. IPv6 drops the header checksum and the in-network
fragmentation fields, giving routers a leaner, faster-to-process header. (Fragmentation, when needed,
is done only by the sender.)
-
Stateless address autoconfiguration (SLAAC). A host can generate its own address from
the network prefix a router advertises — no DHCP server required to get online.
-
No NAT needed. With addresses no longer scarce, hosts get globally unique addresses
and reach each other directly — end-to-end connectivity returns.
The long crossover: dual stack and tunnelling
You cannot flip the whole Internet to IPv6 on a Tuesday — hundreds of millions of devices, routers and
services would have to change at once. So the transition has been gradual, and two mechanisms carry it:
-
Dual stack — a host or router runs both IPv4 and IPv6 simultaneously, with two
addresses, and speaks whichever the other end supports. This is the dominant approach: your phone almost
certainly has both right now, quietly preferring IPv6 when the destination offers it.
-
Tunnelling — when an IPv6 packet must cross a stretch of IPv4-only network, it is
encapsulated inside an IPv4 packet (an IPv6 datagram tucked into an IPv4 payload), carried
across, and unwrapped at the far side. The IPv4 middle never knows it was carrying IPv6.
The upshot is a strange, drawn-out coexistence: IPv6 adoption keeps climbing (past half of Google's
traffic in many countries), yet NAT'd IPv4 stubbornly persists. The "temporary" stopgap and its
replacement have shared the road for two decades — a reminder that on the Internet, nothing is more
permanent than a good temporary fix.
There was an "IPv5" — sort of. The version number 5 in the IP header was assigned to an
experimental 1979 protocol called ST (the Internet Stream Protocol), designed for real-time voice and
video. It never became a general-purpose successor to IPv4, but it had already claimed version number 5.
So when the next-generation IP was designed in the 1990s, it took the next free number: 6.
There is no missing IPv5 conspiracy — just a version number quietly spent on a research protocol before
the real successor arrived.