The Runtime Theory
SystemArchitecturenetwork

What happens when TCP retransmits a packet?

A step-by-step walk of loss recovery: the RTO timer, exponential backoff, fast retransmit on duplicate ACKs, SACK, and what each path costs.

The Runtime Theory Team1 min read05 steps

trace spine

  1. 01 A segment never arrives
  2. 02 RTO fires, backoff doubles
  3. 03 Duplicate ACKs trigger fast retransmit
  4. 04 SACK narrows the recovery
  5. 05 Window recovery and cwnd math

Loss is TCP's operating condition, not its failure mode. The sender must detect that a segment vanished, decide which one, and resend it — and the difference between the good recovery path and the bad one is measured in RTTs. This trace follows a single lost segment.

trace stepKernel

The sender transmitted segments 100–109; segment 103 dies in the network (a full buffer somewhere, a flaky radio, a checksum failure). The receiver still gets 104–109, and since those are out of order, its kernel ACKs the last in-order byte — 103 — producing duplicate ACKs. The receiver keeps ACKing 103 on every subsequent segment. Meanwhile the sender's retransmission timer — set when 103 went out — is ticking.

trace stepKernel

If the RTO expires before any ACK arrives, the sender retransmits the oldest unacknowledged segment and — critically — doubles the RTO for the next try: 1s → 2s → 4s → 8s (capped at 120s by default). This is the slow path: timeout recovery stalls the connection for the full backoff interval. A single lost packet on a 40 ms RTT path can cost a 1-second stall. This is what "the connection just froze" means at the kernel level.

trace stepKernel

The fast path: the third duplicate ACK (three dup-ACKs total) triggers fast retransmit — the sender retransmits segment 103 immediately, without waiting for the timer. Recovery starts at ~1 RTT instead of 1+ seconds. This works because dup-ACKs prove the network is still delivering — the loss was isolated, not catastrophic. This is why TCP can't fast-retransmit on the first dup-ACK: one ACK can be reordered by the network itself.

bash
nstat -az | grep -E "TcpRetransSegs|TcpLostRetransmit|TcpOutSegs"
trace stepKernel

With SACK negotiated at handshake time, the receiver's ACKs carry block information: "I have 100–102, 104–109, missing 103." The sender now knows exactly what to resend — one segment, not the whole window. Without SACK (or with a burst loss), the sender falls back to retransmitting everything from the lost byte forward, wasting bandwidth and reordering the stream. Modern kernels enable SACK by default; net.ipv4.tcp_sack should never be off.

trace stepKernel

Recovery closes by halving the window — the congestion response we traced in tcp-congestion-trace: ssthresh = cwnd / 2, sender enters fast recovery, and the ACK for the retransmitted segment returns the connection to congestion avoidance. The full ledger for this trace: 1 lost segment → ~1 RTT of stall, one halved window, one retransmitted packet. The timeout path: 1 RTT → seconds of stall, backoff-doubled, same window penalty — the same packet, ten to a hundred times the cost.

What the machine actually does when a packet dies is a race between two timers — the RTO and the dup-ACK counter — with the sender's entire latency reputation on the line. The kernel chooses whichever evidence arrives first, and the gap between the good outcome (~1 RTT) and the bad one (seconds) is the single largest latency cliff in all of TCP.