The IP Protocol and Datagram Format
Imagine a postal system where every letter must reach its destination. But no clerk along the way promises delivery. No one checks for damage or keeps letters in order. Astonishingly, this is exactly the deal the Internet Protocol (IP) offers. It has carried essentially all Internet traffic since the 1970s. Every video call, web page, and email is chopped into datagrams. IP ships these across a chain of unrelated networks — Ethernet, Wi-Fi, fiber backbones. No guarantee exists that any single one arrives. This looks like a design flaw until you see why it is a feature. By refusing to promise reliability, ordering, or timing, IP stays radically simple. That simplicity is precisely what let it run over telephone lines in 1983. It also lets it run over 400-gigabit fiber links today. The hard work — retransmission, ordering, flow control — is pushed up to the Transport Layer. This follows the layering principle sometimes called the end-to-end argument. Put functionality at the edges unless the network truly must handle it. This article is about the datagram itself — the actual bytes IP routers read to do their job. We dissect the IPv4 header field by field. We explain what "best-effort connectionless delivery" precisely means as a service contract. We also show how a handful of header fields let IP act as the universal glue. This glue connects wildly different link-layer technologies. This builds directly on Network Layer Services and the Forwarding vs. Routing Distinction. It turns that abstract service model into concrete bits on the wire. By the end you will be able to read a raw IPv4 header. You will explain what each field is for and why it exists. You will understand precisely what guarantees IP does and does not give the layers above it. This groundwork also sets up two topics addressed elsewhere: IPv4 Addressing and Subnetting and IP Fragmentation and the TTL Field. Both of these depend on header fields introduced here.
- IP provides best-effort, connectionless delivery: no guarantee of delivery, order, or timing.
- The IPv4 header is 20–60 bytes, with fixed fields plus optional variable-length options.
- Key fields: Version, Header Length, Total Length, Identification, Flags/Fragment Offset, TTL, Protocol, Header Checksum, Source/Destination Address.
- IP is deliberately "dumb": it does not retransmit, reorder, or flow-control — that is delegated upward.
- This simplicity is why IP can run over almost any link layer, acting as the network's universal glue.
- Routers process only the header per hop; the payload is opaque to IP itself.
IP achieves universal interoperability by promising almost nothing — best-effort, connectionless, per-datagram forwarding — and pushing every stronger guarantee to the endpoints.
The idea
The Internet Protocol (IP) is the network-layer protocol that moves discrete units of data, called datagrams, from a source host to a destination host across an internetwork of possibly heterogeneous networks. IP's service model has two defining properties. It is connectionless: every datagram is routed independently, carrying full source and destination addresses, with no setup phase and no per-flow state kept by routers. It is best-effort: the network makes no promise that a datagram will arrive, arrive only once, arrive undamaged, or arrive in the order it was sent. Datagrams can be dropped (queue overflow), duplicated, corrupted (rare, checksums help), reordered (different paths), or delayed arbitrarily. This minimalism is intentional and traces to the end-to-end argument: a function like reliable delivery can only be correctly and completely implemented by the endpoints that understand the application's needs, so building a "mostly reliable" network layer only adds cost and complexity without eliminating the need for end-to-end checks. IP therefore focuses on one job — getting a datagram, hop by hop, as far as it can toward its destination, using the destination address and each router's forwarding table — and leaves reliability, ordering, and flow control to protocols like TCP. The concrete artifact that makes this work is the IP datagram format: a header of well-defined fields prepended to whatever payload the transport layer hands down. Every router that touches a datagram inspects only this header (never trusting or even usually understanding the payload) to decide where to send it next, whether it has expired, and whether it needs to be broken apart. Studying the header field by field is really studying the entire contract IP offers the rest of the Internet.
How it works
An IP datagram is built once at the source and reconstructed conceptually — though never truly "processed" beyond the header — at every router along the path. The following pseudocode captures what a router does to every datagram at every hop; this is the actual mechanism, executed billions of times a second across the Internet. ``` function IP_FORWARD(datagram, incoming_interface): header = parse_header(datagram) // 1. Sanity check if header.version != 4: drop(datagram); return if not verify_checksum(header): drop(datagram); return // corrupted header, discard silently // 2. Decrement time-to-live header.TTL = header.TTL - 1 if header.TTL <= 0: send_ICMP_time_exceeded(header.source) drop(datagram); return // 3. Recompute checksum (TTL changed, so checksum must change too) header.checksum = compute_checksum(header) // 4. Is this router the final destination? if header.dest_addr == this_router_address: deliver_to_local_process(datagram, header.protocol) return // 5. Look up next hop using longest-prefix match next_hop, out_interface = forwarding_table.lookup(header.dest_addr) if next_hop is null: send_ICMP_destination_unreachable(header.source) drop(datagram); return // 6. Fragment if the outgoing link's MTU is too small if datagram.length > MTU(out_interface): if header.flags.DONT_FRAGMENT: send_ICMP_fragmentation_needed(header.source) drop(datagram); return fragments = fragment(datagram, MTU(out_interface)) for each fragment in fragments: transmit(fragment, out_interface, next_hop) else: transmit(datagram, out_interface, next_hop) ``` Notice what is absent: there is no acknowledgment sent back to the source confirming forwarding, no retry if `transmit` silently fails at the link layer, no buffer held in case the datagram needs to be resent, and no coordination with any other datagram from the same flow. Each datagram is a self-contained decision problem for each router — this is exactly what "connectionless" and "best-effort" mean operationally.
The IPv4 header, field by field
The header is a minimum of 20 bytes (5 32-bit words), extendable to 60 bytes via the Options field.
| Field | Size (bits) | Purpose |
|---|---|---|
| Version | 4 | Identifies IP version (4 for IPv4); lets routers reject/handle mismatched versions. |
| Header Length (IHL) | 4 | Header length in 32-bit words (min 5 = 20 bytes); needed because Options are variable-length. |
| Type of Service (DSCP/ECN) | 8 | Requests differentiated handling (e.g., low delay) and signals congestion (ECN). |
| Total Length | 16 | Entire datagram length (header + payload) in bytes; max 65,535 bytes. |
| Identification | 16 | Tags all fragments of one original datagram so the receiver can reassemble them. |
| Flags | 3 | Controls/reports fragmentation: Don't Fragment (DF), More Fragments (MF). |
| Fragment Offset | 13 | Position of this fragment's data within the original datagram, in 8-byte units. |
| Time to Live (TTL) | 8 | Decremented at every hop; datagram is discarded at zero — prevents infinite routing loops. |
| Protocol | 8 | Identifies the upper-layer protocol (6 = TCP, 17 = UDP, 1 = ICMP) so the payload is delivered correctly. |
| Header Checksum | 16 | Detects corruption in the header only (not payload); recomputed at every hop since TTL changes. |
| Source Address | 32 | The sending host's IP address. |
| Destination Address | 32 | The receiving host's IP address; used for forwarding decisions. |
| Options (+padding) | variable, 0–320 | Rarely used extensions (e.g., record route, timestamp); padded to a 32-bit boundary. |
Two design choices deserve emphasis. First, TTL exists purely as a self-defense mechanism: routing loops are possible in any distributed system without global coordination, and TTL guarantees a datagram is eventually destroyed rather than circulating forever, consuming bandwidth. Second, the checksum covers only the header, not the payload — checking the payload is left to the transport layer (TCP/UDP have their own checksums), another direct application of the end-to-end argument: since transport-layer checksums are needed anyway (the network cannot verify application-level correctness), duplicating full-payload checking at every router hop would be wasted work.
Complexity & correctness
Per-router processing time. The core operations at each hop are: checksum verification, TTL decrement, checksum recomputation, and a routing-table lookup. Checksum verification and recomputation over a 20-byte header (or up to 60 with options) take time relative to header size — it is a fixed-size one's-complement sum, independent of payload length. The routing lookup is a longest-prefix match over the destination address; using a trie-based structure (as real routers do), this is where is the address width in bits — effectively constant, and in practice implemented in specialized hardware (TCAMs) for true lookup. So per-datagram forwarding work at each router is in the size of the network — it does not grow with the number of hosts, flows, or the datagram's payload size. This is a deliberate efficiency property: a core router forwarding millions of datagrams per second cannot afford per-flow bookkeeping, and IP's connectionless design guarantees it never has to maintain any.
Space. IP routers keep no per-datagram or per-flow state between forwarding decisions (ignoring optional features like NAT, which is not part of IP proper). The only persistent memory is the forwarding table, whose size is bounded by the number of network prefixes known to the router (hundreds of thousands in core Internet routers), not by traffic volume. This is the crucial space efficiency argument: a router handling a million simultaneous flows uses the same memory as one handling ten, because IP never allocates per-flow resources.
Correctness of the "no infinite loop" guarantee. Consider the invariant: every datagram's TTL strictly decreases by at least 1 at every hop it visits, and a datagram with TTL = 0 is always discarded. Since TTL starts at a finite value (commonly 64) and is a non-negative integer that strictly decreases each hop, the datagram can visit at most `initial_TTL` routers before being discarded — even if the forwarding tables contain a routing loop (a temporary inconsistency that can occur during route changes). This proves termination: no datagram circulates the network forever, bounding wasted bandwidth to hops per errant datagram, regardless of how badly routing tables are misconfigured.
Correctness of best-effort delivery (what is and isn't guaranteed). IP's specification makes exactly one promise: if a datagram is delivered, its header was not corrupted undetected (subject to the checksum's limitations — a 16-bit one's-complement sum only detects, it does not correct, and can miss some multi-bit error patterns). It makes no promise of delivery at all. This is not a bug to be proven away — it is the specification. The honest tradeoff: by refusing to guarantee delivery, IP avoids buffering, retransmission timers, and acknowledgment traffic at every router, which is what makes it scale to arbitrary network sizes and heterogeneous, sometimes lossy, link technologies (early IP ran over noisy packet-radio links). Reliability, when an application needs it, is reconstructed end-to-end by TCP using sequence numbers and retransmission — a strictly more expensive mechanism that IP correctly avoids imposing on traffic that does not need it (e.g., real-time voice, which prefers a dropped datagram over a late one).
Worked example
Trace a real datagram carrying a DNS query (over UDP) from a laptop (`203.0.113.7`) to a DNS resolver (`198.51.100.53`), crossing two routers, R1 and R2, with R2's outgoing link having a smaller MTU (1200 bytes) than R1's.
Original datagram built at the source:
| Field | Value |
|---|---|
| Version | 4 |
| IHL | 5 (20-byte header, no options) |
| Total Length | 1500 bytes (20 header + 1480 payload) |
| Identification | 0x1A2B |
| Flags | DF=0, MF=0 |
| Fragment Offset | 0 |
| TTL | 64 |
| Protocol | 17 (UDP) |
| Header Checksum | 0x9F3C (computed over header) |
| Source | 203.0.113.7 |
| Destination | 198.51.100.53 |
Step 1 — At R1 (link MTU 1500, no fragmentation needed):
| Action | Result |
|---|---|
| Checksum verified | valid — accept |
| TTL decremented | 64 → 63 |
| Checksum recomputed | 0x9F3C → new value (TTL changed) |
| Lookup dest 198.51.100.53 | longest-prefix match → next hop is R2 |
| MTU check | 1500 ≤ 1500, no fragmentation |
| Action | forward whole datagram to R2 |
Step 2 — At R2 (outgoing link MTU 1200):
| Action | Result |
|---|---|
| Checksum verified | valid |
| TTL decremented | 63 → 62 |
| MTU check | 1500 > 1200 → must fragment |
| DF flag | 0 (fragmentation permitted) |
R2 splits the 1480-byte payload into two fragments (each payload chunk must be a multiple of 8 bytes, per the Fragment Offset unit):
| Fragment | Header Total Length | Payload bytes carried | Fragment Offset (×8 bytes) | MF flag |
|---|---|---|---|---|
| Fragment A | 20 + 1176 = 1196 | bytes 0–1175 | 0 | 1 (more fragments follow) |
| Fragment B | 20 + 304 = 324 | bytes 1176–1479 | 147 (=1176/8) | 0 (last fragment) |
Both fragments keep Identification = 0x1A2B (same as the original), so the destination host knows they belong together; TTL = 62 in both. Each fragment gets its own header checksum computed over its own (smaller) header. Both are forwarded independently — potentially arriving out of order, since IP gives no ordering guarantee even for fragments of the same datagram.
Step 3 — At the destination host: the IP layer collects fragments sharing (Source, Destination, Protocol, Identification), reassembles the original 1480-byte payload using each fragment's offset, and — only once all fragments have arrived — passes the reassembled datagram up to UDP using the Protocol field (17) to route it to the UDP module, which then delivers it to the process bound to the destination port inside the DNS query. If any single fragment is lost, the destination eventually times out reassembly and discards all fragments received so far — IP does not request retransmission of the missing piece; that is left entirely to whatever (if anything) sits above UDP. This example shows the header fields working together as a system: Identification and Fragment Offset exist specifically to make fragmentation and reassembly possible, TTL keeps decrementing independently of fragmentation, and the checksum must be recomputed at every point the header changes, including after fragmentation splits it into new headers.
Common pitfalls & traps
| Pitfall | Why it happens | Consequence |
|---|---|---|
| Assuming IP guarantees delivery | Confusing "network layer" with "reliable transport" | Applications built directly on raw IP silently lose data with no warning |
| Forgetting the checksum covers only the header | Payload corruption is assumed caught by IP | Corrupted payloads pass IP's checksum check; must rely on TCP/UDP checksum |
| Off-by-one on TTL semantics | Treating TTL as "hops remaining" exactly vs. "decrement-then-check-zero" | Miscounted traceroute hop counts; confusion about whether TTL=1 delivers locally |
| Ignoring that fragments can arrive out of order or be lost independently | Assuming fragmentation is transparent and safe | Reassembly can stall forever waiting for one lost fragment; wastes buffer space until timeout |
| Believing Total Length bounds real-world packet size | Reading the RFC number (65,535) without considering link MTU | Datagrams exceeding path MTU get needlessly fragmented or dropped if DF is set |
| Recomputing checksum incorrectly after modifying only some header fields | Forgetting TTL changes at every hop | A stale checksum causes the next router to (correctly) discard a technically valid datagram |
Pioneers & history
Origins · 1 minVinton G. Cerf and how this was discovered
Cerf co-authored the 1974 paper "A Protocol for Packet Network Intercommunication," which introduced the idea of a network of networks connected by a common, minimal internetworking protocol — the conceptual seed of IP. He later helped split the original monolithic TCP into separate TCP and IP protocols, formalizing the layering that makes IP's best-effort simplicity possible.
Kahn's work on open-architecture networking established the ground rules IP still follows: each network could keep its own internal technology, and a common internetworking layer would glue them together without requiring internal changes to any of them — precisely the role IP plays as "network-layer glue" today.
Where this matters
Every router on the Internet — from a home Wi-Fi gateway to a core backbone router pushing hundreds of gigabits per second — implements exactly the forwarding logic sketched above: check the header, decrement TTL, look up the next hop, forward or fragment. Content delivery networks, cloud data centers, and mobile carrier networks all rely on IP's connectionless design to let traffic be rerouted instantly around failures without any per-flow renegotiation, since no router held connection state to begin with. Network diagnostic tools like `traceroute` and `ping` directly exploit the TTL field (sending datagrams with increasing TTL values to provoke "time exceeded" replies) and the header checksum mechanism to map paths and test reachability. Firewalls and routers inspect the Protocol field to distinguish TCP from UDP from ICMP traffic, and Quality-of-Service systems use the Type of Service byte to prioritize latency-sensitive traffic such as voice-over-IP. The entire architecture of the modern Internet — its ability to run over Ethernet, Wi-Fi, satellite links, and cellular data with no changes to the applications above — rests on the deliberately minimal service contract this header encodes.
Ask the tutor: How does Path MTU Discovery use the Don't Fragment flag to avoid IP fragmentation entirely?
Does IP guarantee that datagrams arrive in the order they were sent?
No. Because IP is connectionless, each datagram is routed independently and may take a different path due to routing changes, load balancing, or route flaps. Different paths have different latencies, so a datagram sent second can easily arrive before one sent first. Even fragments of the same original datagram can arrive out of order, since each fragment is itself forwarded as an independent datagram once created. Any application or protocol that needs in-order delivery — most obviously TCP — must add its own sequence numbering and reordering logic on top of IP; this is not optional bookkeeping IP could "just add," since maintaining state to guarantee order at every intermediate router would contradict IP's stateless, connectionless design and destroy its scalability.
What actually happens when a datagram's TTL reaches zero?
When a router decrements the TTL field and the result is zero, it discards the datagram rather than forwarding it, and it typically sends an ICMP "Time Exceeded" message back to the original source address. The datagram is not "returned" — it is simply dropped, and the source only learns of the failure via this optional ICMP notification, which itself can be lost since ICMP messages are also delivered best-effort. This is exactly the mechanism `traceroute` exploits: it sends a series of datagrams with TTL = 1, 2, 3, ... and uses the sequence of ICMP Time Exceeded replies (each from the router where TTL hit zero) to map every hop on the path to a destination.
Why is the IP header checksum computed only over the header, not the whole datagram?
The transport layer above IP — TCP or UDP — already computes its own checksum over both its own header and the payload, and that check happens end-to-end at the actual receiving host. If IP additionally checksummed the full payload at every router, it would duplicate work that is already done correctly and necessarily at the endpoints, since only the endpoints can detect corruption introduced anywhere along the path, including inside end-host software. This is a direct instance of the end-to-end argument: checking the header alone lets routers quickly discard datagrams with corrupted routing information (which would otherwise misroute or crash parsing logic), while leaving payload integrity — which routers cannot usefully act on anyway — entirely to the endpoints.
How is "connectionless" different from "unreliable," and does IP have to be both?
Connectionless describes how datagrams are routed: independently, with no setup phase and no state retained between them, each carrying full addressing information. Unreliable (best-effort) describes what guarantees are made about outcome: no promise of delivery, order, or non-duplication. These are logically separable — one can imagine a connectionless protocol that is nonetheless reliable (each datagram independently routed, but retransmitted until acknowledged) — but IP chooses to be both, because adding reliability to a connectionless protocol at the network layer would require every router to buffer datagrams and manage retransmission timers, defeating the statelessness that makes connectionless routing efficient in the first place. IP's actual design keeps both properties minimal, by choice, for speed and scalability.
What is the difference between IP fragmentation and normal per-hop forwarding?
Normal forwarding leaves a datagram's header and payload untouched aside from TTL decrement and checksum recomputation; the router simply picks a next hop and transmits. Fragmentation is triggered only when a datagram is too large for the outgoing link's Maximum Transmission Unit (MTU) and the Don't Fragment (DF) flag is not set; in that case, the router splits the payload into smaller chunks, gives each chunk its own IP header (copying Identification, but computing a fresh Total Length, Fragment Offset, and checksum), and forwards each as an independent datagram. Fragmentation adds overhead (extra headers, possible loss of any one fragment invalidating the whole datagram) and is generally avoided in modern networks via Path MTU Discovery, which uses DF-flagged probe datagrams and ICMP feedback to find a size that needs no fragmentation at all.
Why does the IP header have a variable length via Options, and are Options still used today?
The Options field was designed to let IP support less-common features — such as recording the route taken (Record Route), timestamping at each hop (Timestamp), or specifying required intermediate hops (Loose/Strict Source Routing) — without bloating every ordinary datagram's header. The Header Length (IHL) field exists precisely because Options make header length variable; a router must read IHL before it knows where the header ends and the payload begins. In practice, IP options are rarely used on the modern Internet: many routers process them slowly (since fixed-length headers are far easier to optimize in hardware) or drop datagrams containing them as a security precaution, since options have historically been exploited for reconnaissance and spoofing attacks.
Does IP itself use port numbers to deliver data to the correct application?
No. Port numbers are a transport-layer concept, introduced by TCP and UDP, not by IP. IP's Protocol field only identifies which transport protocol (TCP, UDP, ICMP, etc.) should receive the payload once it reaches the destination host; IP has no notion of applications, sockets, or ports at all. Once IP delivers the reassembled datagram's payload up to the correct transport protocol module based on the Protocol field, that transport module then reads its own header (which does contain port numbers) to determine which specific process on the host should receive the data. This division is another instance of layering: IP moves datagrams between hosts, transport protocols move data between processes on those hosts.