Knowledge Guide
HomeSystem DesignNetworking Fundamentals

Networking at Scale — NAT Exhaustion, L4-vs-L7-vs-DSR, QUIC Limits, MTU/PMTUD & CIDR (Deep Dive)

Every layer above IP hides a finite table, and scale bugs are almost always that table running out

A NAT gateway has a finite pool of source ports per destination. An L7 load balancer has a finite pool of worker threads holding TLS state. A connection pool has a finite number of sockets. A CIDR block has a finite number of addresses. None of these are bandwidth problems — they are counting problems: a resource that looks unlimited at low traffic and hard-fails once demand crosses its table size. This page is the practitioner's tour of those tables — where they live, how they fail, and how a senior engineer reasons about which layer to spend cost on. It builds directly on this guide's existing networking pages: The Layered Network Model — OSI, TCP/IP, and L4 vs L7 and IP Addressing, Subnets & CIDR for the basics, TCP Deep — Handshake Cost, Congestion Control & Head-of-Line Blocking and Bandwidth-Delay Product for transport mechanics, TCP Incast for fan-in collapse, and Subnetting & CIDR: usable addresses and the overlap trap for address-block sizing — none of that is re-derived here; it is assumed and extended.

1. NAT and the SNAT port table: why fan-out to ONE destination is the dangerous shape

NAT (network address translation) rewrites a packet's source IP and port as it leaves a private network, and keeps a connection table entry so return traffic can be un-translated back to the right internal host. For outbound (source) NAT specifically, the table's key is effectively the 4-tuple the far side will see: (translated-src-ip, translated-src-port, dst-ip, dst-port). Because the translated source IP is usually fixed (one or a few egress IPs) and the destination is fixed once you pick a downstream service, the only thing that can vary to keep connections distinguishable is the translated source port — and there are only ~64,512 of those (the 16-bit port space minus the ~1,024 reserved low ports). That budget is per (dst-ip, dst-port) pair, and it is shared across every internal host behind that NAT IP, not per-host. This is why the dangerous topology is specifically many source hosts → one popular destination through a shared NAT (many pods calling one database or one third-party API gateway): the port budget doesn't grow with the number of callers, only with the number of egress IPs and destinations.

2000 pods share one NAT gateway egress IP; the connection table to a single destination payment-api has a 64512 port budget; Little's Law shows 50000 requests per second held 1.3 seconds needs 65000 concurrent connections, exceeding the budget so about 500 connections per second fail
2000 pods share one NAT gateway egress IP; the connection table to a single destination payment-api has a 64512 port budget; Little's Law shows 50000 requests per second held 1.3 seconds needs 65000 concurrent connections, exceeding the budget so about 500 connections per second fail

Traced example: port exhaustion arithmetic

QuantityValueWhy
Ephemeral port range1,024–65,535 → 64,512 usablePorts below 1,024 are reserved for well-known services
NAT egress IPs1Single shared gateway IP for 2,000 pods
Destinationpayment-api : 443 (one dst-ip, dst-port pair)All pods call the same downstream service
Port budget for this destination64,512Bounded by ports, not by pod count
Arrival rate λ50,000 requests/secTraffic spike (e.g. batch job fan-out)
Hold time per connection h1.3 sTLS handshake + request/response, connection not reused (no pooling)
Concurrent connections needed (Little's Law, L = λh)50,000 × 1.3 = 65,000Little's Law: number in the system = arrival rate × time each spends in it
Result65,000 > 64,512~500 conns/sec have no free translated port for this destination → SYN retransmits, connection refused/timeouts

Notice what does not appear in this arithmetic: pod count. Whether it's 200 pods or 20,000 pods behind that NAT, the ceiling is the same 64,512, because the constraint is keyed by destination, not by caller. This is also why the failure is intermittent and confusing in production — most requests succeed (there's almost always a free port most of the time), and only the requests that land when the table is momentarily full fail, which looks like flaky networking rather than a capacity limit.

Mitigations, in the order a senior engineer reaches for them

2. L4 vs L7 vs DSR: where the load balancer sits changes what it costs

The three load-balancing shapes differ in how much of the connection the LB owns, and that difference is exactly what makes each one expensive or cheap in a particular direction.

Three lanes compare L4 passthrough load balancing, L7 load balancing that terminates TLS and buffers, and DSR direct server return where the backend replies straight to the client bypassing the load balancer on the return path
Three lanes compare L4 passthrough load balancing, L7 load balancing that terminates TLS and buffers, and DSR direct server return where the backend replies straight to the client bypassing the load balancer on the return path

Cost ordering and when each wins

Cost to operate climbs L4 → L7 (stateless forwarding → stateful termination), while DSR trades LB cost for topology constraints rather than removing cost outright. A senior engineer picks based on what the traffic shape demands: symmetric small request/response and cost-sensitive fan-out → L4; need content-aware routing or centralized TLS/WAF → L7 (and budget for its CPU/memory); wildly asymmetric payloads (video, big downloads) where the LB would otherwise choke on return traffic → DSR.

3. QUIC's limits — it is not "TCP but strictly better"

QUIC's headline pitch is fixing TCP's cross-stream head-of-line (HOL) blocking: over TCP, HTTP/2 multiplexes many logical streams onto one ordered byte stream, so one lost packet stalls every stream until it's retransmitted, even streams that had nothing to do with the loss. QUIC gives each stream its own independent loss-recovery sequence, so a lost packet on stream A no longer blocks stream B. That is a real and valuable fix — but it is narrower than it sounds, and it comes with real costs the guide should name explicitly:

When QUIC helps vs. hurts

QUIC earns its keep on lossy or variable networks (mobile, Wi-Fi, satellite) and workloads with genuinely independent multiplexed streams (many small requests over one connection, connection migration across a network change) — exactly the case where cross-stream HOL blocking on TCP hurts most. It is the wrong tool on low-loss, CPU-constrained, high-throughput paths — intra-datacenter service mesh traffic, for instance, where kernel TCP with hardware offload is cheaper per byte and loss is rare enough that HOL blocking rarely triggers anyway.

4. DNS is part of the connection, not a step before it

It's easy to reason about connection cost as "TCP handshake + TLS handshake" and forget the RTT that happens before either: resolving the hostname. A cold connection's true latency is:

StepCostNotes
DNS resolution~1 RTT (cache miss)0 if cached (client OS/browser/resolver cache) — but a cache miss is a full round trip to a resolver before any packet reaches the actual server
TCP handshake1 RTTSYN → SYN-ACK → ACK, as covered in TCP Deep — Handshake Cost
TLS handshake1 RTT (TLS 1.3) / 2 RTT (TLS 1.2)Key exchange + certificate verification
First application byte+1 RTT for the actual request/responseThe part most people count as "the request"

So a fully cold request over TLS 1.3 costs roughly 3–4 RTTs before the app even sees the response — and DNS is a full quarter to a third of that, invisible in most latency dashboards because it's not "the connection," it's what happens before it. This matters most for tail latency: any request that happens to hit a DNS cache miss, a cold TCP+TLS pair, and a busy backend stacks all of those RTTs, which is why the p99 can be several multiples of the median. It's also why connection reuse (keep-alive pools) and pre-connect (warming a DNS+TCP+TLS-established connection before it's needed, e.g. browsers' rel=preconnect or a backend pre-warming its downstream pool) are disproportionately effective latency fixes — they eliminate 3–4 RTTs of setup cost entirely, rather than shaving milliseconds off the request itself.

5. MTU, MSS, and the PMTUD blackhole — "small payloads work, large ones hang"

Every link on a path has a maximum transmission unit (MTU) — the biggest frame it can carry. TCP negotiates a maximum segment size (MSS) at handshake time assuming the standard Ethernet path MTU (1,500 bytes → MSS ≈ 1,460), and relies on Path MTU Discovery (PMTUD) to shrink that assumption if some hop on the path has a smaller MTU. PMTUD works by setting the "Don't Fragment" (DF) bit on outgoing packets; if a router can't forward a DF packet because it's too big for the next hop, it drops the packet and sends back an ICMP "Fragmentation Needed" message telling the sender to use a smaller size. The mechanism completely depends on that ICMP message getting back to the sender — and a very common piece of network hardening is to block inbound ICMP at a firewall, which silently breaks PMTUD without anyone intending to.

Traced example: the blackhole

StepWhat happens
1Client and server complete the TCP handshake; MSS negotiated at ~1,460 bytes, assuming a clean 1,500-byte MTU path
2The real path has a hidden hop with a smaller MTU — e.g. an IPsec/GRE tunnel whose overhead drops the effective MTU to ~1,400 bytes
3Small requests (well under 1,400 bytes per segment) traverse every hop fine — nothing ever exceeds the tunnel's real limit
4A large payload generates a ~1,460-byte segment with DF set (TCP's default); it hits the tunnel hop, is too big, and the hop can't fragment it (DF forbids that) — so it drops the packet and sends an ICMP "Fragmentation Needed" back toward the client
5A firewall between the tunnel hop and the client blocks inbound ICMP as routine hardening — the "shrink your packets" message never arrives
6The client, having heard nothing, keeps retransmitting the same 1,460-byte segment; it keeps getting silently dropped at the tunnel hop → the connection hangs specifically on large payloads while small ones keep working perfectly
7Fix: MSS clamping at the tunnel edge — the edge device rewrites the MSS option inside the SYN/SYN-ACK to a path-safe value (e.g. 1,360) so the client never generates an oversized segment in the first place. No ICMP round trip required, so a blocked-ICMP firewall can't break it.

The diagnostic fingerprint to remember: small requests succeed, large ones hang or time out, and it "worked yesterday on a different network path." That combination points straight at a PMTUD blackhole before you look at anything else.

6. Long-lived streams (WebSocket/gRPC) pin a connection to one worker for its whole life

A WebSocket connection or a gRPC stream is not a short request/response — it's a connection meant to stay open for minutes or hours. Wherever an L7 tier sits in the path, that tier must keep the stream pinned to one specific backend worker for the stream's entire lifetime (it can't hand the stream off mid-flight the way it can route a fresh short request to any healthy backend). Two consequences follow directly:

Where each tier fits: an L4 tier carries long-lived streams cheaply — it's just forwarding packets for the connection's life, no per-stream buffering cost. An L7 tier can carry them but pays the worker-pinning cost above. DSR is a poor fit for bidirectional long-lived streams (it's built around a request-in/response-out asymmetry, not a persistent two-way channel).

7. Connection-pool downsides, and CIDR at scale

Connection pooling (§1's mitigation for NAT exhaustion) is not free. Idle pooled connections still hold a socket, file descriptor, and often TLS session state on both ends — a large pool sitting mostly idle is memory and FD pressure on the server even when it's doing no work. Worse, a pooled connection can silently point at a backend that's since been drained or rotated out: unless the pool actively health-checks idle members, a client can keep sending requests down a "connection" whose far end is gone until a request actually fails and forces re-validation. Separately, many low-latency systems disable Nagle's algorithm (TCP_NODELAY) to avoid the ~40 ms coalescing delay it can add to small writes — but that trade increases the raw packet-per-second rate for the same payload, and PPS (not bandwidth) is what saturates many NICs and middleboxes first.

CIDR block sizing has the same finite-table character at cluster scale. This guide's Subnetting & CIDR: usable addresses and the overlap trap already covers the core arithmetic (every subnet reserves network + broadcast addresses, and cloud providers reserve more on top, so sizing off the "total" address count over-provisions on paper and under-provisions in practice) and the VPC-peering overlap trap — that material is assumed here. Two scale-specific failure modes worth adding on top:

Pitfalls

Judgment layer: how a senior engineer decides

L4 vs L7 vs DSR

Choose…WhenYou give up
L4Symmetric traffic, cost-sensitive high-throughput fan-out, long-lived streams (WebSocket/gRPC), or you don't need content-aware routingContent-based routing, centralized TLS termination, per-request observability at the LB
L7You need path/host routing, WAF, centralized cert management, or auth termination at the edgeA stateful CPU/memory-bound tier that can itself become the bottleneck under connection churn or long-lived-stream pinning
DSRWildly asymmetric traffic (small request, large response — video/streaming/bulk download) where the LB would otherwise bottleneck on return bytesTopology flexibility (needs same L2 or tunneling) and any content-aware handling of the response

TCP vs QUIC

Choose…WhenYou give up
QUIC (HTTP/3)Lossy/variable networks (mobile, Wi-Fi), many independent multiplexed streams, need for connection migration across network changesLower per-packet CPU cost of kernel-offloaded TCP; simplicity of well-understood middlebox behavior (some networks still block/throttle UDP)
TCP (HTTP/2 or below)Low-loss, CPU-constrained, high-throughput paths (intra-datacenter, service mesh); single dominant large stream where cross-stream HOL blocking doesn't apply anywayCross-stream HOL blocking under real-world packet loss on the public internet

Takeaways

Related pages

🤖 Don't fully get this? Learn it with Claude

Stuck on Networking at Scale — NAT Exhaustion, L4-vs-L7-vs-DSR, QUIC Limits, MTU/PMTUD & CIDR (Deep Dive)? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Networking at Scale — NAT Exhaustion, L4-vs-L7-vs-DSR, QUIC Limits, MTU/PMTUD & CIDR (Deep Dive)** (System Design) and want to truly understand it. Explain Networking at Scale — NAT Exhaustion, L4-vs-L7-vs-DSR, QUIC Limits, MTU/PMTUD & CIDR (Deep Dive) from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Networking at Scale — NAT Exhaustion, L4-vs-L7-vs-DSR, QUIC Limits, MTU/PMTUD & CIDR (Deep Dive)** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Networking at Scale — NAT Exhaustion, L4-vs-L7-vs-DSR, QUIC Limits, MTU/PMTUD & CIDR (Deep Dive)** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Networking at Scale — NAT Exhaustion, L4-vs-L7-vs-DSR, QUIC Limits, MTU/PMTUD & CIDR (Deep Dive)** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes