Lesson 03 / 18
Encapsulation and Headers
How data is wrapped as it moves through the layers, the fields of the IPv4 header, the header checksum calculation, and the effect of header overhead on throughput.
Contents
The previous two lessons separated the layers’ responsibilities. The concrete form of the exchange between layers has not yet been opened up: what exactly does a layer do with the data it receives from the layer above it?
The answer can be given in one word: it wraps it. Each layer adds its own control information to the data coming from above and hands it to the layer below. This lesson’s question is what this wrapping looks like field by field, and what it costs.
Encapsulation
Encapsulation is a layer wrapping the data it receives from above with its own header and handing it to the layer below. Data coming from the upper layer is a meaningless byte sequence to the lower layer — it is called the payload. The lower layer does not interpret the payload’s contents; it only carries it.
Suppose the client at 192.168.10.196 sends a 100-byte application request.
Wrapping proceeds downward as follows:
| Layer | Header added | Resulting size | Unit name |
|---|---|---|---|
| Application | — | 100 bytes | Data |
| Transport (TCP) | 20 bytes | 120 bytes | Segment |
| Internet (IPv4) | 20 bytes | 140 bytes | Datagram |
| Link (Ethernet) | 14-byte header + 4-byte trailer | 158 bytes | Frame |
At the receiver, the process runs in reverse. Decapsulation reads and strips off its own header at each layer and hands the remaining payload to the layer above. This symmetry is the layered model’s counterpart in implementation: a stack on the sending side, the other face of the mirror on the receiving side.
The 4-byte piece the Link layer adds is called not a header but a trailer: the frame check sequence must sit at the end of the data it was computed over.
How a Layer Knows Where to Deliver
While decapsulating, each layer must decide which upper-layer protocol to hand the payload to. Every header carries a multiplexing key for this decision:
| Layer | Field | Example value | Meaning |
|---|---|---|---|
| Ethernet | Type (EtherType) | 0x0800 |
The payload is an IPv4 datagram |
| Ethernet | Type | 0x86DD |
The payload is an IPv6 datagram |
| Ethernet | Type | 0x0806 |
The payload is an address resolution message |
| IPv4 | Protocol | 6 |
The payload is a TCP segment |
| IPv4 | Protocol | 17 |
The payload is a UDP datagram |
| IPv4 | Protocol | 1 |
The payload is an ICMP message |
| TCP / UDP | Destination port | 443 |
The local application to receive the payload |
Without this chain, decapsulation would not be possible: the receiver knows what a byte sequence in hand is only as far as the previous header told it.
The IPv4 Header
The IPv4 header, without options, is 20 bytes and consists of five 32-bit words:
| Bit range | Field | Width | Function |
|---|---|---|---|
| 0–3 | Version | 4 bits | 4 for IPv4 |
| 4–7 | Header length (IHL) | 4 bits | Number of 32-bit words, minimum 5 |
| 8–15 | Type of service | 8 bits | Priority and congestion notification |
| 16–31 | Total length | 16 bits | Datagram size including the header |
| 32–47 | Identification | 16 bits | Which datagram the fragments belong to |
| 48–50 | Flags | 3 bits | Don’t fragment / more fragments |
| 51–63 | Fragment offset | 13 bits | The payload’s starting position (in 8-byte units) |
| 64–71 | Time to live (TTL) | 8 bits | Remaining hop count |
| 72–79 | Protocol | 8 bits | The payload’s type |
| 80–95 | Header checksum | 16 bits | Error detection over the header only |
| 96–127 | Source address | 32 bits | Sender |
| 128–159 | Destination address | 32 bits | Receiver |
Three fields deserve particular attention. Header length is a word count: if its value is 5, the header is bytes. Fragment offset counts 8-byte units, not bytes; with 13 bits, positions up to bytes can be represented. Header checksum protects only the header, not the payload — the payload’s integrity is the upper layer’s job.
Building the Header at the Bit Level
The fields’ bit alignment is built with the shifting and masking operations introduced in the Bit-Level Operations lesson of the How Computers Work course. Version and header length are packed into the same byte: version is shifted left four bits and combined with the length.
The program below builds and reads back the IPv4 header of an empty TCP
segment going from the client on the example network to 198.51.100.20.
import ipaddress import struct def header_checksum(data: bytes) -> int: """The one's complement of the one's complement sum of 16-bit words.""" total = 0 for i in range(0, len(data), 2): total += (data[i] << 8) + data[i + 1] while total >> 16: # add the overflow bit back in total = (total & 0xFFFF) + (total >> 16) return ~total & 0xFFFF def ipv4_header(source: str, dest: str, total_length: int, identifier: int, ttl: int, protocol: int) -> bytes: version_ihl = (4 << 4) | 5 # version 4, header 5 words = 20 bytes flags_offset = (0b010 << 13) | 0 # don't fragment (DF), offset 0 raw = struct.pack( "!BBHHHBBH4s4s", version_ihl, 0, total_length, identifier, flags_offset, ttl, protocol, 0, ipaddress.ip_address(source).packed, ipaddress.ip_address(dest).packed, ) c = header_checksum(raw) return raw[:10] + struct.pack("!H", c) + raw[12:] header = ipv4_header("192.168.10.196", "198.51.100.20", total_length=40, identifier=7238, ttl=64, protocol=6) print(header.hex()) print("length:", len(header), "bytes") print("checksum check:", header_checksum(header)) fields = struct.unpack("!BBHHHBBH4s4s", header) print("version :", fields[0] >> 4) print("header size :", (fields[0] & 0x0F) * 4, "bytes") print("total size :", fields[2], "bytes") print("identifier :", fields[3]) print("flags :", format(fields[4] >> 13, "03b")) print("frag offset :", fields[4] & 0x1FFF) print("TTL :", fields[5]) print("protocol :", fields[6]) print("checksum :", hex(fields[7])) print("source :", ipaddress.ip_address(fields[8])) print("dest :", ipaddress.ip_address(fields[9]))
Output:
450000281c464000400628d6c0a80ac4c6336414 length: 20 bytes checksum check: 0 version : 4 header size : 20 bytes total size : 40 bytes identifier : 7238 flags : 010 frag offset : 0 TTL : 64 protocol : 6 checksum : 0x28d6 source : 192.168.10.196 dest : 198.51.100.20
The ! prefix in the struct format string selects network byte order,
meaning big-endian. This ordering, introduced in the Byte Order lesson of the
How Computers Work course, is mandatory in protocol headers: the sending and
receiving machines’ internal byte orders can differ, but the order on the
wire is single.
The first byte of the hex output is 45: the high nibble 4 gives the
version, the low nibble 5 gives the header length. The next two bytes are
0028, that is 40 — the total length. The first three bits of the value
4000 are 010, the “don’t fragment” flag.
The Header Checksum
The checksum is the one’s complement of the one’s complement sum of 16-bit
words. During the calculation, the checksum field itself is taken as zero.
The while total >> 16 loop adds the part that overflows 16 bits back into
the sum; this step is a requirement of one’s complement arithmetic.
The elegance of verification is this: when the receiver performs the same
calculation on the header with the checksum field filled in, it must get
zero. The checksum check: 0 line in the output above shows this. As a
result, the receiver does not need to make a separate comparison.
The method’s power is limited: if word order changes, the sum does not change, so this error cannot be detected. It provides no protection against deliberate tampering. Still, it is cheap, and because TTL changes at every hop, the checksum must be recomputed at every router — keeping the cost low is a design requirement.
The Header Overhead Ratio
Every header takes space away from the data that could be carried. Overhead gives the size of this share. Ethernet’s fixed 18-byte cost (14 header + 4 trailer), IPv4’s 20 bytes, and TCP’s 20 bytes add up to 58 bytes.
| Application data | Frame size | Throughput ratio |
|---|---|---|
| 100 bytes | 158 bytes | 63.29% |
| 500 bytes | 558 bytes | 89.61% |
| 1460 bytes | 1518 bytes | 96.18% |
The ratio improves rapidly as the data size grows. This is the numerical justification for a fundamental rule in network programming: batching small writes and sending them at once is markedly more efficient than sending the same data piece by piece.
The real cost on the wire is a bit higher. Ethernet requires an 8-byte preamble before every frame and a 12-byte gap between frames. For 1460 bytes of data, the total on the wire is bytes; the throughput ratio drops to 94.93%.
Summary
- Encapsulation is each layer wrapping the data it receives from above with its own header and handing it to the layer below; at the receiver, the process runs in reverse, symmetrically.
- Every header carries a multiplexing key: the type field in Ethernet, the protocol field in IP, the port number at the Transport layer.
- The IPv4 header without options is 20 bytes; header length counts 32-bit words, and fragment offset counts 8-byte units.
- The header checksum is the one’s complement of the one’s complement sum of 16-bit words; recomputed over the filled-in header, it must yield zero.
- The checksum protects only the header, not the payload, and cannot detect word-order changes.
- Header overhead is 58 bytes; throughput is 63.29% for 100 bytes of data and 96.18% for 1460 bytes.
Next Step
This lesson used the names “segment,” “datagram,” and “frame,” each denoting a specific layer’s data unit. These terms are used interchangeably in everyday speech, yet the difference between them determines size limits. The next lesson will pin down the per-layer data unit terms, define what the maximum transmission unit is, and calculate piece by piece how a 4000-byte payload passes through a 1500-byte medium.
To keep your progress and take notes, Log in
My notes
Log in to take notes.