Chapter 07 · Part III · NAT

NAT & port translation

Your phone, your laptop and your TV all reach the internet through one public address. The trick that makes that work is a small table inside your router, rewriting every packet on the way out and every reply on the way back. This chapter opens the table up.

4,294,967,296IPv4 addresses in total, which ran out at IANA in February 2011
65,536port numbers per protocol per address, the room NAT shares out
2 h 4 minminimum idle time before a NAT may forget an established TCP connection (RFC 5382)

Common mix-up: NAT is not a firewall. It drops unsolicited inbound packets only because it has nowhere to send them, not because it judged them dangerous. Anything that creates a mapping, from a game console asking for a port to malware calling home, opens the door just as wide.

The shortage

Why NAT exists

An IPv4 address is 32 bits long, so there can be at most 2³², or 4,294,967,296, of them. When the format was written down in RFC 791 in 1981, that looked like an absurd surplus: there were a few hundred hosts on the ARPANET. Not all of the space is usable, either. Big blocks are reserved for multicast, for private networks, for loopback and for documentation, which leaves roughly 3.7 billion addresses that can appear on the public internet.

By the early 1990s it was clear the supply would run out. The long-term fix was a bigger address, which became IPv6 (Chapter 3 covers both). The short-term fix, published in 1994 as RFC 1631, was to stop giving every machine its own public address at all. Give a whole site one address, number the machines inside with addresses nobody else will route, and have the box at the edge rewrite packets as they cross.

That box is a network address translator, and the address ranges it hides are the private blocks of RFC 1918: 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16. Anyone may use them, as many times as they like, because routers on the public internet refuse to carry them. Millions of homes are all numbered 192.168.1.something at once, and none of them collide, because none of those addresses ever leave the house.

The short-term fix turned out to be very long-term. IANA handed out its last free blocks to the five regional registries on 3 February 2011. The registries then ran dry one after another: APNIC in 2011, RIPE NCC in 2012 (and finally completely in 2019), ARIN in 2015. Today a new ISP that wants IPv4 addresses has to buy them from someone who already has them. NAT is why that shortage is an inconvenience rather than a wall: almost every device you own is behind one, and many are behind two (Chapter 8).

Go deeper: what a translator actually has to rewrite

Changing an address sounds like a one-field edit, but two checksums depend on it. The IPv4 header has its own checksum, which covers the source and destination addresses. TCP and UDP checksums cover a "pseudo-header" that also includes both addresses, plus the ports. So a NAT that changes an address and a port has to fix three numbers: the address, the port and both checksums.

It doesn't recompute them from scratch. Internet checksums are ones' complement sums, which means you can subtract the old value and add the new one; RFC 1624 spells out the arithmetic. That's why a home router with a modest CPU can translate hundreds of megabits per second.

ICMP is the awkward case. An echo request (ping) has no ports, so a NAT uses the 16-bit query identifier in its place. ICMP error messages, such as "destination unreachable", carry a copy of the packet that caused them, and the NAT must rewrite the addresses inside that copy too, or the sender won't recognize which connection the error belongs to. RFC 5508 sets out the rules.

Three flavors

Static NAT, dynamic NAT and NAPT

The word NAT covers a few different machines. They all rewrite addresses; they differ in how many outside addresses they have to work with and whether they touch ports.

Static NAT is a fixed one-to-one pairing. Inside address 10.0.0.5 always appears outside as 203.0.113.5, and traffic to 203.0.113.5 always goes to 10.0.0.5. Nothing is shared and nothing is saved; it's used when a server must keep an internal address but be reachable at a public one.

Dynamic NAT keeps a pool of public addresses and lends one to each inside host while it is busy. Ten public addresses can serve fifty machines, as long as no more than ten talk at once.

NAPT, network address and port translation, is the one in your house. Cisco calls it PAT, port address translation; Linux calls it masquerading; most people just call it NAT. It shares a single public address among every inside device by rewriting the port as well as the address. The port is a 16-bit number in the TCP or UDP header that tells the receiving machine which conversation a packet belongs to. Port numbers exist for exactly this kind of multiplexing, and NAPT borrows them: each inside conversation gets its own public port, and that port is the key the router uses to send the replies home.

(192.168.1.10, 51000) → (203.0.113.7, 51000)
An NAPT mapping: one inside address and port, paired with one public address and port. The public port is the only thing that tells the laptop's replies apart from the phone's.
KindOutside addressesRewritesWhere you meet it
Static NATOne per inside hostAddress onlyA server with a fixed public face; cloud "elastic" IPs
Dynamic NATA pool, lent outAddress onlyOlder enterprise edges; rare at home
NAPT / PATOne, sharedAddress and portEvery home router, phone hotspot and Wi-Fi café
CGNATA few, shared by thousandsAddress and portMobile carriers and many ISPs (Chapter 8)
The heart of it

The translation table

Everything a NAT knows lives in one table. Each row is a mapping, and a row is born the moment an inside device sends its first packet to somewhere outside.

Follow one. Your laptop, 192.168.1.10, opens a web page on 198.51.100.20. Its operating system picks a temporary source port, say 51000, and sends a TCP packet: from 192.168.1.10:51000 to 198.51.100.20:443. The router sees a source address that can't go out on the internet, so it looks for a row for (TCP, 192.168.1.10, 51000). There isn't one. It picks a public port, writes the row, rewrites the packet's source to 203.0.113.7:51000, fixes the checksums and sends it on.

The web server never learns the laptop exists. As far as it can tell, 203.0.113.7 opened a connection from port 51000, so that's where it sends the reply: to 203.0.113.7:51000. The router looks up port 51000 in its table, finds the laptop's row, rewrites the destination back to 192.168.1.10:51000, and the laptop receives a reply that looks exactly as if no translation had ever happened.

A connection is identified by five things: the protocol, the source address and port, and the destination address and port. That's the 5-tuple. A NAT's table is a map from inside 5-tuples to outside ones, and the replies are matched on the outside 5-tuple coming back the other way.

Choosing a port

Most routers first try port preservation: keep the same number the device chose, if it's free. It often is, but not always. Every device picks its own source ports independently, so two devices on the same LAN can easily both pick 51000 for UDP. Ports are counted separately for TCP and UDP, so a TCP 51000 and a UDP 51000 don't clash, but two UDP 51000s do. The second one gets a different public port. Some routers pick the next free number, some pick at random; RFC 6056 recommends randomizing ports so an attacker can't guess them, and many NATs follow suit.

The table has to answer one more question: if the same inside port talks to a second destination, does it get the same public port again? A NAT that reuses it is said to have endpoint-independent mapping. One that hands out a new port per destination has endpoint-dependent mapping, the old "symmetric NAT". For browsing the difference is invisible. For two devices trying to reach each other directly, it's the whole game, and Chapter 9 is about exactly that.

Go deeper: inside local, inside global, outside global

Cisco's documentation names the four addresses a NAT juggles, and the terms turn up in every enterprise config, so they are worth decoding. "Inside" and "outside" say which side of the router the host is on. "Local" and "global" say which side's point of view the address is written from.

  • Inside local: the inside host's real address, as the LAN sees it. 192.168.1.10.
  • Inside global: the same host as the world sees it. 203.0.113.7 (and, with NAPT, a port).
  • Outside global: the remote host's real address. 198.51.100.20.
  • Outside local: the remote host as the inside sees it. Usually identical to the outside global; it differs only when the router translates the destination as well, which is rare.

The live router below labels its columns the plain-English way: inside, public, remote. Inside is inside local, public is inside global, remote is outside global.

Forgetting on purpose

Timeouts: why mappings expire

A NAT can't keep rows forever; the table and the port supply are both finite. So each row carries an idle timer. Every packet that matches the row resets it. If the timer runs out, the row is deleted and its public port goes back on the shelf.

TCP gives the router help. Connections open with a SYN handshake and close with FIN or RST packets, so a router can see when a conversation is over and drop the row soon afterward. While a connection is established, the standard says to be patient: RFC 5382 says the idle timeout for an established TCP connection must not be shorter than 2 hours and 4 minutes. That number isn't arbitrary. TCP's own keepalive, when an application turns it on, waits two hours of silence before probing, so a NAT that waits slightly longer won't cut off a connection that is merely quiet. Half-open and closing connections, which the standard calls transitory, may be dropped after 4 minutes.

UDP gives no help at all. There's no handshake and no goodbye, just datagrams. A router can only guess from silence. RFC 4787 says a UDP mapping must not expire in less than 2 minutes, and recommends 5 minutes or more. Plenty of real routers are stingier, some as short as 30 seconds. That's why protocols that need to stay reachable over UDP send small keepalive packets on a timer. WireGuard's documentation suggests a persistent keepalive of 25 seconds when a peer sits behind NAT; it's chosen to be shorter than almost any router's UDP timeout.

When a row expires early, the symptom is distinctive. The connection doesn't fail at once; it fails the next time the far end tries to talk. The reply arrives at the public port, finds nothing there, and vanishes. Long SSH sessions that freeze after you walk away for lunch, and VoIP calls that go one-way after a pause, are often exactly this.

Kind of mappingMinimum idle timeoutSource
TCP, established2 hours 4 minutesRFC 5382
TCP, transitory4 minutesRFC 5382 (opening or closing)
UDP2 minutes (5+ recommended)RFC 4787 (some well-known ports may be shorter)
ICMP query60 secondsRFC 5508
Go deeper: which direction refreshes a timer?

RFC 4787 requires that an outbound packet always refreshes a UDP mapping. Whether an inbound packet does is left to the vendor. The cautious reading is that a NAT should not let the outside world keep a hole open on its own, so many routers only count outbound traffic.

The practical rule follows: if you need a mapping to stay alive, the inside device has to send something. A server pushing data at a client every 20 seconds may not keep the mapping alive by itself; the client sending a tiny packet back will. The live router below behaves this way: inbound replies are delivered but don't reset the timer.

Knock, knock

Why strangers get dropped

Now flip the direction. A packet arrives from the internet, from an address nobody inside has talked to, aimed at 203.0.113.7 port 3389. The router looks in its table for a row with public port 3389. There isn't one. It has a public address and a port number and no idea which of the devices behind it, if any, should get the packet. So it does the only thing it can: it throws the packet away.

That accident is what most people mean when they say NAT "protects" their home network. Unsolicited inbound connections simply have nowhere to go. A laptop with a file share wide open sits behind the router invisible to the internet, not because anything decided it should be safe, but because there's no row pointing at it.

Even an existing row doesn't necessarily let anyone in. When a packet arrives at a public port that does have a mapping, the router can apply a filter: accept it from anyone (endpoint-independent filtering), only from an address the inside device has already sent to (address-dependent), or only from that exact address and port (address- and port-dependent). The strict setting is common on home routers; the loose one turns every outbound connection into a small public doorway, which is exactly what makes peer-to-peer connections easy, and exactly why it is a security trade-off.

Why that still isn't a firewall

A firewall is a policy: rules about what may cross, written on purpose and enforced even when translation would happily deliver a packet. NAT has no such policy, and several everyday things go straight through the gap.

  • Inside devices open doors. Any program can make an outbound connection, and once it has, replies flow freely. Malware calls home, and the NAT carries the conversation as faithfully as a video call.
  • Devices ask for holes. UPnP and NAT-PMP let any program on the LAN ask the router to forward a port, usually with no password (Chapter 8).
  • IPv6 has no NAT at home. Each device gets a real global address. If the router's IPv6 firewall weren't on by default, every device would be directly reachable. RFC 4864 makes the point that the protection people credit to NAT comes from stateful filtering, which works just as well without translation.
  • The inside is wide open. NAT does nothing about other devices on your own Wi-Fi.

Most home routers do run a real stateful firewall alongside the NAT, with rules that drop unsolicited inbound traffic on purpose. The two travel together so often that people stop distinguishing them. The difference shows up the moment you add a port forward, enable IPv6 or join a network you don't control.

Instrument 1

A live NAT router

Three devices on 192.168.1.0/24 share one public address, 203.0.113.7. Press the buttons to make traffic. Watch the table allocate a public port, translate the reply on the way back, and let the row expire when the idle timer runs out. The router clock runs faster than real time so you don't have to wait two minutes for UDP. Then have a stranger knock, add a port forward, and try again.

Router clock0:00
Rows in table0
Packets translated0
Packets dropped0

Press a button to send some traffic.

ProtoInsidePublicRemoteIdle timer
Empty. No device has sent anything yet, so nothing from outside can get in.

TCP 3389 is the Remote Desktop port. The timeout defaults here are deliberately shorter than RFC 5382's 2 h 4 min so you can watch TCP rows expire too; drag the slider to 7440 s to see the standard's value.

Letting someone in

Port forwarding and hairpinning

Sometimes you want strangers to get in. You run a game server, a security camera, or Remote Desktop on a machine at home. The fix is to write a row by hand: a port forward, also called a static mapping or virtual server. "TCP port 3389 on the public address goes to 192.168.1.10 port 3389." Now when the stranger's packet arrives, the router finds a row, rewrites the destination and delivers it. The forward never expires; it sits in the table permanently, waiting.

Two catches follow from how the table works. First, a public port can point at only one inside device. If two PCs both want Remote Desktop on 3389 from outside, one of them has to use a different public port, say 3390 forwarded to the second PC's 3389. Second, the inside device must have a stable address, so forwards usually go with a DHCP reservation. If the laptop's address changes from .10 to .14 after a reboot, the forward points at nothing.

A DMZ host setting is the blunt version: "anything that doesn't match a row, send to this one device." It's the same as forwarding every port. On a router facing the internet it's risky. On a router facing another router it's a standard fix for double NAT, which is the next chapter's subject.

Hairpinning: reaching yourself through the front door

Here is a puzzle. The forward works from outside. But at home, your phone, on the same Wi-Fi, tries to connect to 203.0.113.7:3389, because that's the address saved in the app. The packet goes to the router, since 203.0.113.7 isn't on the LAN. The router has to notice that the destination is its own public address, apply the forward, rewrite the destination to 192.168.1.10, and rewrite the source to its public address too. That last step matters: if the laptop saw the reply's destination as 192.168.1.11 (the phone's real address), it would answer directly over the LAN, and the phone would discard a reply from an address it never talked to.

This U-turn is called hairpinning, or NAT loopback. RFC 4787 says a NAT must support it for UDP, and RFC 5382 says the same for TCP. Plenty of consumer routers still don't. When they don't, the classic workaround is split DNS: a name that resolves to the public address outside and the private address inside.

Go deeper: why the source has to be rewritten too

Write out the hairpin packet step by step. The phone sends 192.168.1.11:52000 → 203.0.113.7:3389. If the router changed only the destination, the laptop would receive 192.168.1.11:52000 → 192.168.1.10:3389. It would reply 192.168.1.10:3389 → 192.168.1.11:52000, and that reply would go straight across the switch to the phone, never touching the router. The phone is waiting for a reply from 203.0.113.7:3389, so TCP rejects it. The connection never opens.

With the source rewritten as well, the laptop sees 203.0.113.7:p → 192.168.1.10:3389, replies to the router's public address, and the router un-translates both fields on the way back. Both ends see a consistent conversation. This is called "external source IP address and port" behavior in the RFCs; the instrument above does it when you turn hairpinning on.

When the payload talks back

ALGs: addresses inside the packet

NAT rewrites headers. Some protocols, though, write addresses into the payload, where a NAT normally never looks. The oldest example is FTP. In active mode the client tells the server, in plain text, "connect back to me at 192,168,1,10 port 200,24" (the PORT command; the port is 200×256+24 = 51224). The server dutifully tries to connect to 192.168.1.10, a private address on someone else's LAN, and fails.

An application-level gateway, or ALG, is protocol-specific code in the router that parses such messages and rewrites the addresses inside them, then opens a matching mapping so the server's connection back gets through. Routers have shipped ALGs for FTP, SIP (internet phone calls), PPTP and the IPsec family, among others.

ALGs are an uneasy fix. A rewrite can change the length of the payload, which then means adjusting TCP sequence numbers for the rest of the connection. They have to understand each protocol perfectly, and SIP ALGs in particular are notorious for mangling calls; "turn off SIP ALG" is the first line of many VoIP troubleshooting guides. And they can't work at all on encrypted traffic, which is now most traffic. Modern protocols take the opposite approach: they assume NAT exists and work around it from the endpoints, with passive-mode FTP, STUN and ICE (Chapter 9).

PORT 192,168,1,10,200,24 → PORT 203,0,113,7,195,80
An FTP ALG rewriting an active-mode PORT command: the private address becomes the public one, and the port becomes whichever public port the ALG mapped (195×256+80 = 50000).
Instrument 2

How many devices behind one address?

A public address has 65,536 ports per protocol, and the first 1,024 are normally left alone. How far does that stretch? It depends on how many simultaneous connections each device keeps open, and on whether the NAT can reuse a public port for different destinations. Carrier-grade NATs also hand each subscriber a fixed block of ports; pick a block size to see what that costs.

Usable ports–
Devices that fit–
Subscribers per address–
Total subscribers–

Side by side

What crosses a home NAT, and how

TrafficWhat happensWhy
Outbound webWorksA row is created on the way out; replies match it.
Reply after timeoutDroppedThe row has expired; the public port means nothing now.
Unsolicited inboundDroppedNo row, no forward: nowhere to send it.
Inbound, forwarded portWorksA permanent hand-written row points at one device.
Inside → own public IPWorks only with hairpinningThe router must translate both source and destination.
Active-mode FTP, SIPNeeds an ALGAddresses hide in the payload, not the header.
Peer-to-peerSometimesDepends on mapping and filtering behavior (Chapter 9).
Cheat sheet

Terms from this chapter

NAT
Network address translation: rewriting the addresses in packets as they cross a router.
NAPT / PAT
NAT that rewrites ports as well, so many inside devices share one public address. What every home router does.
Private address
An address from 10/8, 172.16/12 or 192.168/16 (RFC 1918). Reusable by anyone; never routed on the internet.
5-tuple
Protocol, source address, source port, destination address, destination port. Identifies one conversation.
Mapping
A row in the NAT's table pairing an inside address and port with a public address and port.
Port preservation
Keeping the device's own source port as the public port, when it's free.
Idle timeout
How long a row survives with no traffic. At least 2 minutes for UDP, 2 h 4 min for established TCP.
Filtering
Which outside senders may use an existing mapping: anyone, the same address, or the same address and port.
Port forward
A permanent, hand-written mapping that sends one public port to one inside device.
DMZ host
A device that receives every inbound packet the table doesn't otherwise claim.
Hairpinning
An inside device reaching another inside device through the router's public address. Also called NAT loopback.
ALG
Application-level gateway: router code that rewrites addresses buried in a protocol's payload, such as FTP or SIP.
Where this comes from

Sources