Computer Networks

The Transport Layer

TCP Connections and Reliability

RELIABLE every byte sent arrives, or the

JrCodex·9 min read

Jr Codex Computer Networks Notes

Level: Intermediate Prerequisites: Chapter 2: UDP Time to complete: ~25 minutes


Table of Contents

  1. What TCP Promises
  2. The Header
  3. The Three-Way Handshake
  4. Sequence Numbers and ACKs
  5. Detecting and Retransmitting Loss
  6. Closing a Connection
  7. Summary & Next Steps

1. What TCP Promises

The Guarantees
─────────────────────────────────────────
  RELIABLE     every byte sent arrives, or the
               connection fails and you are told
  ORDERED      bytes are delivered in the order
               sent
  DE-DUPLICATED duplicates are discarded
  ERROR-CHECKED corrupted segments are detected
               and resent
  FLOW-CONTROLLED a fast sender cannot overwhelm a
               slow receiver (Chapter 4)
  CONGESTION-  the network is not overwhelmed
  CONTROLLED   either (Chapter 4)
─────────────────────────────────────────
The Framing It Provides — and Does Not
─────────────────────────────────────────
  TCP delivers an ordered BYTE STREAM.

  It does NOT preserve message boundaries
  (Chapter 1). Everything above must define its own
  framing.

  Worth restating because it is the most common
  practical mistake in TCP programming.
─────────────────────────────────────────

2. The Header

20 Bytes, Minimum
─────────────────────────────────────────
   0                16                       31
  ┌─────────────────┬────────────────────────┐
  │   Source Port   │   Destination Port     │
  ├─────────────────┴────────────────────────┤
  │           Sequence Number                │
  ├──────────────────────────────────────────┤
  │        Acknowledgement Number            │
  ├────┬────┬───────┬────────────────────────┤
  │Off │Rsv│ Flags │        Window          │
  ├────┴───┴───────┼────────────────────────┤
  │    Checksum     │    Urgent Pointer      │
  ├─────────────────┴────────────────────────┤
  │        Options (SACK, timestamps, MSS,   │
  │                 window scale)            │
  └──────────────────────────────────────────┘
─────────────────────────────────────────
The Flags
─────────────────────────────────────────
  SYN   synchronise — open a connection
  ACK   this segment acknowledges data
  FIN   finished sending — begin closing
  RST   reset — abort immediately
  PSH   deliver to the application now, do not
        buffer
  URG   urgent pointer is valid (essentially
        unused)
─────────────────────────────────────────
RST vs FIN — a Practical Distinction
─────────────────────────────────────────
  FIN   a graceful close. "I have finished sending;
        data already in flight will still arrive."

  RST   an abort. "This connection is invalid; drop
        it now." Sent when connecting to a closed
        port, when a connection is in a bad state,
        or on SO_LINGER with a zero timeout.

  "Connection reset by peer" means you received an
  RST. It usually indicates the far end crashed,
  a firewall injected it, or the application closed
  abruptly with unread data.
─────────────────────────────────────────

3. The Three-Way Handshake

Opening a Connection
─────────────────────────────────────────
  CLIENT                          SERVER
    │                               │
    │──── SYN, seq=x ──────────────►│
    │                               │  SYN_RECEIVED
    │◄─── SYN-ACK, seq=y, ack=x+1 ──│
    │                               │
    │──── ACK, ack=y+1 ────────────►│
    │                               │  ESTABLISHED
    │         ESTABLISHED           │
─────────────────────────────────────────
Why Exactly Three
─────────────────────────────────────────
  Both sides must agree on the other's INITIAL
  SEQUENCE NUMBER, and each must know that the
  other knows.

    SYN      client tells the server its ISN
    SYN-ACK  server acknowledges it AND sends its
             own
    ACK      client acknowledges the server's

  Two messages would leave the server unsure the
  client received its ISN. Four would be redundant,
  since the server's acknowledgement and its own
  SYN are combined.

  Three is the minimum for mutual confirmation.
─────────────────────────────────────────
THE COST: ONE ROUND TRIP BEFORE ANY DATA
─────────────────────────────────────────
  On a 100ms path, that is 100ms before the first
  byte of the request is even sent.

  Add TLS (Module 6, Chapter 3) and it is 200-300ms
  before anything useful moves.

  This latency is the reason for HTTP keep-alive,
  connection pooling, TCP Fast Open, and ultimately
  QUIC's 0-RTT handshake (Chapter 5). A large part
  of modern protocol design is an attack on these
  round trips.
─────────────────────────────────────────
Why the ISN Is Random
─────────────────────────────────────────
  If initial sequence numbers were predictable, an
  attacker could inject data into a connection
  without seeing it — guessing valid sequence
  numbers.

  Modern stacks generate the ISN from a
  cryptographic function of the 4-tuple and a
  secret. It also prevents old duplicate segments
  from a previous connection with the same tuple
  being accepted.
─────────────────────────────────────────
SYN FLOOD
─────────────────────────────────────────
  An attacker sends many SYNs with spoofed source
  addresses and never completes the handshake.

  Each leaves the server holding half-open
  connection state, until its table fills and
  legitimate connections are refused.

  DEFENCE — SYN COOKIES: the server encodes the
  connection state into its own ISN and keeps NO
  state until the final ACK arrives, which proves
  the client is real.
─────────────────────────────────────────

4. Sequence Numbers and ACKs

What They Count
─────────────────────────────────────────
  Sequence numbers count BYTES, not segments.

  Send 100 bytes starting at seq=1000:
    this segment covers bytes 1000-1099
    the next segment starts at seq=1100

  The ACK number is the NEXT BYTE EXPECTED, not the
  last byte received.

    receiving bytes 1000-1099 ──► ack = 1100
    meaning "I have everything up to 1099; send
    1100 next"
─────────────────────────────────────────
CUMULATIVE ACKNOWLEDGEMENT
─────────────────────────────────────────
  An ACK confirms EVERYTHING up to that point, not
  just one segment.

  + a lost ACK is harmless — the next one covers it
  + fewer ACKs are needed

  - it cannot express "I got 1000-1099 and
    1200-1299 but not 1100-1199". The ACK can only
    say 1100.

  SACK (Selective Acknowledgement), a TCP option,
  fixes this by listing the received blocks
  explicitly — so the sender resends only the
  actual gap rather than everything after it.
  Essential on lossy paths, and enabled by default
  everywhere.
─────────────────────────────────────────
class TCPReceiver:
    """Reassembling an ordered stream from segments that may arrive out of order."""
 
    def __init__(self, isn):
        self.next_expected = isn + 1
        self.out_of_order = {}                 # seq -> data, held until the gap fills
        self.delivered = bytearray()
 
    def on_segment(self, seq, data):
        if seq < self.next_expected:
            return self.next_expected          # DUPLICATE — already have it, re-ACK
 
        if seq > self.next_expected:
            self.out_of_order[seq] = data      # a GAP — buffer, do not deliver yet
            return self.next_expected          # ACK the gap: a DUPLICATE ACK
 
        self.delivered.extend(data)            # exactly what we wanted
        self.next_expected = seq + len(data)
 
        while self.next_expected in self.out_of_order:      # drain what the gap unblocked
            buffered = self.out_of_order.pop(self.next_expected)
            self.delivered.extend(buffered)
            self.next_expected += len(buffered)
 
        return self.next_expected

5. Detecting and Retransmitting Loss

Two Detection Mechanisms
─────────────────────────────────────────
  1. TIMEOUT (RTO)
     The sender starts a timer per segment. No ACK
     before it expires ──► assume loss, retransmit.
     SLOW: the RTO is conservative, at least
     several hundred milliseconds.

  2. DUPLICATE ACKS ──► FAST RETRANSMIT
     Every out-of-order segment makes the receiver
     re-send the same ACK. THREE duplicate ACKs
     ──► the sender retransmits IMMEDIATELY without
     waiting for the timer.
     FAST: roughly one round trip.
─────────────────────────────────────────
Why Three Duplicates
─────────────────────────────────────────
  Packet REORDERING also produces duplicate ACKs,
  and reordering is not loss.

  One or two duplicates are likely reordering.
  Three strongly suggests a genuine gap.

  It is a threshold chosen empirically to balance
  fast recovery against spurious retransmission —
  a recurring theme in TCP's design.
─────────────────────────────────────────
def estimate_rto(srtt, rttvar, sample_rtt, alpha=0.125, beta=0.25):
    """Jacobson's algorithm. RTO adapts to both the mean AND the variance of RTT."""
    if srtt is None:
        srtt, rttvar = sample_rtt, sample_rtt / 2
    else:
        rttvar = (1 - beta) * rttvar + beta * abs(srtt - sample_rtt)
        srtt   = (1 - alpha) * srtt + alpha * sample_rtt
    rto = srtt + 4 * rttvar                    # ← the variance term is the key insight
    return srtt, rttvar, max(rto, 0.2)         # never below ~200ms
 
srtt = rttvar = None
for sample in [0.10, 0.11, 0.09, 0.35, 0.10]:          # note the spike
    srtt, rttvar, rto = estimate_rto(srtt, rttvar, sample)
    print(f"sample {sample:.2f}s ──► RTO {rto:.3f}s")
Why the Variance Term Matters
─────────────────────────────────────────
  Early TCP used only the mean RTT, and on a
  network with variable delay it retransmitted
  constantly — packets that were merely late were
  assumed lost, adding load to an already congested
  network.

  Including 4×variance widens the timeout when the
  network is JITTERY (Module 1, Chapter 5), so
  variability no longer causes spurious
  retransmission.

  This change, in 1988, is one of the fixes that
  ended the internet's congestion collapse.
  Chapter 4 covers the rest of it.
─────────────────────────────────────────

6. Closing a Connection

The Four-Way Close
─────────────────────────────────────────
  CLIENT                          SERVER
    │──── FIN ─────────────────────►│
    │◄─── ACK ──────────────────────│  client half
    │                               │  is closed
    │        (server may still send)│
    │◄─── FIN ──────────────────────│
    │──── ACK ─────────────────────►│
    │  TIME_WAIT (2×MSL)            │  CLOSED
    │  then CLOSED                  │
─────────────────────────────────────────
Why Four, Not Three
─────────────────────────────────────────
  TCP connections are FULL DUPLEX — two independent
  byte streams.

  Closing one direction does not close the other.
  After the client's FIN, the server may still have
  data to send, and TCP allows it.

  This HALF-CLOSE is genuinely used: a client can
  signal "I have sent my whole request" while
  remaining able to receive the response.
─────────────────────────────────────────
TIME_WAIT, Explained Properly
─────────────────────────────────────────
  The side that closes FIRST waits 2×MSL (maximum
  segment lifetime), typically 60 seconds, before
  releasing the 4-tuple.

  TWO REASONS:

  1. If the final ACK is lost, the peer will resend
     its FIN. Something must still exist to
     re-acknowledge it — otherwise the peer gets an
     RST and reports an error on a connection that
     closed cleanly.

  2. Delayed segments from this connection must
     expire before the same 4-tuple could be reused,
     or they would be accepted into a NEW connection
     as valid data.

  Chapter 1 covered the operational consequences.
  Both reasons are correctness, not conservatism.
─────────────────────────────────────────

7. Summary & Next Steps

Key Takeaways

  • The three-way handshake is the minimum for both sides to confirm the other's initial sequence number, and it costs one full round trip before any data flows.
  • Sequence numbers count bytes and the ACK number names the next byte expected; cumulative ACKs are robust but cannot express gaps, which is what SACK adds.
  • Fast retransmit on three duplicate ACKs recovers in about one round trip, while the timeout path is deliberately conservative.
  • Including RTT variance in the timeout calculation stopped spurious retransmissions on jittery paths, and was one of the fixes for congestion collapse.

Concept Check

  1. Why is the handshake three messages rather than two or four?
  2. What can a cumulative ACK not express, and how does SACK address it?
  3. Give both reasons TIME_WAIT exists, and explain why it is a correctness feature.

Next Chapter

Chapter 4: Flow and Congestion Control


Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index | Back to Computer Networks Index