Encapsulation: Data, Segment, Packet, Frame
Each layer wraps the one above in a header the layer below never reads: 100 bytes of HTTP become a 120-byte TCP segment, a 140-byte IP packet and a 154-byte Ethernet frame — and the 1500-byte MTU, the MSS derived from it, and path-MTU discovery decide how a large response is cut up so that nothing on the way has to fragment it.
The problem
Nesting on the way down
Start with 100 bytes of application data — a small HTTP request. TCP prepends a header: 20 bytes without options, typically 32 with the timestamp option every modern stack uses. The result is a segment. IP prepends its 20-byte IPv4 header (40 for IPv6); the result is a packet (RFC language: a datagram). Ethernet prepends a 14-byte header — destination MAC, source MAC, EtherType — and appends a 4-byte frame check sequence; the result is a frame. On the wire the NIC also sends an 8-byte preamble and leaves a 12-byte inter-packet gap, which never appear in a capture but do consume line time.
The names are not decoration; they tell you which layer you are talking about. A "lost packet" is an IP-layer statement; a "retransmitted segment" is TCP; a "dropped frame" is the link. tcpdump shows you frames and decodes every nested layer inside.
The arithmetic for 100 bytes of payload: 100 + 20 (TCP) + 20 (IPv4) + 14 (Ethernet) = 154 bytes in the frame, 158 with the FCS, 178 of line time. Overhead is 54 of 154, about 35%. For a full-size segment carrying 1460 bytes the same 54 bytes are 3.6%, which is why bulk transfer is efficient and tiny messages are not — and why TCP tries to coalesce small writes (Nagle’s algorithm) and why HTTP/2 packs many small frames into one segment.
- Application data100 B — `GET / HTTP/1.1 …`↓
- TCP segment+20 B header (src/dst port, seq, ack, flags, window) → 120 B↓
- IP packet+20 B IPv4 header (src/dst IP, TTL, protocol=6) → 140 B↓
- Ethernet frame+14 B header (dst MAC, src MAC, EtherType) +4 B FCS → 158 B
offset size layer field 0 6 Ethernet destination MAC 6 6 Ethernet source MAC 12 2 Ethernet EtherType 0x0800 = IPv4 14 20 IPv4 version/IHL, DSCP, total length, id, flags/frag, TTL, protocol, checksum, src IP, dst IP 34 20 TCP src port, dst port, seq, ack, data offset/flags, window, checksum, urgent 54 100 HTTP "GET / HTTP/1.1\r\nHost: engineer-atlas.dev\r\n..." 154 4 Ethernet frame check sequence (CRC-32)
Unwrapping on the way up
The receiving NIC checks the FCS and drops the frame if it is corrupt — silently; nobody is told. The driver hands the frame up. The link layer checks the destination MAC is its own (or broadcast/multicast) and reads the EtherType to know which handler to call: 0x0800 → IPv4, 0x86DD → IPv6, 0x0806 → ARP. The IP layer validates the header checksum, checks the destination IP is local (otherwise, if forwarding is enabled, it routes the packet onward instead), and reads the protocol field to pick the next handler: 6 → TCP, 17 → UDP, 1 → ICMP. TCP validates its checksum (computed over the segment *and* a pseudo-header with the IP addresses, so a packet delivered to the wrong host fails it), finds the socket by the 4-tuple, and places the 100 bytes in that socket’s receive buffer.
Each layer reads only its own header and one field that says who is next. That is the entire mechanism of demultiplexing: EtherType → protocol → port. When the application calls read(), it receives exactly the 100 bytes it would have received over any other link, on any other network, over any other IP version. It has no way to learn how many headers came and went.
- Each layer trusts only its own checksum: FCS covers the frame, the IPv4 checksum covers only the IP header, the TCP checksum covers the segment plus a pseudo-header. IPv6 drops the header checksum entirely and relies on the transport.
- A frame for another MAC is dropped by the NIC before software sees it — unless the interface is in promiscuous mode, which is what
tcpdumpenables.
MTU, MSS and the size of a piece
The MTU (maximum transmission unit) is the largest payload a link will carry in one frame: 1500 bytes on standard Ethernet and most Wi-Fi, up to 9000 with jumbo frames inside a data centre, less on tunnels and PPPoE (1492), VPNs (often 1400–1420) and some mobile networks. The IP packet, headers included, must fit in the MTU. So the MSS — the largest TCP payload per segment — is the MTU minus the IP and TCP headers: 1500 − 20 − 20 = 1460 for IPv4, 1500 − 40 − 20 = 1440 for IPv6, and less again with TCP options. Each side announces its MSS in the SYN, and both use the smaller.
A 200 kB HTTP response therefore leaves the server as ~140 segments of 1460 bytes, each in its own IP packet and frame. The receiver reassembles the byte stream from sequence numbers; the application reads it as one continuous body. Segmentation is invisible to HTTP and is the reason TCP is a *byte stream* rather than a message service.
The MSS is only correct if the *whole path* has an MTU of at least 1500. It often does not: a VPN, a tunnel, or a PPPoE link in the middle may be 1400. Something has to give.
Why nobody wants to fragment
IPv4 has a built-in answer: a router facing a smaller-MTU link may fragment the packet into pieces, each with the same identification field and an offset, and the *destination* host reassembles them. It works and it is a bad idea. Every fragment must arrive for the packet to count — lose one of three and TCP retransmits the whole segment, which is fragmented again. Fragments after the first carry no TCP header, so firewalls and NATs cannot classify them and many simply drop them. Reassembly costs the receiver memory and time, and the identification field is only 16 bits, which at high speed wraps quickly enough to mis-reassemble. IPv6 removed the option: IPv6 routers never fragment; only the sending host may, and TCP over IPv6 avoids that too.
The modern approach on both versions is Path MTU Discovery (PMTUD). The sender sets the DF (don’t fragment) bit on IPv4, or simply sends on IPv6. A router that cannot forward the packet drops it and returns an ICMP message — *Fragmentation Needed* (IPv4, type 3 code 4) or *Packet Too Big* (ICMPv6, type 2) — that includes the MTU of the link it could not fit. The sender lowers its path MTU for that destination, re-segments, and carries on. The dependency is the ICMP message getting back.
When a firewall on the path filters ICMP — a common "security" misconfiguration — PMTUD fails silently and you get an MTU black hole: small packets (handshake, small requests, ping) pass, full-size packets vanish, and the connection hangs precisely when the first large response is sent. Linux mitigates this with TCP MSS clamping on routers (rewriting the MSS in the SYN to fit the link) and with *Packetization Layer PMTUD* (RFC 4821), which probes with progressively smaller segments when it detects the pattern of loss. Both exist because the clean mechanism has a single point of failure.
- IPv4: routers *may* fragment; senders set
DFto prevent it. IPv6: routers *never* fragment; the sender must fit the path MTU. - PMTUD relies on ICMP *Fragmentation Needed* / *Packet Too Big* reaching the sender. Filtering all ICMP breaks it.
- Symptom of a black hole:
pingand TLS handshake work, the page never arrives;ping -M do -s 1472(Linux) fails while-s 1400succeeds.
Key points
- Data → segment (TCP) → packet (IP) → frame (Ethernet). Each layer adds a header the layer below treats as payload; the names tell you which layer a statement is about.
- Ethernet 14 B (+4 FCS), IPv4 20 B, TCP 20 B (+options). A 100-byte payload costs 35% overhead; a 1460-byte one costs 3.6%.
- Demultiplexing is one field per layer: EtherType → IP protocol → port.
- MTU 1500 → MSS 1460 (IPv4) / 1440 (IPv6). Each side announces its MSS in the
SYN. - IPv4 routers may fragment; IPv6 routers never do. Both prefer the sender to discover the path MTU and fit it.
- PMTUD depends on ICMP getting back. A filtered ICMP produces the classic black hole: small packets work, large ones hang.
Why does this exist?
Mechanisms are answers to constraints. Open each question before reading the answer.
▸Why a header per layer instead of one combined header?
So that each layer can be replaced without the others changing, and so that each device reads only what it needs: a switch stops at 14 bytes, a router at 34, a NAT at 54. One combined header would couple every device to every protocol.
▸Why is the MTU 1500?
It is a 1980s Ethernet compromise between per-frame overhead and how long one station may hold the shared cable. Everything since (Wi-Fi, fibre) kept it for compatibility, so 1500 is the internet’s de facto path MTU and the MSS follows from it.
▸Why did IPv6 forbid router fragmentation?
Fragmentation multiplies the cost of loss, defeats stateless filtering, and forces routers to do per-packet work. Pushing the problem to the sender with PMTUD keeps the network simple at the cost of depending on ICMP.
Encapsulation
MTU = 1500 B (Ethernet payload limit) MSS = MTU − IP(20) − TCP(20) = 1460 B frames = ceil(100 / 1460) = 1 headers/frame = TCP 20 + IP 20 + Eth 14 + FCS 4 = 58 B on the wire = 100 + 1 × 58 = 158 B overhead = 58 / 158 = 36.7 %
How it fails
What the failure looks like from inside real software.
- MTU black hole through a VPN or tunnel: connections open fine and then hang on the first large transfer; fixed by MSS clamping or lowering the tunnel MTU.
- Firewall drops non-initial fragments: DNS responses over 1500 bytes (DNSSEC, large TXT records) time out while small ones work.
- Per-message overhead dominates: a protocol sending 40-byte messages spends 57% of the wire on headers; batching or a larger MSS is the fix, not more bandwidth.
- Jumbo frames on one host and not the other: a 9000-byte packet leaves, the switch or the peer drops it; large writes fail, small ones succeed, and nothing logs.