Skip to main content

The TCP Segment Header

The previous lessons covered what TCP does - multiplex connections, reassemble a byte stream, handshake, and control flow. This lesson is about where that machinery lives: the fields packed into every TCP segment. The handshake and the window mechanics are covered in The TCP Handshake and Flow Control; here they are just named as they show up in the header.

The fixed header

Every TCP segment starts with a 20-byte fixed header (before options):

FieldBytesPurpose
Source port2which local socket sent this segment
Destination port2which socket on the receiver to demux to
Sequence number4byte offset of the first data byte in this segment
Acknowledgment number4next byte the sender of this segment expects to receive
Header length4 bitslength of the TCP header in 32-bit words (accounts for options)
Flags9 bitscontrol bits - see below
Window2receive window (rwnd) - see Flow Control
Checksum2error-detection over header, payload, and a pseudo-header of IP fields
Urgent pointer2offset to urgent data, only meaningful when the URG flag is set
Options0-40variable-length extensions - MSS, window scaling, SACK, timestamps

Two fields do double duty as the reliability mechanism: the sequence number counts bytes, not segments, so a segment carrying bytes 1000-1499 has sequence number 1000. The acknowledgment number is cumulative - it names the next byte expected, implicitly acknowledging everything before it.

The flags

Six single-bit flags (plus three newer congestion-notification bits not covered here) drive the connection state machine:

FlagMeaning
SYNsynchronize sequence numbers - sent to open a connection (see The TCP Handshake)
ACKthe acknowledgment field is valid - set on every segment after the handshake
FINsender is done sending - begins the four-way teardown
RSTabort the connection immediately - no handshake, no graceful close, just reset
PSHpush buffered data to the application now rather than waiting to fill a buffer
URGurgent pointer field is valid; rarely used in practice
info

FIN says "I have no more data, but let's close politely" and still lets in-flight data finish arriving. RST says "something is wrong, discard all state now" - a half-open connection, a port with no listener, or an application choosing to abort. A RST segment carries no payload and expects no ACK.

Options: extending the header

The fixed header predates modern link speeds and modern loss-recovery needs. Options fill the gap, negotiated during the handshake unless noted otherwise:

  • MSS (Maximum Segment Size) - each side advertises the largest payload it will accept in a single segment, typically derived from the local interface's MTU minus header overhead (commonly 1460 bytes on a 1500-byte Ethernet MTU). Sent only in the SYN and SYN-ACK.
  • Window scaling - the 16-bit window field caps rwnd at 65,535 bytes, far too small for high-bandwidth, high-latency paths. This option carries a shift count, negotiated once at handshake time, that both sides multiply the window field by for the life of the connection - e.g. a shift of 7 turns a 65,535 field into an effective window near 8 MB.
  • SACK (Selective Acknowledgment) - plain cumulative ACKs can only say "everything up to byte N arrived." If bytes N and N+3000 arrived but N+1000-N+2999 did not, a cumulative ACK cannot say so, forcing the sender to guess what to resend. SACK lets the receiver list the specific out-of-order byte ranges it already has, so the sender retransmits only the actual gaps.
  • Timestamps - each segment carries a sending timestamp, echoed back in the ACK. This gives an RTT sample on essentially every segment (see Retransmission and Timers) and also protects against wrapped sequence numbers on very fast connections.
MSS is not MTU

MSS is a payload size, MTU is a whole-frame size. Forgetting to subtract the IP and TCP headers (20 bytes each, minimum) from the link MTU when computing MSS is a classic source of "works on my network" fragmentation bugs, especially across tunnels that add their own headers and shrink the effective MTU further.

Reading a header in practice

A packet capture (tcpdump -nvv, Wireshark) prints these fields directly: flags as letters (S for SYN, . for a plain ACK, F for FIN, R for RST), sequence and ack numbers relative to the start of the stream, and the window value. Recognizing Flags [S] versus Flags [S.] versus Flags [F.] is enough to read the shape of a connection's lifecycle straight off the wire without decoding raw bytes.