Loading learning content...
The designers of IPv4 faced a fundamental challenge: how do you create a protocol flexible enough to accommodate features that haven't been invented yet, while keeping the base header efficient for the common case? Their solution was elegant—optional header extensions.
The IPv4 options field is a variable-length extension that follows the fixed 20-byte header. It enables advanced functionality like route recording, source routing, and timestamping without burdening every packet with rarely-used fields. Understanding options is essential for deep protocol analysis, network troubleshooting, and security assessment.
By the end of this page, you will understand the complete structure of IPv4 options, including the type field encoding, length semantics, copying behavior, and how options are padded to maintain 32-bit alignment. You'll be able to decode any IPv4 option from raw packet data.
Before diving into the format details, let's understand where the options field sits within the IPv4 header and why it exists.
IPv4 Header Structure Recap:
The IPv4 header consists of two parts:
The Internet Header Length (IHL) field indicates the total header size in 32-bit words. With a minimum value of 5 (20 bytes) and maximum of 15 (60 bytes), the options field can consume up to 40 bytes.
| IHL Value | Header Size (bytes) | Options Size (bytes) | Description |
|---|---|---|---|
| 5 | 20 | 0 | Minimum header, no options |
| 6 | 24 | 4 | One 32-bit word of options |
| 7 | 28 | 8 | Two 32-bit words of options |
| 10 | 40 | 20 | Five 32-bit words of options |
| 15 | 60 | 40 | Maximum header, full options |
The maximum options size of 40 bytes is a hard constraint of IPv4. This limit proved challenging for features like Record Route, which can only record 9 router addresses. This limitation influenced IPv6's design, which uses extension headers without such constraints.
IPv4 options come in two distinct formats, distinguished by their structure:
1. Single-Byte Options (Type Only)
These options consist of just the 1-byte type field. They carry no additional data and are used for control purposes.
2. Multi-Byte Options (Type + Length + Data)
These options use a Type-Length-Value (TLV) structure:
| Aspect | Single-Byte | Multi-Byte |
|---|---|---|
| Structure | Type only | Type + Length + Data |
| Size | 1 byte fixed | Variable (minimum 2 bytes) |
| Examples | EOL (0), NOP (1) | Record Route, Source Routing, Timestamp |
| Purpose | Padding and list termination | Functional capabilities |
| Parsing | Read 1 byte, done | Read type, read length, read (length-2) data bytes |
The 1-byte type field is not merely an identifier—it's a carefully encoded structure with three distinct subfields:
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| C |Class| Number |
+---+---+---+---+---+---+---+---+
|1b | 2b | 5 bits |
Bit 0 - Copy Flag (C):
0: Option is NOT copied to all fragments during fragmentation1: Option IS copied to every fragmentBits 1-2 - Option Class:
00 (0): Control options10 (2): Debugging and measurement options01 (1) and 11 (3): Reserved for future useBits 3-7 - Option Number:
The copy flag determines option behavior during fragmentation. If an option must be present in every fragment for proper interpretation (like Source Route), the copy flag is set. Options needed only by the first fragment (like Record Route progress) don't require copying.
| Option Name | Type Value (Binary) | Copy | Class | Number | Decimal |
|---|---|---|---|---|---|
| End of Options List | 00000000 | 0 | 00 | 00000 | 0 |
| No Operation | 00000001 | 0 | 00 | 00001 | 1 |
| Record Route | 00000111 | 0 | 00 | 00111 | 7 |
| Timestamp | 01000100 | 0 | 10 | 00100 | 68 |
| Loose Source Routing | 10000011 | 1 | 00 | 00011 | 131 |
| Strict Source Routing | 10001001 | 1 | 00 | 01001 | 137 |
Decoding Example:
Let's decode option type value 137 (0x89 in hexadecimal):
100010011 → Option is copied to all fragments00 → Control option01001 = 9 → Strict Source RoutingThis encoding tells us that Strict Source Routing must be present in every fragment because routers along the path need routing instructions for each fragment they receive.
The length field in multi-byte options deserves careful attention because its interpretation differs from some other protocols.
Critical Point: Length Includes Type and Length Bytes
The length value indicates the total size of the option, including:
Therefore:
2 (type + length, no data)A frequent bug in packet parsing code is treating the length field as the data-only length. If you read 'length' bytes of data after the length field, you'll read 2 bytes too many, corrupting your parse and potentially causing security vulnerabilities.
12345678910111213141516171819202122232425
// INCORRECT: Common mistake in option parsingvoid parse_option_wrong(uint8_t *option) { uint8_t type = option[0]; uint8_t length = option[1]; // Bug: Reading 'length' bytes of data reads 2 extra bytes! uint8_t data[length]; memcpy(data, &option[2], length); // WRONG!} // CORRECT: Proper option parsingvoid parse_option_correct(uint8_t *option) { uint8_t type = option[0]; uint8_t length = option[1]; // Validate minimum length if (length < 2) { // Invalid option - handle error return; } // Data length is total length minus type and length bytes uint8_t data_length = length - 2; uint8_t data[data_length]; memcpy(data, &option[2], data_length); // CORRECT!}The End of Options List (EOL) option, type value 0, is a single-byte option that marks the absolute end of the options area. After encountering EOL, no further option processing occurs—remaining bytes until the IHL-indicated header end are padding.
Key Characteristics:
Usage Rules:
EOL (End of Options List) terminates parsing entirely. NOP (No Operation) is just a 1-byte skip that allows parsing to continue. Using NOP where EOL is intended can cause security issues, as parsers may continue processing garbage data as options.
The No Operation (NOP) option, type value 1, is a single-byte padding option used to align subsequent options on specific boundaries.
Key Characteristics:
Primary Uses:
Example: Using NOP for 32-bit alignment Options area structure:Offset 0: Type = 1 (NOP) - 1 byte paddingOffset 1: Type = 1 (NOP) - 1 byte padding Offset 2: Type = 1 (NOP) - 1 byte paddingOffset 3: Type = 68 (Timestamp) Offset 4: Length = 12Offset 5-14: Timestamp data Without NOPs, Timestamp would start at offset 0.With NOPs, Timestamp is aligned to 32-bit boundary (offset 3). Benefits:- Faster memory access on aligned architectures- Cleaner debugging display- Predictable option positionsWhile alignment using NOP is optional, some systems perform better with aligned options. The Timestamp option, in particular, often benefits from 32-bit alignment since it contains 32-bit timestamp values. However, in memory-constrained scenarios, omitting alignment NOPs conserves the precious 40-byte options budget.
Options processing significantly impacts router performance and behavior. Unlike the fixed header fields, which can be processed in hardware at line rate, options require software handling and careful security consideration.
Processing Flow:
Packets with options bypass hardware fast-path processing on most modern routers. A router that handles millions of packets per second in fast-path may only handle thousands in slow-path. This performance differential is why options are rarely used in production traffic and why many networks filter or drop packets containing options.
Security Considerations:
IPv4 options have significant security implications:
RFC 7126 (Internet Architecture Board) recommends filtering source-routed packets at network borders.
The IPv4 header must always be a multiple of 32 bits (4 bytes). Since options have variable lengths, padding is required when options don't naturally end on a 32-bit boundary.
Padding Rules:
Calculation:
Total options size = Sum of all option lengths
Padding needed = (4 - (options size mod 4)) mod 4
Final header size = 20 + options size + padding
IHL = Final header size / 4
| Options Total | Mod 4 | Padding Needed | Final Options Area | IHL Value |
|---|---|---|---|---|
| 3 bytes | 3 | 1 byte | 4 bytes | 6 |
| 7 bytes | 3 | 1 byte | 8 bytes | 7 |
| 8 bytes | 0 | 0 bytes | 8 bytes | 7 |
| 15 bytes | 3 | 1 byte | 16 bytes | 9 |
| 39 bytes | 3 | 1 byte | 40 bytes | 15 |
1234567891011121314151617181920212223242526272829303132333435363738
def calculate_ipv4_header_size(options_bytes: int) -> dict: """ Calculate IPv4 header size with padding for options. Args: options_bytes: Total size of all options (without padding) Returns: Dictionary with padding details """ if options_bytes > 40: raise ValueError("Options cannot exceed 40 bytes") # Calculate padding needed for 32-bit alignment remainder = options_bytes % 4 padding = (4 - remainder) % 4 # This handles the 0 case correctly # Calculate final sizes options_area = options_bytes + padding header_size = 20 + options_area ihl = header_size // 4 return { "options_bytes": options_bytes, "padding_bytes": padding, "options_area_total": options_area, "header_size": header_size, "ihl_value": ihl } # Examplesprint(calculate_ipv4_header_size(7))# Output: {'options_bytes': 7, 'padding_bytes': 1, # 'options_area_total': 8, 'header_size': 28, 'ihl_value': 7} print(calculate_ipv4_header_size(39))# Output: {'options_bytes': 39, 'padding_bytes': 1,# 'options_area_total': 40, 'header_size': 60, 'ihl_value': 15}Let's consolidate everything into a complete algorithm for parsing IPv4 options. This algorithm handles all option types, validates lengths, and properly terminates on EOL or area exhaustion.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
#include <stdint.h>#include <stdbool.h> #define OPT_EOL 0 // End of Options List#define OPT_NOP 1 // No Operation#define OPT_RR 7 // Record Route#define OPT_TS 68 // Timestamp (0x44)#define OPT_LSR 131 // Loose Source Routing (0x83)#define OPT_SSR 137 // Strict Source Routing (0x89) typedef struct { uint8_t type; uint8_t length; // Total length (includes type and length bytes) uint8_t *data; // Pointer to option data uint8_t data_length; // Actual data length (length - 2)} IPv4Option; /** * Parse IPv4 options from header * * @param options_start Pointer to start of options area * @param options_length Length of options area (from IHL: (IHL - 5) * 4) * @param parsed_options Array to hold parsed options * @param max_options Maximum options to parse * @return Number of options parsed, or -1 on error */int parse_ipv4_options( uint8_t *options_start, int options_length, IPv4Option *parsed_options, int max_options) { int offset = 0; int count = 0; while (offset < options_length && count < max_options) { uint8_t type = options_start[offset]; // Case 1: End of Options List if (type == OPT_EOL) { // Parsing complete - remaining bytes are padding break; } // Case 2: No Operation (single-byte) if (type == OPT_NOP) { parsed_options[count].type = OPT_NOP; parsed_options[count].length = 1; parsed_options[count].data = NULL; parsed_options[count].data_length = 0; offset += 1; count++; continue; } // Case 3: Multi-byte option (TLV format) // Ensure we have room for length byte if (offset + 1 >= options_length) { return -1; // Truncated option } uint8_t length = options_start[offset + 1]; // Validate length if (length < 2) { return -1; // Invalid: length must include type and length bytes } if (offset + length > options_length) { return -1; // Option extends beyond options area } // Parse the option parsed_options[count].type = type; parsed_options[count].length = length; parsed_options[count].data_length = length - 2; parsed_options[count].data = (length > 2) ? &options_start[offset + 2] : NULL; offset += length; count++; } return count;} // Example usagevoid process_packet(uint8_t *ip_header) { uint8_t ihl = ip_header[0] & 0x0F; // Extract IHL from first byte if (ihl < 5) { // Invalid header return; } if (ihl == 5) { // No options present return; } int options_length = (ihl - 5) * 4; uint8_t *options_start = ip_header + 20; // Options start after fixed header IPv4Option options[10]; // Support up to 10 options int num_options = parse_ipv4_options( options_start, options_length, options, 10 ); if (num_options < 0) { // Parse error - malformed options return; } // Process each option for (int i = 0; i < num_options; i++) { switch (options[i].type) { case OPT_RR: // Handle Record Route break; case OPT_TS: // Handle Timestamp break; case OPT_LSR: case OPT_SSR: // Handle Source Routing break; } }}We've covered the complete structure and semantics of IPv4 options. Let's consolidate the key points:
What's Next:
Now that you understand the options format, we'll explore the first major option type: Record Route. This option allows capturing the path a packet takes through the network, providing valuable diagnostic information for troubleshooting and network mapping.
You now have a complete understanding of IPv4 options format. You can decode any option from raw packet data, understand the type field encoding, and properly parse TLV-structured options. Next, we'll apply this knowledge to the Record Route option.