← Chapter 61 Index | Part 3 of 3
The Core Problem TCP Solves
IP is unreliable by design — it does its best to deliver packets but makes no guarantee. Packets can be lost, duplicated, reordered, or corrupted in transit. TCP is built on top of IP and adds reliability: every byte you send is guaranteed to arrive, in order, exactly once.
TCP achieves this using three mechanisms working together:
- Sequence Numbers — each byte is numbered so the receiver can detect missing or out-of-order data.
- Positive Acknowledgements — the receiver explicitly confirms what it has received.
- Retransmission Timers — if no ACK arrives in time, the sender retransmits.
How Sequence Numbers Work
When a TCP connection is established, each side picks a random starting number called the Initial Sequence Number (ISN). From that point, every byte of data sent is assigned the next sequential number. This numbering is per-direction — each side maintains its own independent sequence counter.
| Byte number in stream | Sequence number (ISN = 1000) | Data byte |
|---|---|---|
| Byte 1 | Seq = 1001 | ‘H’ |
| Byte 2 | Seq = 1002 | ‘e’ |
| Byte 3 | Seq = 1003 | ‘l’ |
| Byte 4 | Seq = 1004 | ‘l’ |
| Byte 5 | Seq = 1005 | ‘o’ |
When TCP sends a segment carrying multiple bytes, the segment’s Sequence Number field is set to the sequence number of the first byte in that segment. The receiver uses this to place received bytes in the correct position.
/* Conceptual illustration of segment sequence numbers */
Segment 1: Seq = 1001, length = 500 bytes → carries bytes 1001 to 1500
Segment 2: Seq = 1501, length = 500 bytes → carries bytes 1501 to 2000
Segment 3: Seq = 2001, length = 300 bytes → carries bytes 2001 to 2300
Positive Acknowledgements — How the Receiver Responds
When the receiver successfully accepts a segment, it sends back an ACK segment (a segment with the ACK flag set). The key value in this ACK is the Acknowledgement Number field.
Step-by-Step ACK Flow
| Sender (Host A) | Action | Receiver (Host B) |
|---|---|---|
| Sends segment Seq=1001, Len=500 |
──────────────────→
|
Receives bytes 1001–1500 |
| Receives ACK 1501. Moves window forward. |
←─────────────────
ACK=1501 (Ack #)
|
Sends ACK: Ack#=1501 (“send me byte 1501 next”) |
| Sends segment Seq=1501, Len=500 |
──────────────────→
|
Receives bytes 1501–2000 |
| Receives ACK 2001. All data acknowledged. |
←─────────────────
ACK=2001
|
Sends ACK: Ack#=2001 |
Retransmission: What Happens When a Segment Is Lost
Every time the sender transmits a segment, it starts a retransmission timer. If an acknowledgement does not arrive before the timer expires, TCP assumes the segment was lost and retransmits the same data.
| Sender | Network | Receiver |
|---|---|---|
| Sends Seq=1001, starts timer | → LOST ✗ | Never receives this segment |
| Timer expires — no ACK received | …time passes… | — (nothing) |
| Retransmits Seq=1001 | ──────────→ | Receives it, sends ACK=1501 |
The retransmission timeout (RTO) is not fixed. TCP calculates it dynamically based on the measured round-trip time (RTT). If retransmission is needed multiple times, TCP applies exponential backoff — doubling the timeout each time — to avoid flooding an already congested network.
Cumulative vs Selective ACK
The ACK number acknowledges all bytes up to and including that point. If segments arrive out of order (e.g., 1, 3, 4 but not 2), the receiver keeps sending ACK=2 (“still waiting for byte 2”). When byte 2 finally arrives, a single ACK=5 acknowledges segments 2, 3, and 4 all at once.
With SACK (negotiated during SYN via TCP options), the receiver can say “I have bytes 1–500 and 1001–1500 but missing 501–1000”. The sender can then retransmit only the missing range instead of retransmitting everything from 501 onward. SACK greatly improves performance when multiple segments are lost.
Demonstrating Sequence Numbers: A Practical TCP Sender Simulation
While you cannot manually set TCP sequence numbers in normal socket programming (the kernel handles them), understanding the logic helps debug and reason about your applications. Below is a C program that simulates the sender-side logic for educational purposes.
#include <stdio.h>
#include <stdint.h>
#include <string.h>
/*
* Simulate how TCP tracks sequence numbers.
* In real TCP, the kernel does this — but understanding it
* helps you debug Wireshark captures and race conditions.
*/
typedef struct {
uint32_t isn; /* initial sequence number */
uint32_t send_next; /* next seq# to send */
uint32_t send_una; /* oldest unacknowledged seq# */
uint32_t window; /* peer's receive window */
} tcp_send_state_t;
typedef struct {
uint32_t rcv_next; /* next expected seq# */
uint32_t rcv_window; /* our receive window */
} tcp_recv_state_t;
/* Sender: prepare a segment */
void sender_send(tcp_send_state_t *s, uint32_t data_len)
{
printf("[SENDER] Sending segment: Seq=%u Len=%u (Seq range: %u to %u)\n",
s->send_next,
data_len,
s->send_next,
s->send_next + data_len - 1);
s->send_next += data_len;
}
/* Receiver: accept a segment and generate ACK */
uint32_t receiver_accept(tcp_recv_state_t *r, uint32_t seg_seq, uint32_t seg_len)
{
if (seg_seq != r->rcv_next) {
printf("[RECEIVER] OUT OF ORDER: expected Seq=%u got Seq=%u — holding\n",
r->rcv_next, seg_seq);
/* In real TCP, this would trigger duplicate ACK */
return r->rcv_next;
}
r->rcv_next += seg_len;
uint32_t ack_num = r->rcv_next;
printf("[RECEIVER] Accepted bytes %u–%u. Sending ACK=%u\n",
seg_seq, seg_seq + seg_len - 1, ack_num);
return ack_num;
}
/* Sender: process an incoming ACK */
void sender_process_ack(tcp_send_state_t *s, uint32_t ack_num)
{
if (ack_num > s->send_una) {
printf("[SENDER] ACK=%u received. Data bytes %u–%u are now acknowledged.\n\n",
ack_num, s->send_una, ack_num - 1);
s->send_una = ack_num;
}
}
int main(void)
{
tcp_send_state_t sender = {
.isn = 1000,
.send_next = 1001, /* first data byte after SYN consumed ISN+1 */
.send_una = 1001,
.window = 65535
};
tcp_recv_state_t receiver = {
.rcv_next = 1001,
.rcv_window = 65535
};
printf("=== TCP Sequence / ACK Simulation ===\n\n");
printf("ISN = %u, first data byte seq = %u\n\n", sender.isn, sender.send_next);
/* Send segment 1: 500 bytes */
sender_send(&sender, 500);
uint32_t ack = receiver_accept(&receiver, 1001, 500);
sender_process_ack(&sender, ack);
/* Send segment 2: 500 bytes */
sender_send(&sender, 500);
ack = receiver_accept(&receiver, 1501, 500);
sender_process_ack(&sender, ack);
/* Simulate out-of-order delivery */
printf("--- Simulating out-of-order segment ---\n");
sender_send(&sender, 300);
ack = receiver_accept(&receiver, 2500, 300); /* wrong seq, should be 2001 */
sender_process_ack(&sender, ack);
/* Correct segment arrives */
printf("--- Correct segment arrives ---\n");
ack = receiver_accept(&receiver, 2001, 300);
sender_process_ack(&sender, ack);
return 0;
}
Compile and run:
gcc -o tcp_sim tcp_sim.c
./tcp_sim
Expected output:
=== TCP Sequence / ACK Simulation ===
ISN = 1000, first data byte seq = 1001
[SENDER] Sending segment: Seq=1001 Len=500 (Seq range: 1001 to 1500)
[RECEIVER] Accepted bytes 1001–1500. Sending ACK=1501
[SENDER] ACK=1501 received. Data bytes 1001–1500 are now acknowledged.
[SENDER] Sending segment: Seq=1501 Len=500 (Seq range: 1501 to 2000)
[RECEIVER] Accepted bytes 1501–2000. Sending ACK=2001
[SENDER] ACK=2001 received. Data bytes 1501–2000 are now acknowledged.
--- Simulating out-of-order segment ---
[SENDER] Sending segment: Seq=2001 Len=300 (Seq range: 2001 to 2300)
[RECEIVER] OUT OF ORDER: expected Seq=2001 got Seq=2500 — holding
--- Correct segment arrives ---
[RECEIVER] Accepted bytes 2001–2300. Sending ACK=2301
[SENDER] ACK=2301 received. Data bytes 2001–2300 are now acknowledged.
Why Is the ISN Random?
If TCP always started sequence numbers at 0 or 1, an attacker could inject fake segments into an existing connection by guessing the current sequence number. A random ISN makes this impractical — the attacker would need to guess a 32-bit number correctly. On Linux, the ISN is generated using a secure algorithm based on time and a per-connection hash. The random start also prevents confusion between new and old connections that use the same port pair.
Special Rule: SYN and FIN Each Consume One Sequence Number
Even though SYN and FIN segments carry no actual data, they each consume one sequence number. This means the receiver’s ACK for a SYN will be ISN+1, not ISN. This is why the first real data byte has sequence number ISN+1.
/* Connection establishment sequence numbers */
Client sends: SYN, Seq = 1000 (ISN)
Server replies: SYN+ACK, Seq = 5000 (server ISN), Ack = 1001 (1000+1)
Client sends: ACK, Seq = 1001, Ack = 5001 (5000+1)
/* Now the connection is ESTABLISHED */
/* First data from client will have Seq = 1001 */
/* First data from server will have Seq = 5001 */
Observing Sequence Numbers with tcpdump
You can watch real TCP sequence numbers on your own machine using tcpdump. The -S flag shows absolute sequence numbers (otherwise tcpdump shows relative numbers for readability).
# In terminal 1: start a web server (or any TCP server)
python3 -m http.server 8080
# In terminal 2: capture TCP on port 8080
sudo tcpdump -i lo -S -n 'port 8080'
# In terminal 3: make a request
curl http://localhost:8080/
# tcpdump output will show something like:
# 12:00:00 IP 127.0.0.1.54321 > 127.0.0.1.8080: Flags [S], seq 3491827410
# 12:00:00 IP 127.0.0.1.8080 > 127.0.0.1.54321: Flags [S.], seq 2183940271, ack 3491827411
# 12:00:00 IP 127.0.0.1.54321 > 127.0.0.1.8080: Flags [.], ack 2183940272
# (data segments follow...)
Notice that Flags [S.] means SYN + ACK, and the ack is always the peer’s seq + 1.
Interview Questions and Answers
ETIMEDOUT.rcv_next). If a segment’s sequence range overlaps with data already received, the receiver trims or discards the duplicate bytes. The random ISN also protects against very old duplicate packets from a previous connection appearing in a new one (in conjunction with the TIME_WAIT state).Chapter 61 Part 3 Summary
- Every byte in a TCP stream has a sequence number starting from a random ISN.
- The receiver sends a positive ACK = (last received byte seq) + 1.
- The sender starts a timer; if no ACK arrives, it retransmits with exponential backoff.
- SYN and FIN each consume one sequence number even with no data payload.
- Cumulative ACKs are standard; SACK is an optional extension for better loss recovery.
- Use
tcpdump -Sto see real sequence and ACK numbers on your own system.
