← Module 04 · Layer 4: Transport

TCP Header Structure

TCP (Transmission Control Protocol) turns an unreliable, best-effort IP (Internet Protocol) network into something that behaves like an ordered, reliable conversation. Every field in this header exists to support that job.

What this header is actually for

IP's job stops at "get this packet from one host toward another, best effort, no promises." It doesn't guarantee delivery, doesn't guarantee order, and a packet can even arrive duplicated. TCP sits directly on top of IP and makes up the difference. Every field below exists to support one of three jobs:

  1. Identifying which application conversation a segment belongs to.
  2. Tracking exactly which bytes have arrived.
  3. Controlling how much data is allowed to be in flight at once.

Picture a warehouse shipping cartons through a courier that makes no promises on its own: no guaranteed delivery, no guaranteed order, cartons can even get duplicated in transit. TCP's header is the packing slip stapled to every carton: which shipment this carton belongs to, which carton number it is within the batch, a receipt confirming which cartons already arrived, how much room the receiving dock currently has for more, a stamp for special handling, and a tamper seal. That's the whole header, mapped field by field below.

The header, field by field

Source Port Destination Port Sequence Number Acknowledgment Number Data Offset Reserved Flags Window Size Checksum Urgent Pointer Options (0–40 bytes, rarely used) Data: the application payload TCP is carrying
The TCP header. Everything above the Options row is a fixed 20 bytes; Options and Data are both variable length.
FieldSizeWhat it holds
Source Port16 bitsThe sending application's port, identifying which local conversation this segment belongs to
Destination Port16 bitsThe receiving application's port, e.g. 443 for HTTPS
Sequence Number32 bitsThe byte offset of the first byte of data in this segment, relative to the connection's initial sequence number
Acknowledgment Number32 bitsThe next byte the sender of this segment expects to receive, valid whenever ACK is set
Data Offset4 bitsHeader length in 32-bit words, telling the receiver where the header ends and Data begins
Reserved6 bitsSet to 0, unused
Flags6 bitsURG, ACK, PSH, RST, SYN, FIN, one bit each; see below
Window Size16 bitsHow many more bytes the sender of this segment can currently receive, the core of flow control
Checksum16 bitsIntegrity check covering the TCP header and data
Urgent Pointer16 bitsOnly meaningful when URG is set; rarely used in practice
Options0–40 bytesOptional extensions, most commonly MSS (Maximum Segment Size) and window scaling
DataVariableThe actual application payload: an HTTP request, part of a file transfer, anything

The six control flags

FlagMeansTypically set when
SYNSynchronize sequence numbers, start a new connectionOnly on the first two segments of the three-way handshake
ACKThe acknowledgment number field is validOn effectively every segment after the handshake's first one
FINThis sender has no more data to sendInitiating a normal, graceful close
RSTAbort the connection immediately, no graceful closeThe destination port is closed, or a hard error occurred
PSHPush buffered data up to the application right awayInteractive traffic, like a single SSH (Secure Shell) keypress that shouldn't wait to be batched
URGThe Urgent Pointer field is significantRare in modern traffic

The three-way handshake and connection termination, both built entirely from these flags, get their own full walkthrough in the next topic.

Sequence and acknowledgment numbers: TCP counts bytes, not packets

A sequence number doesn't count segments. It counts bytes. Each connection starts with a random initial sequence number (ISN), and every byte of data sent after that gets its own implicit position in the stream. A segment's Sequence Number field is just the position of its first byte; its Acknowledgment Number, sent by the other side, is the position of the next byte it's still waiting for.

CLIENT SERVER seq=1000, 200 bytes of data ack=1200, "send me byte 1200 next"
The server's ack number (1200) is exactly the client's sequence number (1000) plus the number of bytes sent (200): confirmation that every one of those bytes actually arrived.
WHY WIRESHARK SHOWS SMALL, ROUND SEQUENCE NUMBERS

Real initial sequence numbers are large and effectively random, chosen that way so an attacker can't easily guess them and inject forged segments into someone else's connection. Packet capture tools like Wireshark show "relative" sequence numbers instead, starting each connection at 0, purely to make captures easier to read. The actual numbers on the wire are the real, random ones.