The problem NAT solves

IPv4 has about 4.3 billion addresses, far fewer than there are phones, laptops, cameras and servers today. The Internet Assigned Numbers Authority (IANA) handed out its last free IPv4 blocks in 2011, and the regional registries ran dry in the years after. Long before that, RFC 1918 (a Request for Comments, the numbered document series that defines internet standards) set aside three private ranges that anyone may use inside their own network: 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16 (see IPv4 Addressing). Thousands of organizations use 10.0.0.0/8 at the same time, so internet routers drop those addresses: a reply to "192.168.1.10" could belong to anyone.

Network Address Translation (NAT) is the bridge. A router (or firewall) at the edge of the network rewrites the source address of outgoing packets from a private address into a public one, remembers what it did in a translation table, and reverses the change on the replies. Inside, hosts keep their private addresses; outside, the world sees only the public ones. Defined in RFC 3022, NAT is the main reason IPv4 has lasted this long.

A quick check of which range an address is in, using Python's ipaddress module. It also recognizes 100.64.0.0/10, the shared range that providers use for carrier-grade NAT (CGNAT), explained below. Note the edge of the 172.16.0.0/12 block: it ends at 172.31.255.255.

import ipaddress

RANGES = {
    "private (RFC 1918)": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"],
    "shared CGNAT (RFC 6598)": ["100.64.0.0/10"],
}

def classify(text):
    address = ipaddress.ip_address(text)
    for label, networks in RANGES.items():
        if any(address in ipaddress.ip_network(n) for n in networks):
            return label
    return "public"

samples = ["192.168.1.10", "172.20.4.7", "172.31.255.254",
           "100.72.15.3", "198.51.100.7", "203.0.113.2"]
for text in samples:
    print(f"{text:<14} {classify(text)}")
192.168.1.10   private (RFC 1918)
172.20.4.7     private (RFC 1918)
172.31.255.254 private (RFC 1918)
100.72.15.3    shared CGNAT (RFC 6598)
198.51.100.7   public
203.0.113.2    public

NAT terminology

Cisco, and the CCNA, describe every address in a translated packet with two words. The first says whose address it is: inside (a host in your network) or outside (a host on the internet). The second says from which side you look: local (as seen inside your network) or global (as seen on the outside).

  • An inside local address is the real address of an inside host, as configured on it — usually a private address such as 192.168.1.10.
  • An inside global address is the address that same host appears to have on the outside, after translation — a public address such as 203.0.113.2.
  • An outside global address is the real address of the outside host, such as a web server at 198.51.100.20.
  • An outside local address is how that outside host appears to inside hosts. Unless you also translate outside addresses (rare), it equals the outside global address.
Term Whose address Seen from Example
Inside local your host inside 192.168.1.10
Inside global your host outside 203.0.113.2
Outside local remote host inside 198.51.100.20
Outside global remote host outside 198.51.100.20

Static NAT

Static NAT is a fixed, one-to-one mapping between one inside local address and one inside global address. The mapping always exists, so it works in both directions: outside hosts can open connections to the public address and reach the internal host. That is why static NAT is used for servers that must be reachable from the internet, such as a mail or web server. The price is one public address per server.

R1(config)# ip nat inside source static 192.168.1.20 203.0.113.20

Read it left to right: for packets from the inside, translate the source address 192.168.1.20 (inside local) to 203.0.113.20 (inside global), and the reverse for replies and for new connections from outside.

Dynamic NAT

Dynamic NAT also maps one-to-one, but the public addresses come from a pool. When an inside host sends its first packet out, the router takes a free address from the pool and keeps the mapping until it has been idle long enough to expire. An access control list (ACL) decides which inside addresses may be translated.

R1(config)# access-list 1 permit 192.168.1.0 0.0.0.255
R1(config)# ip nat pool PUBLIC-POOL 203.0.113.65 203.0.113.70 netmask 255.255.255.248
R1(config)# ip nat inside source list 1 pool PUBLIC-POOL
  • access-list 1 permit … — a standard ACL that selects the inside local addresses to translate (0.0.0.255 is a wildcard mask: the last octet may be anything).
  • ip nat pool — six public addresses, .65 to .70.
  • ip nat inside source list 1 pool PUBLIC-POOL — tie the two together.

With six addresses, only six inside hosts can be translated at the same time; the seventh gets no translation and its traffic is dropped until a mapping expires. That limitation is why plain dynamic NAT is rare today.

PAT: many hosts, one address

Port Address Translation (PAT), called NAT overload by Cisco and NAPT (Network Address and Port Translation) in the RFCs, translates the source port as well as the source address. Every connection is identified by its protocol, addresses and ports (see TCP, UDP and Ports), so one public address can carry tens of thousands of connections at once: each gets its own public port. This is the NAT in every home router and most branch offices.

PAT: inside hosts 192.168.1.10 and 192.168.1.11 both use source port 51000 to reach 198.51.100.20 port 443. Router R1 translates both to its outside address 203.0.113.2, keeping port 51000 for the first host and using port 1024 for the second, and records this in its translation table. Inside 192.168.1.10 192.168.1.11 192.168.1.12 R1 PAT outside 203.0.113.2 Internet 198.51.100.20 R1 translation table Inside local Inside global Outside global 192.168.1.10:51000 203.0.113.2:51000 198.51.100.20:443 192.168.1.11:51000 203.0.113.2:1024 198.51.100.20:443
Two hosts picked the same source port. PAT keeps the original port when it is free and chooses another one when it is not.

The router tries to keep the host's own source port. When two inside hosts happen to use the same port toward the outside, the second gets another free port. The reply for 203.0.113.2:1024 then tells the router exactly which inside host to deliver it to. The simulation below follows that logic in a simplified form: keep the port if it is free, otherwise take the lowest free port from 1024 upward. Real implementations differ in the details.

PUBLIC_IP = "203.0.113.2"
table = {}        # (proto, inside ip, inside port) -> public port
in_use = set()    # public ports already taken

def translate(proto, inside_ip, inside_port):
    key = (proto, inside_ip, inside_port)
    if key not in table:
        port = inside_port
        if (proto, port) in in_use:          # taken: next free from 1024
            port = 1024
            while (proto, port) in in_use:
                port += 1
        table[key] = port
        in_use.add((proto, port))
    return PUBLIC_IP, table[key]

flows = [("tcp", "192.168.1.10", 51000), ("tcp", "192.168.1.11", 51000),
         ("udp", "192.168.1.12", 53112), ("tcp", "192.168.1.10", 51000),
         ("tcp", "192.168.1.13", 51000)]

print("Pro  Inside local          Inside global")
for proto, ip, port in flows:
    public_ip, public_port = translate(proto, ip, port)
    print(f"{proto:<4} {ip + ':' + str(port):<21} {public_ip}:{public_port}")

# A reply arrives on the outside: look up who it belongs to.
reverse = {(proto, port): (ip, p) for (proto, ip, p), port in table.items()}
print("Reply to tcp port 1024 goes to", reverse[("tcp", 1024)])
Pro  Inside local          Inside global
tcp  192.168.1.10:51000    203.0.113.2:51000
tcp  192.168.1.11:51000    203.0.113.2:1024
udp  192.168.1.12:53112    203.0.113.2:53112
tcp  192.168.1.10:51000    203.0.113.2:51000
tcp  192.168.1.13:51000    203.0.113.2:1025
Reply to tcp port 1024 goes to ('192.168.1.11', 51000)

Notice the fourth line: the same inside socket gets the same mapping again. And notice what is missing: a packet that arrives from outside on a port with no entry in the table has nowhere to go, so the router drops it. PAT only works for connections that start on the inside.

Port forwarding

What if a server behind PAT must be reachable from outside, but there is only one public address? Port forwarding is a static PAT entry: "anything that arrives on the public address at TCP port 443 goes to 192.168.1.30 port 443". Different ports can go to different servers — TCP 443 to the web server, UDP 1194 to a virtual private network (VPN) appliance — all on the one public address.

R1(config)# ip nat inside source static tcp 192.168.1.30 443 interface GigabitEthernet0/1 443

The keyword interface GigabitEthernet0/1 uses that interface's address as the inside global address, which also works when the address comes from the provider by DHCP. Every forwarded port opens a door to an internal host from the whole internet, so it belongs behind a firewall rule and only for services that are patched and meant to be public.

Configuring and verifying PAT on Cisco IOS

A typical branch router: GigabitEthernet0/0 faces the LAN, GigabitEthernet0/1 faces the provider with a public address.

R1(config)# interface GigabitEthernet0/0
R1(config-if)# ip address 192.168.1.1 255.255.255.0
R1(config-if)# ip nat inside
R1(config-if)# interface GigabitEthernet0/1
R1(config-if)# ip address 203.0.113.2 255.255.255.252
R1(config-if)# ip nat outside
R1(config-if)# exit
R1(config)# access-list 1 permit 192.168.1.0 0.0.0.255
R1(config)# ip nat inside source list 1 interface GigabitEthernet0/1 overload
  1. ip nat inside / ip nat outside — mark which interfaces face the inside and the outside. NAT only happens for traffic that moves between an inside and an outside interface.
  2. access-list 1 permit 192.168.1.0 0.0.0.255 — which inside local addresses may be translated.
  3. ip nat inside source list 1 interface GigabitEthernet0/1 overload — translate matching sources to the address of Gi0/1, and overload it: that one keyword turns the translation into PAT. (With a pool instead: … list 1 pool PUBLIC-POOL overload.)

show ip nat translations shows the table. Mind the column order: Cisco puts inside global before inside local.

R1# show ip nat translations
Pro Inside global         Inside local          Outside local         Outside global
tcp 203.0.113.2:51000     192.168.1.10:51000    198.51.100.20:443     198.51.100.20:443
tcp 203.0.113.2:1024      192.168.1.11:51000    198.51.100.20:443     198.51.100.20:443
udp 203.0.113.2:53112     192.168.1.12:53112    198.51.100.53:53      198.51.100.53:53
tcp 203.0.113.2:443       192.168.1.30:443      ---                   ---

The last line is the port-forwarding entry: static, so it is always present, with no outside host until someone connects. Other useful commands: show ip nat statistics (inside and outside interfaces, hits and misses), clear ip nat translation * (remove dynamic entries after a change) and, carefully on a busy router, debug ip nat.

CGNAT and the side effects of NAT

Carrier-grade NAT

Providers that have run out of public addresses translate a second time, in their own network: carrier-grade NAT (CGNAT). The customer's router gets an address from 100.64.0.0/10, the "shared address space" of RFC 6598, instead of a public one, and the provider's CGNAT device maps many customers onto a few public addresses. A 100.64.x.x address on the wide area network (WAN) interface of a home or branch router is the sign: port forwarding on that router can no longer work, because the provider's NAT sits in front of it.

What NAT costs you

  • It breaks end-to-end addressing. The address a host has is not the address the other side sees. Protocols that put addresses inside their payload (older voice over IP (VoIP) and File Transfer Protocol (FTP) modes, some VPNs) need helpers or workarounds, and peer-to-peer applications need tricks to reach a host behind NAT.
  • It complicates logging and troubleshooting. A complaint from outside says "203.0.113.2 attacked us at 14:03". Which of 300 inside hosts was that? Only the NAT translation log, with the source port and a precise timestamp, can answer — which is one reason accurate time matters (see Operational Services).
  • It is not a firewall. PAT happens to drop unsolicited inbound packets, but it does not inspect traffic, does nothing about connections started from the inside (malware calling out) and offers no protection for a static NAT or port-forwarded host. Security comes from a real firewall policy; see Network Security Basics.

IPv6 and NAT

NAT exists because IPv4 ran short of addresses. IPv6 does not have that problem: a single /64 subnet has more addresses than the whole IPv4 internet many times over, so every device can have a globally unique address and NAT is not needed to save space (see IPv6 Basics). The protection that people attribute to NAT comes, in IPv6 networks, from a stateful firewall that blocks unsolicited inbound traffic.