Security Engineering · Network Security

Connection Tracking Under NAT

How address and port translation rewrites the five-tuple, why the mapping table is the real policy state, and which behaviours the NAT requirement documents leave to the implementation.

Network address translation exists because IPv4 addresses ran out, not because anyone wanted a middlebox in the path. The security properties that get attributed to it are a side effect of the mechanism: an internal host is unreachable from outside because there is no mapping that leads to it, not because a rule decided so. Understanding translation properly matters for security work because the mapping table is state that a firewall trusts, and because the table’s lifetime rules are where reachability actually comes from.

Two rewrites, two directions

A translation gateway holds a mapping from an internal endpoint to an external one. For a connection from an internal host to an external server, the gateway rewrites the source address and, for protocols with ports, the source port, so the packet appears to originate from the gateway’s external address. It records the mapping, and when a reply arrives addressed to that external address and port, it rewrites the destination back to the internal endpoint.

The four-tuple that identifies a flow in the gateway, namely internal address, internal port, external address, and external port, is what the mapping table is keyed on. Port translation is what makes this work at scale: two internal hosts connecting to the same external service on the same source port would otherwise collide, so the gateway allocates distinct external ports. The allocation policy is one of the behaviours the requirement documents specify. RFC 4787 asks that a NAT not reuse an external port for a different internal endpoint while any mapping for it is still active, and that it preserve the internal port where possible. Preserving the port is a convenience, not a requirement, and it matters for protocols that embed port numbers in their payload.

Without port translation, a gateway can only translate addresses, and it needs one external address per internal host that must be reachable. This is the static NAT case, and it is what makes an internal service reachable from outside: the mapping exists in advance, so there is no “no mapping, no reachability” property to rely on. A static mapping is a firewall rule written in a different syntax, and should be audited as one.

The table is the policy

Because the gateway only forwards replies that match a mapping, the mapping table is a dynamic permit list. A stateful firewall that also translates addresses is therefore enforcing two things at once: the rule set decides which outbound flows may create mappings, and the table decides which inbound packets are allowed back. The second decision is the one that most inbound policy is actually made of.

That has a direct consequence for reading policy. An inbound rule that permits traffic to an internal address is not sufficient if no mapping or static translation exists; the packet is dropped for unreachability rather than by policy. Conversely, a permissive outbound rule creates inbound reachability for whatever the remote side sends back on that flow. The two directions are not symmetric in the policy, and a rule set that reads as symmetric is usually misdescribing what happens.

Timeouts are policy too. A UDP mapping with no reply is kept for some interval and then removed; once removed, replies that arrive afterwards are dropped even though the remote endpoint believes the flow is alive. RFC 4787 sets a floor for this interval: the mapping must not expire in less than two minutes when it has not been refreshed, and implementations commonly use considerably longer values. For TCP, the mapping outlives the connection by a period intended to absorb the final acknowledgement and any retransmissions, and the exact duration is implementation-defined.

Hairpinning and the internal-view problem

A host inside the network that tries to reach another internal host by its external address presents a problem: the packet is addressed to the gateway’s external address, so the gateway must translate the destination back inside and also translate the source, otherwise the reply from the internal server goes directly to the client with an address the client’s stack does not recognise as its own. This is hairpinning, and RFC 4787 requires it for UDP when the internal endpoints are in the same private domain. Not every implementation does it, and the resulting failure, connections that work from outside but not from inside, is a recurring source of confusion in split-horizon DNS setups.

The related problem is that an internal host cannot learn its own external address from the packet it sends. Protocols that embed addresses in payload have to be rewritten by a helper that understands the payload format, and each such helper is a protocol-specific parser. FTP in active mode, SIP, and H.323 all need this. The helper reads the control channel, rewrites the embedded address, and opens a pinhole for the expected data flow. Every one of these is an application-layer gateway in the sense of Application Proxy Firewalls and Parser Differentials, with the same exposure to malformed input, and the same potential for the rewritten address to disagree with the address actually used.

Fragment handling and the port problem

A non-initial fragment carries no transport header, so the gateway cannot read the port it would need to match a mapping. The two options are to keep fragment state, reassociating fragments by the identification field, or to forward fragments without port translation. The first requires buffering and has a memory cost proportional to the number of incomplete datagrams; the second produces inconsistent behaviour, because the first fragment is translated and the rest are not.

Most implementations track fragments as part of the flow: the first fragment creates the mapping, and subsequent fragments are associated by source address, destination address, protocol, and the identification field. That association is a lookup an attacker can influence. Overlapping fragments and forged identification values have historically been used both to evade filtering and to exhaust the reassembly buffers. RFC 1858 remains the reference for the filtering-side attacks; the translation-side risk is the same mechanism aimed at the mapping table.

IPv6 changes the shape of the problem without removing it. There is no address shortage, so translation is used for other reasons: to present a stable prefix, to avoid renumbering, or to bridge to an IPv4-only service. The common forms are network prefix translation, which rewrites the prefix and leaves the interface identifier intact, and address plus port translation, which behaves much like its IPv4 counterpart. Because the interface identifier is derived from the address, prefix translation preserves the ability to address a host directly, so it does not provide the unreachability property at all.

Asymmetry and the checksum

Rewriting an address or port invalidates the transport checksum, so the gateway must recompute it. For TCP and UDP the checksum covers a pseudo-header that includes the addresses, so a source address change alone requires a full recomputation. Implementations therefore either recompute in full or apply an incremental update using the arithmetic property of the one’s-complement sum. The incremental form is cheaper and is what most fast paths do; it is also where a class of bug lives, because the update must handle the case where the old or new value is zero and where the sum wraps.

Asymmetric routing breaks translation outright. If the reply does not return through the same gateway, the gateway never sees it, the mapping is not refreshed, and the flow dies at the next timeout. Worse, a reply that arrives at a different gateway with an internal destination address has no mapping to match and is dropped. Deployments that route around a translating gateway therefore need either a single exit point or state synchronisation between the gateways, and the synchronisation channel carries the full mapping table.

What translation does not do

Translation is not authentication and not authorisation. It hides an address, which raises the cost of direct attack, but an internal host that initiates a connection to an attacker’s server has created a mapping that the attacker can use. That is the mechanism behind NAT slipstreaming, where a crafted payload causes a client to connect to an address and port the attacker chooses, and the resulting mapping lets the attacker’s inbound traffic through. The defence is not in the NAT; it is in the outbound policy that permitted the connection.

The same reasoning applies to the interaction with filtering. The mapping table is consulted before the rule set, exactly as described in How a Stateful Firewall Matches a Packet, so an inbound packet that matches a mapping is accepted without the inbound rules being evaluated at all. A rule intended to block a specific source is placed after the state check and therefore never sees the traffic. Translation adds a rewriting step in front of that same decision, which means the tuple being matched is not the tuple the endpoints believe they are using.

Port allocation policy also interacts with address sharing. When many subscribers share one external address, the external port becomes the only identifier of the subscriber, and the allocation policy determines how much information an observer can infer from it. RFC 4787’s requirement that ports not be reused while a mapping is active is what keeps that inference bounded; a gateway that recycles ports aggressively makes the mapping ambiguous, and logs that record only the external tuple become useless for attributing a flow.

References

  1. RFC 3022: Traditional IP Network Address Translator — IETF
  2. RFC 4787: Network Address Translation Behavioral Requirements for Unicast UDP — IETF
  3. RFC 5382: NAT Behavioral Requirements for TCP — IETF