IP: Best-Effort Delivery Between Machines
IP is the one protocol every device on the internet speaks: a source address, a destination address, a hop counter and a payload, forwarded hop by hop with no promise of delivery, order or uniqueness — a deliberately thin contract that leaves reliability to the ends and lets routers stay stateless.
The problem
Progressive depth
The same mechanism at different altitudes — start where you are.
Each IP packet is a postcard: a from-address, a to-address, a note. The post office forwards each one independently and keeps no record. Postcards can be lost, arrive out of order, or arrive twice. If you need a conversation, you and the recipient number the postcards and re-send the missing ones — that is TCP. The post office never does it for you.
The contract: I will try
The Internet Protocol promises exactly this: give me a packet with a destination address and I will make a reasonable attempt to forward it one hop closer. That is all. A packet may be lost (a queue overflowed, a link flapped, a TTL ran out), duplicated (a link-layer retry after a late acknowledgment), reordered (two packets took different paths or were load-balanced across links), delayed arbitrarily, or corrupted in its payload (IPv4 checksums only the header; IPv6 checksums nothing). IP will not tell the sender about any of it except, sometimes, via ICMP.
This is best-effort delivery, and it is a design choice rather than a shortcoming. Every guarantee a router makes costs state and time per packet, per flow, in every router in the world. By keeping routers stateless — read the destination, look it up, forward, forget — the network scales to billions of flows with hardware that does nothing but a prefix lookup. Everything that needs a stronger promise builds it at the ends: TCP builds a reliable ordered stream on top; QUIC does the same over UDP; DNS and video decide they would rather retry or skip than wait. This is the end-to-end argument, and IP is its purest expression.
Practically: "IP delivered it" is never a statement anyone can make. Applications either use TCP and trust its acknowledgments, or use UDP and handle loss themselves. A network that "drops packets" is doing what IP allows; the question is always how much and why.
The header: addresses, a counter, a type
An IPv4 header is 20 bytes (more with rarely used options); an IPv6 header is a fixed 40. The fields that matter are the same in both. Source address — where replies and errors go. Destination address — what every router looks up. TTL (IPv4) or hop limit (IPv6) — a counter routers decrement. Protocol (IPv4) or next header (IPv6) — what is inside. Total length / payload length — where the packet ends. The rest is versioning, service class, and (IPv4 only) fragmentation bookkeeping and a header checksum.
Two small fields carry more meaning than their size. The protocol number is the second stage of demultiplexing after EtherType: 6 TCP, 17 UDP, 1 ICMP, 58 ICMPv6, 47 GRE, 50 ESP (IPsec), 41 IPv6-in-IPv4. Firewalls match on it; iptables -p tcp is a test of this byte. The DSCP field asks for a service class (voice, bulk); the public internet mostly ignores it, corporate and carrier networks honour it, and it is the only "quality of service" knob in the header.
bits field note
4 version 4
4 IHL header length in 32-bit words (5 = 20 bytes)
8 DSCP / ECN service class; ECN bits for congestion signalling
16 total length header + payload, max 65535
16 identification groups fragments of one packet
3 flags DF (don't fragment), MF (more fragments)
13 fragment offset position of this fragment, in 8-byte units
8 TTL decremented by every router; 0 → drop + ICMP Time Exceeded
8 protocol 6 TCP, 17 UDP, 1 ICMP, 47 GRE, 50 ESP
16 header checksum header only; recomputed at each hop
32 source address
32 destination address
IPv6 (40 bytes): version, traffic class, flow label, payload length, next header, hop limit,
128-bit source, 128-bit destination — no checksum, no fragment fields in the base headerTTL: the counter that prevents forever
Routing is distributed and eventually consistent; during convergence two routers can each believe the other is the way to a destination, and a packet would bounce between them until the end of time. The TTL makes that impossible: the sender sets it (64 on Linux and macOS, 128 on Windows, 255 on many network devices), every router subtracts one, and a router that decrements it to zero discards the packet and sends the source an ICMP Time Exceeded message. A loop now costs at most 64 forwards per packet instead of infinity. The name is historical — it was once meant to be seconds — but it has only ever counted hops in practice.
traceroute is the TTL used as an instrument: send a probe with TTL 1 and the first router’s ICMP error reveals its address; TTL 2 reveals the second; and so on until the destination itself answers. Each line is a router that decremented the counter to zero. A traceroute that shows the same two addresses alternating is a loop, caught by exactly the mechanism it is measuring. See traceroute: Discovering the Path Hop by Hop.
- Default TTLs: 64 (Linux, macOS, most Unix), 128 (Windows), 255 (Cisco IOS, many routers). A received TTL of 116 is probably a Windows host 12 hops away.
- ICMP Time Exceeded is sent to the *source* address, which under NAT is the public address; the NAT translates it back because the ICMP payload contains the original packet’s header.
Fragmentation: what IPv4 allows and IPv6 forbids
A packet larger than a link’s MTU cannot be sent as one frame. IPv4 lets a router split it: each fragment carries the same identification, a fragment offset, and the MF (more fragments) flag on all but the last; the destination host reassembles, holding fragments in a buffer until all arrive or a timer (typically 30 s) expires. If the sender sets DF (don’t fragment), the router instead drops the packet and returns ICMP Fragmentation Needed with the link’s MTU, which is how path MTU discovery works. Modern stacks always set DF for TCP; in-transit fragmentation is now mostly a UDP and tunnelling phenomenon.
IPv6 made the decision final: routers never fragment. A packet too big for the next link is dropped with ICMPv6 Packet Too Big, full stop. Only the originating host may fragment, using an extension header, and TCP over IPv6 avoids even that by respecting the discovered path MTU. The minimum MTU every IPv6 link must support is 1280 bytes, so a sender that never exceeds 1280 never needs to discover anything. The cost is the same as in IPv4 with DF: if the ICMP error is filtered, big packets silently vanish — the MTU black hole described in Encapsulation: Data, Segment, Packet, Frame.
- IPv4: routers *may* fragment unless DF is set; hosts reassemble; fragments after the first have no transport header.
- IPv6: routers *never* fragment; senders discover the path MTU or stay ≤ 1280; fragmentation, if any, is an extension header added by the source.
ICMP: IP’s control channel
Best-effort does not mean mute. ICMP (protocol 1; ICMPv6 is protocol 58) is how IP reports what it could not do and how hosts probe the network. It is carried inside IP like a transport but is part of IP’s own machinery — there are no ports, and a message about a dropped packet includes the first bytes of that packet so the sender can match it to a socket. The messages an engineer meets: Destination Unreachable (network, host, port unreachable; *Fragmentation Needed*), Time Exceeded (TTL hit zero; reassembly timed out), Echo Request / Reply (ping), Redirect (use a different first hop), and in IPv6 the whole Neighbor Discovery family.
A connect() that returns EHOSTUNREACH or ENETUNREACH immediately, rather than timing out, received an ICMP unreachable from a router. A UDP sendto() to a port nobody listens on gets ICMP *Port Unreachable*, which is how traceroute (UDP variant) knows it reached the destination. Blocking all ICMP because "ping is a security risk" removes every one of these signals; the correct policy allows unreachables, time-exceeded and packet-too-big and rate-limits echo. See ping: What an Echo Actually Proves and Firewalls.
Key points
- IP promises only to try: packets may be lost, duplicated, reordered or delayed, and IP will not tell you. Reliability is the transport’s job, at the ends.
- Stateless routers are the point: read the destination, longest-prefix lookup, forward, forget. That is why the internet scales.
- The fields that matter: source, destination, TTL/hop limit, protocol/next header, length. IPv4 is 20 bytes with a header checksum; IPv6 is 40 with none.
- TTL is decremented at every router and kills looping packets;
tracerouteuses the resulting ICMP errors to list the path. - IPv4 routers may fragment (unless DF); IPv6 routers never do. Both rely on ICMP Packet Too Big / Fragmentation Needed for path MTU discovery.
- Protocol numbers (6, 17, 1, 58) are how IP hands the payload up; firewalls match on them.
- ICMP is IP’s error and probe channel; filtering it all breaks PMTUD, fast failure and (on IPv6) everything.
Why does this exist?
Mechanisms are answers to constraints. Open each question before reading the answer.
▸Why does IP not guarantee delivery?
Guarantees need state — per-flow buffers, sequence tracking, retransmit timers — in every router. Best-effort lets a router be a lookup and a queue, which is what lets it forward at terabits. The ends, which already have the data, add reliability where it is wanted.
▸Why is there a hop counter but no path?
Routers decide independently and routes change under them; no one holds the path. A counter is the cheapest loop-breaker that works without global knowledge.
▸Why did IPv6 drop the header checksum?
The link layer already CRCs the frame and the transport already checksums the segment plus addresses; the IPv4 header checksum was a third check that had to be recomputed at every hop because of the TTL. Removing it made forwarding cheaper.
The IPv4 header
How it fails
What the failure looks like from inside real software.
- Silent loss on a path: TCP retransmits and the app sees latency; UDP apps see gaps. Nothing logs at the IP layer.
mtrfinds the hop. - MTU black hole: ICMP Fragmentation Needed / Packet Too Big filtered; handshakes work, bulk transfers hang.
- A routing loop during convergence: TTL expires,
tracerouteshows two routers alternating, ICMP Time Exceeded floods the source. - ICMP blocked wholesale:
connect()waits for a full timeout instead of failing instantly with unreachable; IPv6 stops working entirely. - Reordering over ECMP with per-packet hashing: TCP sees duplicate ACKs and spuriously retransmits; throughput collapses with zero loss.
- Fragment drop at a firewall: DNS answers over 1500 bytes (DNSSEC) never arrive; small answers work.