Last week, we examined how TCP uses sequence and acknowledgment numbers to keep track of data in bidirectional transit between two end hosts. However, we observed only an ideal TCP stream, where all packets are delivered to either end successfully, and in order. What happens if one goes missing?
The diagram below illustrates a TCP connection taking place between a client and server separated by a network. Time progresses vertically from top to bottom as packets are sent.
The client sends some request to the server, and the server formulates a response broken into four TCP segments (packets). The server transmits all four packets in response to the request. However, the second response packet is dropped somewhere on the network and never reaches the host. Let's walk through what happens.
Response segment #2 is lost.
The client receives segment #3. Upon examining the segment's sequence number, the client realizes this segment is out of order; there is data missing between the last segment received and this one. The client transmits a duplicate acknowledgment for packet #1 to alert the server that it has not received any (reliable) data beyond packet #1.
As the server is not yet aware that anything is wrong (because it has not yet received the client's duplicate acknowledgment), it continues by sending segment #4. The client realizes that it is still missing data, and repeats its behavior in step three by sending another duplicate acknowledgment for packet #1.
The server receives the client's first duplicate acknowledgment for packet #1. Because the client has only confirmed receipt of the first of the four segments, the server must retransmit all three remaining segments in the response.
The second duplicate acknowledgment received from the client is ignored.
The client successfully receives and acknowledges the three remaining segments.
Enter Selective Acknowledgments
You've probably noticed that this design is inefficient: although only packet #2 was lost, the server was required to retransmit packets #3 and #4 as well, because the client had no way to confirm that it had received those packets.
This problem was originally addressed by RFC 1072, and more recently by RFC 2018, by introducing the selective acknowledgment (SACK) TCP option. SACKs work by appending to a duplicate acknowledgment packet a TCP option containing a range of noncontiguous data received. In other words, it allows the client to say "I only have up to packet #1 in order, but I also have received packets #3 and #4". This allows the server to retransmit only the packet(s) that were not received by the client.
Support for SACK is negotiated at the beginning of a TCP connection; if both hosts support it, it may be used. Let's look at how our earlier example plays out with SACK enabled:
Response segment #2 is lost.
The client realizes it is missing a segment between segments #1 and #3. It sends a duplicate acknowledgment for segment #1, and attaches a SACK option indicating that it has received segment #3.
The client receives segment #4 and sends another duplicate acknowledgment for segment #1, but this time expands the SACK option to show that it has received segments #3 through #4.
The server receives the client's duplicate ACK for segment #1 and SACK for segment #3 (both in the same TCP packet). From this, the server deduces that the client is missing segment #2, so segment #2 is retransmitted. The next SACK received by the server indicates that the client has also received segment #4 successfully, so no more segments need to be transmitted.
The client receives segment #2 and sends an acknowledgment to indicate that it has received all data up to an including segment #4.
Enough Theory, Here's a Capture
This packet capture contains a demonstration of SACKs in action. We know that both end hosts support selective acknowledgments by the presence of the SACK permitted option in the two SYN packets, #1 and #2.
Toward the end of the capture, we can see that packet #30 was received out of order, and the client has sent a duplicate acknowledgment in packet #31. This packet includes a SACK option indicating that the segment in packet #30 was received.
Of course, the SACK option cannot simply specify which segment(s) were received. Rather, it specifies the left and right edges of data that has been received beyond the packet's acknowledgment number. A single SACK option can specify multiple noncontiguous blocks of data (e.g. bytes 200-299 and 400-499).
We can see this duplicate acknowledgment repeated in packets #33, #35, and #37. In each, the SACK is expanded to include the noncontiguous segments the server has continued sending. Finally, the server retransmits the missing segment in packet #38, and the client updates its acknowledgment number appropriately in packet #39.