---
title: 'NAT and PAT'
source: 'https://academia.sh/en/courses/network-models/nat-and-pat'
course: 'Network Models and Protocols'
language: en
updated: '2026-08-17T18:07:09+00:00'
license: 'CC BY-SA 4.0'
---

# NAT and PAT

Types of address translation, the translation table's five-component key, capacity calculations, and the breakdown of end-to-end connectivity.

Every device on the example network now has an address. But all of these addresses come
from the `192.168.10.0/24` block, and that block is not routed on the public Internet:
even if a packet with source address `192.168.10.196` reaches the provider's router,
there is no path by which a return packet could be brought back.

The organization has exactly one public address: `203.0.113.10`. This lesson's question
is how 182 internal addresses can go outbound through a single public address.

## Three Kinds of Translation

**Network address translation (NAT)** is changing the addresses in a packet's header
while it passes through. Three forms are distinguished.

| Type | Mapping | How many public addresses | Inbound connection |
|---|---|---|---|
| Static NAT | One-to-one, fixed | As many as internal hosts | Can be established |
| Dynamic NAT | One-to-one, from a pool | As many as concurrent hosts | Cannot be established |
| Port address translation (PAT) | Many-to-one | One address is enough | Only if configured |

**Static NAT** permanently binds one internal address to one public address. It is used
for a server that must be reachable from outside.

**Dynamic NAT** establishes a temporary mapping from a pool of public addresses. When
the pool is exhausted, new hosts cannot go outbound. Since the organization has only one
public address, this type does not suit the example network.

**Port address translation (PAT)** fits a large number of internal hosts into a single
public address by changing the source port number along with the address. This is the
method the example network uses. In common usage, when people say "NAT" they usually
mean this type.

## The Translation Table

The device performing translation must know which return packet belongs to which
internal host. For this it keeps a **translation table**. The table's key has five
components: source address, source port, destination address, destination port, and
protocol.

```python
EXTERNAL_ADDRESS = "203.0.113.10"


class Pat:
    """A translation table that performs port translation."""

    def __init__(self, external_address: str, initial_port: int = 61000) -> None:
        self.external_address = external_address
        self.next_port = initial_port
        self.forward: dict[tuple, tuple] = {}   # internal key -> external key
        self.reverse: dict[tuple, tuple] = {}   # external key -> internal key

    def outbound(self, internal_ip: str, internal_port: int,
                 dest_ip: str, dest_port: int) -> tuple[str, int]:
        key = (internal_ip, internal_port, dest_ip, dest_port)
        if key not in self.forward:
            self.next_port += 1
            external = (self.external_address, self.next_port)
            self.forward[key] = external
            self.reverse[(external[0], external[1], dest_ip, dest_port)] = (internal_ip, internal_port)
        return self.forward[key]

    def inbound(self, external_ip: str, external_port: int,
                source_ip: str, source_port: int) -> tuple[str, int] | None:
        return self.reverse.get((external_ip, external_port, source_ip, source_port))


pat = Pat(EXTERNAL_ADDRESS)
flows = [
    ("192.168.10.196", 49152, "198.51.100.20", 443),
    ("192.168.10.197", 49152, "198.51.100.20", 443),
    ("192.168.10.196", 49153, "198.51.100.20", 443),
    ("192.168.10.60", 51000, "198.51.100.20", 80),
]

print(f"{'internal source':22s} {'external source':22s} {'destination'}")
for internal_ip, internal_port, dest_ip, dest_port in flows:
    ext_ip, ext_port = pat.outbound(internal_ip, internal_port, dest_ip, dest_port)
    print(f"{internal_ip + ':' + str(internal_port):22s} {ext_ip + ':' + str(ext_port):22s} "
          f"{dest_ip + ':' + str(dest_port)}")

print()
print("return packet 203.0.113.10:61001 <- 198.51.100.20:443 ->",
      pat.inbound("203.0.113.10", 61001, "198.51.100.20", 443))
print("return packet 203.0.113.10:61003 <- 198.51.100.20:443 ->",
      pat.inbound("203.0.113.10", 61003, "198.51.100.20", 443))
print("unsolicited packet 203.0.113.10:61999 <- 198.51.100.20:443 ->",
      pat.inbound("203.0.113.10", 61999, "198.51.100.20", 443))
```

```
internal source        external source        destination
192.168.10.196:49152   203.0.113.10:61001     198.51.100.20:443
192.168.10.197:49152   203.0.113.10:61002     198.51.100.20:443
192.168.10.196:49153   203.0.113.10:61003     198.51.100.20:443
192.168.10.60:51000    203.0.113.10:61004     198.51.100.20:80

return packet 203.0.113.10:61001 <- 198.51.100.20:443 -> ('192.168.10.196', 49152)
return packet 203.0.113.10:61003 <- 198.51.100.20:443 -> ('192.168.10.196', 49153)
unsolicited packet 203.0.113.10:61999 <- 198.51.100.20:443 -> None
```

The second row shows why translation is necessary: two different internal hosts used
the same source port number (49152). If the numbers were not changed, the return
packets could not be told apart.

The last line is translation's most visible side effect: a packet with no counterpart
in the table is dropped. No connection arriving from outside that was not initiated
from inside can get in.

## Fields That Change

When a packet is translated, it is not only the address fields that change. Since the
source address in the IP header and the source port in the transport header change, the
checksums that cover them must be recomputed too.

| Field | On the outbound packet | On the return packet |
|---|---|---|
| IP source address | Internal address → public address | Unchanged |
| IP destination address | Unchanged | Public address → internal address |
| Transport source port | Internal number → translated number | Unchanged |
| Transport destination port | Unchanged | Translated number → internal number |
| IP header checksum | Recomputed | Recomputed |
| Transport checksum | Recomputed | Recomputed |

The reason the transport checksum also changes is that TCP and UDP checksums are
computed over a pseudo-header that includes the IP addresses. This detail makes
concrete that address translation is a layer violation: a device operating at the
network layer is forced to modify the transport-layer header.

For the same reason, a translator that cannot read the transport header cannot work. A
transport layer encrypted end to end makes translation impossible.

## Capacity Limit

The number of simultaneous translations that can be done through a single public
address is limited by the available port numbers.

```python
available = 65535 - 1024 + 1
print("available ports:", available)
for n in (182, 1000, 4000):
    print(f"{n:5d} clients -> concurrent flows per client: {available // n}")
```

```
available ports: 64512
  182 clients -> concurrent flows per client: 354
 1000 clients -> concurrent flows per client: 64
 4000 clients -> concurrent flows per client: 16
```

For the example network's 182 hosts, that works out to 354 concurrent flows per
client — a generous margin. But the number is inversely proportional to the host count
and drops to 64 at a thousand hosts. Applications that open dozens of connections per
page push against this limit.

The calculation is pessimistic: because the translation key also includes the
destination, flows going to different destinations can share the same translated
number. Still, the limit is real, and this is why large networks use multiple public
addresses.

A second limit is memory: a table entry is kept for every flow. Entries are held for a
period and time out — this produces one of translation's most visible side effects.

## The Breakdown of End-to-End Connectivity

The end-to-end principle introduced in the TCP/IP Model lesson says that state should
not be placed inside the network. Address translation directly violates this principle:
the translation table is state kept in the middle of the network. Its consequences can
be listed.

**A connection cannot be established from outside.** A flow that was not initiated from
inside has no counterpart in the table. For a device inside to be reachable from
outside, a rule must be written by hand — **port forwarding**. If the internal server
`192.168.10.226` on the example network is to be exposed, a rule is defined that
forwards traffic arriving at `203.0.113.10:443` to it.

**Idle flows drop.** When table entries are deleted by timeout, the return path is lost
for a connection that has not carried data in a long time. Both ends think the
connection is still open, but packets do not get through. Applications prevent this
with regular **keep-alive** packets; the TCP lesson details this mechanism.

**Peers cannot meet directly.** If both ends are behind translation, neither can
connect to the other. The workaround is for both ends to connect to a shared external
server, learn their own external address and port, and then send packets to each other
simultaneously. This method only works if the translator's mapping behavior is
predictable; when it does not work, traffic is carried through a relay server and
latency increases.

**An internal client may not be able to reach its own external address.** A packet from
a device inside trying to connect to `203.0.113.10:443` arrives at the translator from
its own internal interface and needs to turn back around. This behavior is called
**hairpinning**, and not every implementation supports it. The symptom is typical: a
service works from outside, but not from inside the same network.

## Translation Is Not a Security Mechanism

Not being able to establish a connection from outside looks like security, but
translation was not designed as a security mechanism and should not be treated as one.

The access barrier is a side effect, not a policy. Every flow initiated from inside is
opened outward; malicious software gets past the barrier by initiating the connection
from inside. Translation also does not look at the content of the traffic passing
through; it does not inspect what is carried inside a permitted flow.

Access policy is established through explicitly written filtering rules. Address
translation solves address scarcity; access control is the name of a separate job.

## Carrier-Grade Translation

When address scarcity is felt at the provider level too, translation is applied one
more layer: subscribers are given addresses from the `100.64.0.0/10` block instead of a
public address, and outbound traffic goes through the provider's public addresses. This
is called **carrier-grade NAT**.

Under two layers of translation, port forwarding cannot be defined by the subscriber,
peer-to-peer rendezvous fails more often, and address-based diagnosis loses its meaning
because a large number of subscribers share one external address.

IPv6 removes the need for any of these layers: once every endpoint can be given a
public address, the translation table becomes unnecessary too. This is one of the
rationales for the transition methods covered in the previous lesson.

## Summary

- Private-use addresses are not routed on the public Internet; outbound access is
  provided through address translation.
- Static translation is one-to-one and permanent, dynamic translation is temporary
  from a pool, and port address translation is many-to-one.
- The translation table's key has five components; return packets are told apart even
  when different internal hosts use the same source port.
- Because the address and port change, both the IP and transport checksums are
  recomputed; this is evidence that translation is a layer violation.
- A single public address has 64,512 ports; that works out to 354 concurrent flows per
  host for 182 hosts and 64 for a thousand hosts.
- Translation breaks end-to-end connectivity: connections cannot be established from
  outside, idle flows drop, and peers cannot meet directly. It is not a security
  mechanism.

## Next Step

Throughout this topic, the term "port" was used without being defined: in the
translation table's key, in the leasing protocol's numbers 67 and 68, in the
destination server's 443. What these numbers are, and how an operating system decides
which application an incoming segment goes to, is the transport layer's subject. The
next lesson covers ports, the concept of a socket, and the four-tuple that makes a
connection unique.
