Skip to content
academia.sh

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

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=15381460 + 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.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close