Loading learning content...
When TCP sends ACK=5000, it makes a powerful statement: "I have received every single byte from the beginning of the stream up to byte 4999, and I'm ready for byte 5000." This is cumulative acknowledgment—each ACK implicitly acknowledges all prior data.
Cumulative acknowledgments are elegant and robust, but they come with limitations. When a packet in the middle of a stream is lost, the ACK "sticks"—it can't skip ahead to report successfully received later data. The sender knows something is missing at the ACK position but doesn't know what arrived after.
This page explores cumulative acknowledgments in depth: their design, their behavior under loss, their limitations, and how SACK (Selective Acknowledgment) extends the model to provide richer feedback.
By the end of this page, you will understand: why TCP uses cumulative acknowledgments, how cumulative ACKs behave under packet loss, the limitations of cumulative-only acknowledgments, how duplicate ACKs trigger fast retransmit, what Selective Acknowledgment (SACK) adds, and practical implications for loss recovery performance.
TCP's acknowledgment scheme is cumulative—each acknowledgment number confirms receipt of all bytes with sequence numbers less than the ACK value.
Formal Definition:
If a receiver sends ACK = N:
The "Contiguous Prefix" Property:
Cumulative ACKs always acknowledge a contiguous prefix of the byte stream. There can be no holes or gaps in the acknowledged range. If bytes 1000-1999 and 3000-3999 have been received but 2000-2999 are missing, the ACK is 2000—not 4000.
Cumulative ACK Semantics:═══════════════════════════════════════════════════════════════ Single ACK covers entire prefix: Received bytes: 1000, 1001, 1002, ..., 4999 ACK sent: 5000 What ACK=5000 confirms: ✓ Byte 1000 received ✓ Byte 1001 received ✓ Byte 1002 received ✓ ... (every byte in between) ✓ Byte 4999 received This is IMPLICIT - sender doesn't need separate ACK for each byte ───────────────────────────────────────────────────────────────── Cumulative property - later ACK subsumes earlier ACKs: ACK=2000 followed by ACK=5000 The ACK=5000 implicitly includes everything ACK=2000 said: - Bytes < 2000: Already confirmed by ACK=2000 - Bytes 2000-4999: Now confirmed by ACK=5000 If ACK=2000 was lost in transit, ACK=5000 provides complete info ───────────────────────────────────────────────────────────────── Contiguous prefix requirement: Received: [1000-1999] and [3000-3999] (gap at 2000-2999) CANNOT send: ACK=4000 (would falsely claim bytes 2000-2999) MUST send: ACK=2000 (honest about what's missing) The out-of-order bytes [3000-3999] are BUFFERED but not ACKedCumulative ACKs are robust to ACK loss. If ACK=2000 is lost but ACK=3000 arrives, the sender gets all necessary information—bytes 1000-2999 are confirmed. There's no need to retransmit the lost ACK. This is a key advantage over per-segment acknowledgment schemes.
Cumulative acknowledgments behave distinctively when packets are lost. Let's trace through a scenario:
Scenario: Middle segment lost
Sender transmits 5 segments (1000 bytes each):
Key Observations:
ACK sticks at the gap: Despite receiving segments 3, 4, and 5, the ACK remains at 2000
Receiver buffers out-of-order data: Segments 3, 4, 5 are not discarded—they're buffered awaiting segment 2
Duplicate ACKs indicate problems: Each out-of-order segment triggers the same ACK (2000), creating "duplicate ACKs"
Gap fill triggers jump: When segment 2 arrives, ACK jumps from 2000 to 6000, acknowledging everything at once
No retransmission of segments 3, 4, 5 needed: They were buffered, not lost
With cumulative ACKs alone, the sender only knows that byte 2000 is missing. It doesn't know that bytes 3000-5999 arrived successfully. This information gap affects loss recovery efficiency—the sender might unnecessarily retransmit segments that arrived but can't be acknowledged yet.
Duplicate ACKs—receiving the same acknowledgment number multiple times—signal to the sender that data is arriving out of order, likely because a segment was lost.
Why Duplicates Indicate Loss:
Every segment after the lost one triggers a duplicate ACK:
Fast Retransmit (RFC 5681):
After receiving 3 duplicate ACKs (4 total ACKs with the same value), the sender retransmits the missing segment without waiting for a timeout:
dupACK count = 0
on receiving ACK:
if ACK == last_ACK:
dupACK count++
if dupACK count >= 3:
retransmit segment at ACK position
Fast Retransmit Timeline:═══════════════════════════════════════════════════════════════ Time Event dupACK count──── ───── ────────────0 Send SEQ=1000 1 Send SEQ=2000 2 Send SEQ=3000 3 Send SEQ=4000 4 Receive ACK=2000 (for SEQ=1000) 0 (new ACK)5 — SEQ=2000 lost in network —6 Receive ACK=2000 (for SEQ=3000) 1 (first dup)7 Receive ACK=2000 (for SEQ=4000) 2 (second dup)8 Send SEQ=5000 (pipeline continues)9 Receive ACK=2000 (for SEQ=5000) 3 (third dup) → FAST RETRANSMIT!10 Retransmit SEQ=2000 ...15 Receive ACK=6000 Recovery complete! ───────────────────────────────────────────────────────────────── Performance comparison: Without Fast Retransmit: Wait for RTO timeout (e.g., 1000ms) Then retransmit and continue With Fast Retransmit: Detect loss after ~3 segment times (maybe 30ms) Retransmit immediately → Recovery 10-100x faster!Duplicate ACKs indicate out-of-order delivery, which usually means loss. But network reordering can also cause duplicates. Modern TCP implementations have tunable thresholds (DUPACK threshold) and use additional signals (like DSACK) to distinguish loss from reordering.
While cumulative ACKs are robust, they have significant limitations for loss recovery:
Limitation 1: Information Loss
The sender only knows where the gap starts, not what arrived after. Consider:
The sender can infer SEQ 2000 is lost, but it doesn't know SEQ 5000 is also lost until the first gap is filled.
| Scenario | Information Available to Sender | Consequence |
|---|---|---|
| Single segment lost | Which segment is missing | Efficient recovery possible |
| Multiple segments lost | Only first missing segment | May need multiple RTTs to recover all |
| Tail loss (last segments) | No duplicate ACKs generated | Must rely on timeout (slow) |
| Burst loss (consecutive) | Only first is known | May over-retransmit after first recovery |
| Sparse loss (spread out) | Hidden until prior gaps filled | Very slow iterative recovery |
Limitation 2: Tail Loss
When the last segments in a burst are lost, there are no subsequent segments to trigger duplicate ACKs. The sender must wait for a timeout:
Limitation 3: Retransmission Ambiguity
When a retransmitted segment is acknowledged, the sender can't tell if the ACK is for the original or the retransmit (Karn's algorithm addresses this by not using retransmission RTT samples).
With only cumulative ACKs, recovering from burst losses can take O(n) round trips where n is the number of lost segments. Each recovery round might reveal another lost segment. Selective Acknowledgment was designed specifically to address this limitation.
RFC 2018 introduced Selective Acknowledgment (SACK) to provide richer feedback without abandoning cumulative ACKs.
How SACK Works:
SACK adds a TCP option that reports blocks of contiguous data received out of order. The cumulative ACK still reports the left edge of the gap, but SACK blocks describe what's been received beyond that point.
Cumulative ACK = 2000 (first gap at byte 2000)
SACK blocks = [3000-4000], [5000-7000]
This tells the sender:
SACK in Action:═══════════════════════════════════════════════════════════════ Sender transmits 8 segments (SEQ 1000-8999, 1000 bytes each)Lost: SEQ 2000 and SEQ 5000 Receiver state after all non-lost segments arrive: Received: [1000-2000) [3000-5000) [6000-9000) Gaps: [2000-3000) [5000-6000) Without SACK: ACK = 2000 Sender knows: "something's wrong at byte 2000" With SACK: ACK = 2000 SACK Option: 3000-5000, 6000-9000 Sender knows: ✓ Bytes 0-1999: ACKed (by cumulative ACK = 2000) ✗ Bytes 2000-2999: MISSING (gap before first SACK block) ✓ Bytes 3000-4999: Received (SACK block 1) ✗ Bytes 5000-5999: MISSING (gap between SACK blocks) ✓ Bytes 6000-8999: Received (SACK block 2) ───────────────────────────────────────────────────────────────── SACK-based recovery: Round 1: Retransmit SEQ=2000 AND SEQ=5000 simultaneously Round 2: Receive ACK=9000 (complete recovery in 1 RTT!) Without SACK: Round 1: Retransmit SEQ=2000 (only known loss) Round 2: ACK=5000, sender now learns SEQ=5000 lost Round 3: Retransmit SEQ=5000 Round 4: ACK=9000 (recovery took 3 extra RTTs)SACK Option Format:
SACK Negotiation:
SACK must be negotiated during connection setup:
Modern operating systems enable SACK by default. It significantly improves loss recovery, especially on high-bandwidth, lossy paths. Unless you're dealing with legacy systems or specific firewall restrictions, you can expect SACK to be available.
RFC 2883 extends SACK with Duplicate SACK (DSACK)—a mechanism to report when duplicate data arrives. This provides valuable feedback about network behavior.
How DSACK Works:
A DSACK block reports receipt of data that was already acknowledged (via cumulative ACK) or already reported (via SACK). The first SACK block in a DSACK-bearing segment covers the duplicate range.
Why DSACK Matters:
DSACK Scenarios:═══════════════════════════════════════════════════════════════ Scenario 1: Spurious Retransmit───────────────────────────────────────────────────────────────── 1. Sender transmits SEQ=1000, LEN=10002. Segment delayed (not lost)3. Sender's RTO fires, retransmits SEQ=10004. BOTH original and retransmit arrive at receiver Receiver sends: ACK = 2000 (cumulative, as expected) SACK Block: [1000, 2000) ← This is a DSACK! Why is it DSACK? The SACK block [1000, 2000) is entirely BELOW ACK=2000 This means: "I already ACKed this, but I got another copy" Sender learns: "My retransmit was unnecessary - original arrived!" "Maybe adjust my RTO or reordering tolerance" ═══════════════════════════════════════════════════════════════ Scenario 2: Network Duplication───────────────────────────────────────────────────────────────── 1. Sender transmits SEQ=5000, LEN=5002. Network duplicates the packet3. Receiver gets two copies First arrival: ACK = 5500 Second arrival: ACK = 5500 (unchanged) SACK Block: [5000, 5500) ← DSACK Sender learns: "Network duplicated a packet" "Not a retransmission event on my end" ═══════════════════════════════════════════════════════════════ DSACK Identification Rule: A SACK block is DSACK if: - Its left edge < cumulative ACK OR - It's covered by a subsequent SACK block in the same segmentWith DSACK feedback, TCP can distinguish between true packet loss and aggressive retransmission. Algorithms like F-RTO use DSACK to undo congestion window reductions after spurious retransmits. This improves throughput on networks with variable delay.
Cumulative acknowledgments deeply influence TCP's congestion control behavior. Let's examine the connection:
ACK-Clocking:
ACKs provide "clocking" for the sender. Each ACK (roughly) indicates that some data has left the network, making room for new data:
This "conservation of packets" principle (Van Jacobson) keeps the network stable.
| ACK Pattern | Congestion Control Response | Rationale |
|---|---|---|
| Progressive ACKs | Increase congestion window (slow start/AIMD) | Network has capacity, can send more |
| Duplicate ACKs (3) | Fast retransmit + fast recovery | Packet lost, reduce rate moderately |
| Timeout (no ACKs) | Slow start from initial window | Severe congestion, reset and restart |
| Large ACK jump | "Burst" of new packets allowed | Multiple segments ACKed at once (delayed ACK) |
| ACK with reduced window | Reduce bytes in flight | Receiver buffer pressure |
The Cumulative ACK Limitation in Congestion Control:
Traditional congestion control (Reno, NewReno) struggles with multiple losses because cumulative ACKs provide limited information:
SACK-based Congestion Control:
With SACK, algorithms like TCP SACK-based recovery can:
Modern TCP congestion control algorithms (CUBIC, BBR) strongly benefit from SACK. While they can operate with cumulative ACKs only, performance with multiple losses degrades significantly. SACK is considered essential for high-performance TCP.
Cumulative acknowledgments form the foundation of TCP's reliability feedback mechanism. While they have limitations, extensions like SACK address the gaps. Let's consolidate what we've learned:
Module Complete:
You've now completed the TCP Sequence Numbers module. You understand:
These concepts form the foundation for understanding TCP's reliability mechanisms, which we'll explore further in subsequent modules on flow control, congestion control, and retransmission strategies.
Congratulations! You've mastered TCP sequence numbers—the fundamental mechanism that enables reliable, ordered delivery over unreliable networks. You now understand how bytes are numbered, how acknowledgments work, and why these mechanisms are essential to TCP's operation. This knowledge is foundational for understanding flow control, congestion control, and TCP performance optimization.