NAT & Connection Tracking

NAT and connection tracking sit on the packet path of every Linux box, and most outages blamed on 'the firewall' are really conntrack: a full table dropping new flows, an exhausted SNAT port range, MASQUERADE's per-packet route lookup, or asymmetric routing that hides the return packet from conntrack. Adjust the NAT type, connection rate, table size, port range and timeout and watch the table, port space and correctness cascade. Validated against Linux v6.12.93 netfilter.

Launch Simulator →

What you'll explore

  1. 01

    The Conntrack Table Is a Finite Resource

    Every packet that isn't explicitly untracked enters nf_conntrack_in(), which finds the existing flow or creates a new entry keyed on the connection's 5-tuple. Those entries live in a fixed-size hash table capped by nf_conntrack_max. The number of entries a steady workload holds is roughly the new-connection rate multiplied by how long each entry lingers — new-conns/sec × timeout — so a modest rate with a long timeout can quietly fill the table. When it fills, the kernel drops new connections and bumps the 'insert_failed'/'drop' counters, which reads at the application layer as random connection failures under load. Debugging starts at /proc/sys/net/netfilter/nf_conntrack_count versus nf_conntrack_max, not at the app.

  2. 02

    SNAT vs DNAT: Which End Gets Rewritten

    NAT is just controlled packet rewriting plus the state to reverse it. SNAT rewrites the source address/port on the way out (hiding a private network behind one public IP); DNAT rewrites the destination on the way in (a load balancer or port-forward sending traffic to a backend); REDIRECT is a special DNAT to the local machine. In all cases nf_nat_manip_pkt() performs the actual header rewrite, and conntrack stores the mapping so the reply — which arrives with the translated addresses — is rewritten back to what the original peer expects. The reverse translation is entirely dependent on the reply traversing the same box that holds the conntrack entry.

  3. 03

    Port Exhaustion: The SNAT Ceiling Nobody Sizes For

    SNAT and MASQUERADE hide many internal hosts behind one source IP, and every simultaneous flow to the same destination IP:port needs a unique source port so the reply is unambiguous. That means the real concurrency ceiling toward a single destination is the number of free ephemeral ports — commonly ~28,000 for the default 32768–60999 range — not conntrack_max. When a new flow needs a mapping, nf_conntrack_tuple_taken() checks whether the candidate tuple is already in use and forces the allocator to pick another port; when none is free, the SNAT fails and connections are refused. High connection_rate × long timeout is what drives the port space toward exhaustion.

  4. 04

    Conntrack Helpers: Protocols That Break Simple NAT

    Some protocols carry IP addresses and ports inside their payload and open secondary connections on dynamically negotiated ports — FTP active mode, SIP/RTP, PPTP, TFTP. A stateful firewall that only knows the control channel will drop the data channel, because it has no matching entry. Conntrack helper modules (nf_conntrack_ftp, nf_conntrack_sip, …) parse the control stream, create a RELATED 'expectation' for the incoming data connection so it's permitted, and under NAT rewrite the addresses embedded in the payload so the far side dials back to the translated address. Helpers are powerful and also a security surface, which is why modern setups scope them tightly rather than loading them globally.

  5. 05

    Asymmetric Routing and Hairpinning: When the Return Packet Goes Missing

    Conntrack is per-box state, and NAT's correctness depends on the forward and return packets of a flow passing through the same box. Asymmetric routing breaks that: the client's SYN creates a conntrack entry and is SNATed by one router, but the SYN-ACK returns via a different router that has no entry for the flow — so no reverse translation happens and the packet is dropped as INVALID, and the connection hangs and times out silently. The related hairpin case is a host on the inside reaching another inside host by its public (DNATed) address: without a hairpin NAT rule the reply carries the internal source address and the initiator drops it. Both are 'the network is fine, ping works, but the app can't connect' problems that only make sense once you think in conntrack state.

Validated against Linux kernel netfilter v6.12.93 net/netfilter/nf_conntrack_core.c and friends. Every simulated behavior cites a real source function, and a server-side correlation engine computes how each layer's state cascades into the next. See the methodology →

This module is part of zerohop Pro — it unlocks alongside the full kata library, progress tracking, and weak-area analysis.