---
title: 'Encapsulation and Headers'
source: 'https://academia.sh/en/courses/network-models/encapsulation-and-headers'
course: 'Network Models and Protocols'
language: en
updated: '2026-08-17T18:07:05+00:00'
license: 'CC BY-SA 4.0'
---

# 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.

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 $5 \times 4 = 20$ bytes. **Fragment
offset** counts 8-byte units, not bytes; with 13 bits, positions up to
$8 \times 8191 = 65\,528$ 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`.

```python
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 $1460 + 58 + 20 = 1538$ 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.
