Loading learning content...
We've journeyed through the individual fields of the IPv4 header—Version, IHL, Type of Service, Total Length, and Identification. Now it's time to step back and see the complete picture. Understanding how all these fields work together, along with the remaining fields we haven't yet covered, provides the foundation for network analysis, packet crafting, and protocol implementation.
This comprehensive overview will consolidate your knowledge and introduce the remaining header fields: Flags, Fragment Offset, Time to Live (TTL), Protocol, Header Checksum, Source Address, Destination Address, and Options. By the end of this page, you'll have a complete mental model of the 20-60 byte structure that governs every IPv4 packet on the Internet.
By the end of this page, you will have a complete understanding of the entire IPv4 header structure: all 14 fields, their positions, interactions, and purposes. You'll be able to parse any IPv4 header, understand its complete meaning, and recognize common patterns in network traffic.
The IPv4 header, defined in RFC 791, has a minimum size of 20 bytes and a maximum size of 60 bytes. The variable length results from optional fields that may be included after the mandatory 20 bytes.
| Bytes | Bit Positions | Field | Size | Purpose |
|---|---|---|---|---|
| 0 | 0-3 | Version | 4 bits | IP version (always 4 for IPv4) |
| 0 | 4-7 | IHL | 4 bits | Header length in 32-bit words |
| 1 | 8-15 | Type of Service / DSCP+ECN | 8 bits | QoS and congestion signaling |
| 2-3 | 16-31 | Total Length | 16 bits | Entire packet size in bytes |
| 4-5 | 32-47 | Identification | 16 bits | Fragment grouping identifier |
| 6 | 48 | Reserved Flag | 1 bit | Must be zero |
| 6 | 49 | DF (Don't Fragment) | 1 bit | Fragmentation control |
| 6 | 50 | MF (More Fragments) | 1 bit | Fragment continuation |
| 6-7 | 51-63 | Fragment Offset | 13 bits | Fragment position (×8 bytes) |
| 8 | 64-71 | Time to Live (TTL) | 8 bits | Hop limit |
| 9 | 72-79 | Protocol | 8 bits | Upper layer protocol |
| 10-11 | 80-95 | Header Checksum | 16 bits | Header integrity check |
| 12-15 | 96-127 | Source Address | 32 bits | Sender's IPv4 address |
| 16-19 | 128-159 | Destination Address | 32 bits | Receiver's IPv4 address |
| 20-59 | 160-479 | Options + Padding | 0-40 bytes | Optional features |
Visual Representation:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL | DSCP |ECN| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The IPv4 header is designed for 32-bit alignment. Every row in the diagram represents 32 bits (4 bytes). This alignment enables efficient processing on 32-bit and 64-bit CPUs, allowing fields to be read in single memory operations without byte manipulation.
The 16-bit field at bytes 6-7 is split between the 3-bit Flags field and the 13-bit Fragment Offset field. Together with the Identification field, these control IP fragmentation.
| Bit | Name | Values | Description |
|---|---|---|---|
| 0 | Reserved | Must be 0 | Reserved for future use; violation indicates malformed packet |
| 1 | DF (Don't Fragment) | 0 = Allow, 1 = Don't | If set, routers must not fragment; drop and send ICMP if too large |
| 2 | MF (More Fragments) | 0 = Last/Only, 1 = More | Indicates additional fragments follow; MF=0 marks final fragment |
Fragment Offset:
The 13-bit Fragment Offset field indicates where this fragment's data belongs in the original unfragmented datagram:
Common Flag Combinations:
| Flags | DF | MF | Offset | Meaning |
|---|---|---|---|---|
| 0x4000 | 1 | 0 | 0 | Unfragmented, fragmentation prohibited |
| 0x0000 | 0 | 0 | 0 | Unfragmented, fragmentation allowed |
| 0x2000 | 0 | 1 | 0 | First of multiple fragments |
| 0x2000+ | 0 | 1 | 0 | Middle fragment |
| 0x0000+ | 0 | 0 | 0 | Last fragment |
Path MTU Discovery relies on setting DF=1. When a router encounters a packet too large for a link and DF=1, it drops the packet and returns ICMP 'Fragmentation Needed' (Type 3, Code 4) containing the MTU. The sender reduces packet size accordingly. This is why you see DF=1 on virtually all modern TCP traffic.
The Time to Live (TTL) field at byte 8 is an 8-bit value that limits how long a packet can remain in the network. Despite its name referencing time, TTL is actually a hop counter that is decremented at each router.
Original Design vs. Modern Reality:
RFC 791 originally specified TTL as a time limit in seconds. Routers were supposed to decrement TTL by at least 1 for each second the packet was held. In practice, processing times became negligible, and TTL evolved into a pure hop count.
Common Initial TTL Values:
| Operating System | Default TTL | Notes |
|---|---|---|
| Linux | 64 | Configurable via /proc/sys/net/ipv4/ip_default_ttl |
| Windows | 128 | Configurable via registry |
| macOS | 64 | BSD heritage |
| Cisco IOS | 255 | Network equipment often uses max |
| FreeBSD | 64 | BSD standard |
TTL and Traceroute:
The traceroute utility exploits TTL behavior:
This reveals each hop in the path to a destination.
The initial TTL value is a passive OS fingerprinting indicator. If you receive a packet with TTL=58, and you're typically 6 hops away, the sender likely uses initial TTL=64 (Linux/macOS). TTL=122 suggests TTL=128 (Windows). Advanced fingerprinting tools use this alongside other indicators.
The Protocol field at byte 9 is an 8-bit value identifying the upper-layer protocol encapsulated in the IP payload. This field enables demultiplexing—directing the payload to the correct handler.
| Value | Name | RFC | Description |
|---|---|---|---|
| 1 | ICMP | RFC 792 | Internet Control Message Protocol |
| 2 | IGMP | RFC 1112 | Internet Group Management Protocol |
| 4 | IP-in-IP | RFC 2003 | IP encapsulation (tunneling) |
| 6 | TCP | RFC 793 | Transmission Control Protocol |
| 17 | UDP | RFC 768 | User Datagram Protocol |
| 41 | IPv6 | RFC 2473 | IPv6 encapsulation |
| 47 | GRE | RFC 2784 | Generic Routing Encapsulation |
| 50 | ESP | RFC 4303 | Encapsulating Security Payload (IPsec) |
| 51 | AH | RFC 4302 | Authentication Header (IPsec) |
| 58 | ICMPv6 | RFC 4443 | ICMP for IPv6 |
| 89 | OSPF | RFC 2328 | Open Shortest Path First |
| 132 | SCTP | RFC 4960 | Stream Control Transmission Protocol |
How Protocol Demultiplexing Works:
Incoming Packet
│
▼
┌─────────────┐
│ IP Layer │──► Extract Protocol field
└─────────────┘
│
▼ Protocol=6
┌─────────────┐
│ TCP Handler │──► Process TCP segment
└─────────────┘
Raw Sockets:
Applications can create raw sockets bound to specific protocol numbers, allowing them to receive or send packets with custom protocol values. This is used for:
Protocol 0 is reserved and labeled 'HOPOPT' (Hop-by-Hop Options for IPv6). In IPv4 context, protocol 0 is invalid and packets with this value should be discarded. Some attackers use protocol 0 to probe for weak input validation.
The Header Checksum field at bytes 10-11 is a 16-bit one's complement checksum computed over the IP header only (not the payload). It detects corruption in transmission.
Checksum Algorithm:
Verification:
To verify, sum all 16-bit words including the checksum. The result should be 0xFFFF (all ones). Any other value indicates corruption.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
def compute_ipv4_checksum(header: bytes) -> int: """ Compute IPv4 header checksum. Args: header: IP header bytes (checksum field should be 0) Returns: 16-bit checksum value """ if len(header) % 2 != 0: header = header + b'\x00' # Pad to even length # Sum all 16-bit words checksum = 0 for i in range(0, len(header), 2): word = (header[i] << 8) + header[i + 1] checksum += word # Add carries (fold 32-bit sum to 16 bits) while checksum >> 16: checksum = (checksum & 0xFFFF) + (checksum >> 16) # One's complement return ~checksum & 0xFFFF def verify_ipv4_checksum(header: bytes) -> bool: """Verify IP header checksum is valid.""" if len(header) % 2 != 0: header = header + b'\x00' checksum = 0 for i in range(0, len(header), 2): word = (header[i] << 8) + header[i + 1] checksum += word while checksum >> 16: checksum = (checksum & 0xFFFF) + (checksum >> 16) # Valid checksum results in 0xFFFF return checksum == 0xFFFF # Example: Create header and verify checksumheader = bytearray([ 0x45, 0x00, 0x00, 0x3C, # Version, IHL, ToS, Total Length 0x1A, 0x2B, 0x40, 0x00, # ID, Flags, Fragment Offset 0x40, 0x06, 0x00, 0x00, # TTL, Protocol, Checksum (0 initially) 0xC0, 0xA8, 0x01, 0x64, # Source: 192.168.1.100 0x0A, 0x00, 0x00, 0x01 # Dest: 10.0.0.1]) # Compute and insert checksumchecksum = compute_ipv4_checksum(bytes(header))header[10] = (checksum >> 8) & 0xFFheader[11] = checksum & 0xFF print(f"Checksum: 0x{checksum:04X}")print(f"Valid: {verify_ipv4_checksum(bytes(header))}")Because TTL is decremented at each router, the header checksum MUST be recalculated at every hop. This creates processing overhead. IPv6 eliminated the header checksum entirely, relying on link-layer and transport-layer checksums instead.
The Source Address (bytes 12-15) and Destination Address (bytes 16-19) are 32-bit fields containing the IPv4 addresses of the sender and intended recipient.
Address Roles:
Source Address:
Destination Address:
| Address/Range | Type | Purpose |
|---|---|---|
| 0.0.0.0 | This host | Used as source during bootstrap (DHCP) |
| 127.0.0.0/8 | Loopback | Local host; never leaves the machine |
| 10.0.0.0/8 | Private | RFC 1918 private addresses |
| 172.16.0.0/12 | Private | RFC 1918 private addresses |
| 192.168.0.0/16 | Private | RFC 1918 private addresses |
| 169.254.0.0/16 | Link-local | Automatic private addressing (APIPA) |
| 224.0.0.0/4 | Multicast | Class D addresses for group delivery |
| 255.255.255.255 | Broadcast | Limited broadcast (local network only) |
The source address can be set arbitrarily by the sender—nothing in IP validates authenticity. This enables attacks like reflection/amplification DDoS. Defenses include ingress filtering (BCP 38) at network edges, which verifies source addresses are within expected ranges.
The Options field extends from byte 20 to the end of the header (up to byte 59), providing up to 40 bytes for optional features. Options are rarely used in modern networks but remain important for understanding the protocol.
| Type | Name | Purpose | Status |
|---|---|---|---|
| 0 | End of Option List | Marks end of options | Common |
| 1 | No Operation (NOP) | Padding for alignment | Common |
| 7 | Record Route | Record route through network | Rarely used |
| 68 | Timestamp | Record timestamp at each hop | Rarely used |
| 131 | Loose Source Route | Suggested route (can deviate) | Historically used |
| 137 | Strict Source Route | Mandatory route (must follow) | Historically used |
| 148 | Router Alert | Examine packet at every router | Used by RSVP, IGMP |
Option Format:
+--------+--------+--------+--------+
| Type | Length | Data... |
+--------+--------+--------+--------+
1 byte 1 byte Variable
Type Byte Structure:
Why Options Are Rarely Used:
Strict and Loose Source Routing options allow senders to specify the path packets take. This has been used to bypass firewalls and conduct attacks. Best practice: drop all packets with source routing options at network perimeters. Most modern routers do this by default.
Let's bring everything together with a comprehensive parsing example that extracts and validates all IPv4 header fields.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
import structfrom dataclasses import dataclassfrom typing import Optional, List @dataclassclass IPv4Header: """Complete IPv4 header representation.""" # Basic fields version: int ihl: int dscp: int ecn: int total_length: int identification: int # Flags reserved: bool dont_fragment: bool more_fragments: bool fragment_offset: int # Remaining fields ttl: int protocol: int header_checksum: int source_address: str destination_address: str # Derived values header_length: int payload_length: int options: bytes checksum_valid: bool def parse_ipv4_header(packet: bytes) -> IPv4Header: """ Parse a complete IPv4 header from raw bytes. Args: packet: Raw packet bytes (minimum 20 bytes) Returns: IPv4Header dataclass with all parsed fields Raises: ValueError: If packet is malformed """ if len(packet) < 20: raise ValueError(f"Packet too short: {len(packet)} bytes") # Byte 0: Version + IHL version = (packet[0] >> 4) & 0x0F ihl = packet[0] & 0x0F if version != 4: raise ValueError(f"Not IPv4: version = {version}") if ihl < 5: raise ValueError(f"Invalid IHL: {ihl}") header_length = ihl * 4 if len(packet) < header_length: raise ValueError(f"Header truncated: need {header_length}, have {len(packet)}") # Byte 1: DSCP + ECN dscp = (packet[1] >> 2) & 0x3F ecn = packet[1] & 0x03 # Bytes 2-3: Total Length total_length = struct.unpack('!H', packet[2:4])[0] if total_length < header_length: raise ValueError(f"Total length {total_length} < header {header_length}") # Bytes 4-5: Identification identification = struct.unpack('!H', packet[4:6])[0] # Bytes 6-7: Flags + Fragment Offset flags_offset = struct.unpack('!H', packet[6:8])[0] reserved = bool(flags_offset & 0x8000) dont_fragment = bool(flags_offset & 0x4000) more_fragments = bool(flags_offset & 0x2000) fragment_offset = flags_offset & 0x1FFF # Byte 8: TTL ttl = packet[8] # Byte 9: Protocol protocol = packet[9] # Bytes 10-11: Header Checksum header_checksum = struct.unpack('!H', packet[10:12])[0] # Bytes 12-15: Source Address src_bytes = packet[12:16] source_address = '.'.join(str(b) for b in src_bytes) # Bytes 16-19: Destination Address dst_bytes = packet[16:20] destination_address = '.'.join(str(b) for b in dst_bytes) # Bytes 20+: Options options = packet[20:header_length] if header_length > 20 else b'' # Calculate payload length payload_length = total_length - header_length # Verify checksum checksum_valid = verify_checksum(packet[:header_length]) return IPv4Header( version=version, ihl=ihl, dscp=dscp, ecn=ecn, total_length=total_length, identification=identification, reserved=reserved, dont_fragment=dont_fragment, more_fragments=more_fragments, fragment_offset=fragment_offset, ttl=ttl, protocol=protocol, header_checksum=header_checksum, source_address=source_address, destination_address=destination_address, header_length=header_length, payload_length=payload_length, options=options, checksum_valid=checksum_valid ) def verify_checksum(header: bytes) -> bool: """Verify IPv4 header checksum.""" if len(header) % 2 != 0: header = header + b'\x00' total = 0 for i in range(0, len(header), 2): total += (header[i] << 8) + header[i + 1] while total >> 16: total = (total & 0xFFFF) + (total >> 16) return total == 0xFFFF # Example usagesample_packet = bytes([ 0x45, 0x00, # Version=4, IHL=5, DSCP=0, ECN=0 0x00, 0x3c, # Total Length = 60 0x1a, 0x2b, # Identification = 0x1A2B 0x40, 0x00, # DF=1, MF=0, Offset=0 0x40, 0x06, # TTL=64, Protocol=TCP(6) 0xb6, 0x2e, # Checksum (pre-computed) 0xc0, 0xa8, 0x01, 0x64, # Source: 192.168.1.100 0x5d, 0xb8, 0xd8, 0x22, # Dest: 93.184.216.34]) + bytes(40) # TCP segment header = parse_ipv4_header(sample_packet)print(f"Version: {header.version}")print(f"Header Length: {header.header_length} bytes")print(f"Total Length: {header.total_length} bytes")print(f"TTL: {header.ttl}")print(f"Protocol: {header.protocol} ({'TCP' if header.protocol == 6 else 'Other'})")print(f"Source: {header.source_address}")print(f"Destination: {header.destination_address}")print(f"DF Flag: {header.dont_fragment}")print(f"Checksum Valid: {header.checksum_valid}")To validate your understanding, capture real traffic in Wireshark and compare your manual parsing with Wireshark's dissection. Expand the 'Internet Protocol Version 4' section to see all fields. This hands-on practice solidifies header structure knowledge.
We've completed our comprehensive exploration of the IPv4 header format. Let's consolidate everything we've learned across all five pages of this module:
Module Complete:
You now have complete mastery of the IPv4 header format. This knowledge enables you to:
What's Next:
The subsequent modules in this chapter cover Fragmentation in greater depth (calculations, edge cases, performance), TTL and Protocol field interactions, Special Addresses, and IPv4 Options. Each builds on the foundation established here.
Congratulations! You have completed the IPv4 Header Format module. You now possess comprehensive understanding of every field in the IPv4 packet header—their encoding, purposes, interactions, and security implications. This knowledge forms the bedrock for all subsequent networking study.