Why NAT exists
IPv4 addresses are 32 bits, so there are only about 4.3 billion of them, far fewer than the devices that want to be online. The fix that kept the Internet running is to give every home or company one public address and let all its devices use private addresses inside. The private ranges (RFC 1918) may be reused in every network but are never routed on the Internet:
| Range | Size |
|---|---|
| 10.0.0.0/8 | 16.7 million addresses |
| 172.16.0.0/12 (172.16.x.x to 172.31.x.x) | 1 million |
| 192.168.0.0/16 | 65,536 |
A NAT router sits on the border. It has a private address on the inside (here 192.168.1.1) and a public one on the outside (203.0.113.5), and it rewrites addresses in every packet that crosses it.
Three kinds of NAT
| Kind | Mapping | Use |
|---|---|---|
| Static NAT | one private address ↔ one public address, fixed | a server that must always be reachable |
| Dynamic NAT | private addresses get a free public address from a pool | rare today |
| PAT (port address translation), also NAPT or masquerading | many private addresses ↔ one public address, told apart by port | almost every home and office router |
The 3D model shows PAT.
Following one connection
Press Outbound and reply:
| Where | Source | Destination |
|---|---|---|
| PC1 → router (inside) | 192.168.1.10:51000 | 93.184.216.34:80 |
| router → server (outside) | 203.0.113.5:40001 | 93.184.216.34:80 |
| server → router (outside) | 93.184.216.34:80 | 203.0.113.5:40001 |
| router → PC1 (inside) | 93.184.216.34:80 | 192.168.1.10:51000 |
- Outbound: the router has no entry for 192.168.1.10:51000, so it picks a free public port (40001), records
192.168.1.10:51000 ↔ 203.0.113.5:40001in the NAT table and rewrites the source. - The server sees a client called 203.0.113.5:40001. It never learns about 192.168.1.10.
- Inbound: the reply comes to 203.0.113.5:40001. The router looks up port 40001, finds 192.168.1.10:51000 and rewrites the destination.
Changing an address also changes the checksums: the IP header checksum, and the TCP/UDP checksum, which covers the addresses too (through the “pseudo-header”). The router recomputes both.
Many hosts, one address
Press Two hosts share one IP. PC1 and PC2 both happen to choose source port 51000. The router gives them different public ports:
| Private side | Public side |
|---|---|
| 192.168.1.10:51000 | 203.0.113.5:40001 |
| 192.168.1.11:51000 | 203.0.113.5:40002 |
A reply to port 40002 can only belong to PC2. The 16-bit port number allows about 65,000 simultaneous connections per public address (per protocol and remote endpoint), which is plenty for a home and enough for a small company. ISPs even put whole neighbourhoods behind one address with carrier-grade NAT (CGNAT).
Packets nobody asked for
Press Inbound and port forwarding. A packet arrives from the Internet for 203.0.113.5:8080. There is no table entry, because no inside host started this conversation, so the router has no idea which PC it is for. It drops the packet. As a side effect NAT blocks unsolicited inbound connections, which is often mistaken for a firewall. (It is not a real security feature: a firewall should still be configured.)
To run a server behind NAT, the administrator adds a port-forwarding rule: “public port 8080 → 192.168.1.10:80”. Now the same packet is translated and delivered.
The cost of NAT
- It breaks end-to-end addressing. Hosts behind NAT cannot be contacted directly, which hurts peer-to-peer apps, games and VoIP. They work around it with STUN, TURN, UDP hole punching or UPnP (which lets a program open a port forward automatically).
- Protocols that put IP addresses inside the payload (classic FTP, SIP) need an Application Level Gateway (ALG) in the router to rewrite those too.
- The router keeps state for every connection, so it can run out of table space or ports.
- IPv6 has 128-bit addresses, enough for every device to have a public one, so NAT is mostly unnecessary there.
Code
class Nat:
"""A NAT router with one public address that translates ports too (PAT, also called NAPT or masquerading)."""
def __init__(self, public_ip, first_port=40001):
self.public_ip, self.next_port = public_ip, first_port
self.out = {} # (private ip, private port) -> public port
self.back = {} # public port -> (private ip, private port)
self.forward = {} # static rules: public port -> (private ip, private port)
def outbound(self, pkt):
"""A packet leaves the LAN: rewrite the SOURCE address and port."""
key = (pkt['src'], pkt['sport'])
if key not in self.out: # first packet of this connection: create a table entry
self.out[key] = self.next_port
self.back[self.next_port] = key
self.next_port += 1
return {**pkt, 'src': self.public_ip, 'sport': self.out[key]}
def inbound(self, pkt):
"""A packet arrives from the Internet: rewrite the DESTINATION, or drop it if nobody asked for it."""
if pkt['dst'] != self.public_ip:
return None
target = self.back.get(pkt['dport']) or self.forward.get(pkt['dport'])
if target is None:
return None # no table entry: drop
return {**pkt, 'dst': target[0], 'dport': target[1]}
def show(p):
return f"{p['src']}:{p['sport']} -> {p['dst']}:{p['dport']}"
nat = Nat('203.0.113.5')
web = ('93.184.216.34', 80)
p1 = {'src': '192.168.1.10', 'sport': 51000, 'dst': web[0], 'dport': web[1]}
p2 = {'src': '192.168.1.11', 'sport': 51000, 'dst': web[0], 'dport': web[1]} # the same source port!
print(show(nat.outbound(p1)))
# 203.0.113.5:40001 -> 93.184.216.34:80
print(show(nat.outbound(p2)))
# 203.0.113.5:40002 -> 93.184.216.34:80
print(show(nat.outbound(p1))) # same connection again: same mapping
# 203.0.113.5:40001 -> 93.184.216.34:80
reply = {'src': web[0], 'sport': web[1], 'dst': '203.0.113.5', 'dport': 40002}
print(show(nat.inbound(reply)))
# 93.184.216.34:80 -> 192.168.1.11:51000
probe = {'src': '198.51.100.9', 'sport': 4444, 'dst': '203.0.113.5', 'dport': 8080}
print(nat.inbound(probe)) # nobody inside asked for this
# None
nat.forward[8080] = ('192.168.1.10', 80) # port forwarding rule
print(show(nat.inbound(probe)))
# 198.51.100.9:4444 -> 192.168.1.10:80
print(nat.out)
# {('192.168.1.10', 51000): 40001, ('192.168.1.11', 51000): 40002}
Common mistakes
- Saying NAT changes the destination of outgoing packets. Outbound, it changes the source; inbound, the destination.
- Forgetting the port. With one public address, only the public port identifies the connection. That is the “P” in PAT.
- Calling NAT a firewall. Dropping unknown inbound packets is a side effect, not a security policy.
- Thinking the server sees the private address. It sees only the router’s public address and port.
- Forgetting the checksums. Rewriting an address without recomputing the IP and TCP/UDP checksums would make the receiver discard the packet.
Complexity at a glance
| Case / operation | Time | Why |
|---|---|---|
| Translating one packet | O(1) | A hash-table lookup on (IP, port), plus updating the IP and TCP/UDP checksums. |
| Connections per public IP | about 65,000 | Per protocol and remote endpoint, limited by the 16-bit port number. |
| Table entry lifetime | until idle timeout | Typically minutes for UDP and longer for established TCP connections. |
| Extra space | One table entry per active connection |
Quick check
Test yourself — pick an answer to see if you got it.
1. Which of these is a private IPv4 address that may not appear on the public Internet?
The private ranges are 10.0.0.0/8, 172.16.0.0/12 (172.16 to 172.31) and 192.168.0.0/16. 172.20.4.1 lies in the second range.
2. When a packet goes from the LAN out to the Internet, what does a PAT router change?
The private source (IP, port) is replaced by the public IP and a public port recorded in the NAT table. On the way back the destination is changed back.
3. Two PCs behind the same NAT both use source port 51000 to the same server. How does the router tell the replies apart?
The public port is the key. Each (private IP, private port) pair gets its own public port, so each reply can be matched to the right host.
4. A packet from the Internet arrives at the NAT router's public address on a port that has no table entry and no forwarding rule. What happens?
Without an entry there is no way to choose a destination. That is why servers behind NAT need port forwarding, and why NAT blocks unsolicited connections.
5. Why is NAT much less needed with IPv6?
NAT exists mainly because IPv4's 32-bit addresses ran out. IPv6 has 128-bit addresses, so devices can be addressed end to end.