Labpacketencapsulationheadersttlnat

Follow One Packet

Pick a source, a destination, a protocol and a port, and follow a single packet from send() through the socket, transport, IP and link layers, out of the NIC, through a NAT router and every hop after it, watching which headers exist, which fields change and which stay fixed — and what a router does in the microseconds it holds the packet.

ConceptualIPv4Educational model

The problem

A packet leaves your laptop as a few hundred bytes and arrives at a server across the world. Between them are a dozen devices that each rewrite part of it. Which bytes survive the trip unchanged, which are rewritten at every hop, and by whom?

Choose the packet, then follow it

The interactive asks for four things: a client address (say 192.168.1.42 behind a home router), a server address (203.0.113.10), a protocol (TCP or UDP) and a destination port (443). Everything that follows is determined by those choices plus the state of the devices on the way. This lesson is the narration: at each stop, which headers exist, which fields change, and what the device is doing. The model is conceptual — a real path has more hops, and the numbers are illustrative — but every field behaviour described here is what happens on a real network.

The layers that build the packet are the subject of Encapsulation: Data, Segment, Packet, Frame; the Packet Inspector: Read a Frame Field by Field shows the bytes. Here the focus is movement: the same packet, re-examined at every hop.

The journey of one packet (client behind NAT to a public server)
  1. Application`send(fd, buf, len)` — bytes, no headers yet
  2. Socketthe descriptor maps to a kernel socket with a 4-tuple: 192.168.1.42:51742 → 203.0.113.10:443
  3. TransportTCP header: src/dst port, seq, ack, flags, window, checksum — ~20–32 bytes
  4. IPIP header: src 192.168.1.42, dst 203.0.113.10, TTL 64, protocol 6, checksum — 20 bytes
  5. Linkrouting table says via 192.168.1.1; ARP gives its MAC; Ethernet frame: dst MAC = router, src MAC = laptop
  6. NICserialises the frame onto the wire or the air; may compute checksums in hardware
  7. Home router (NAT)rewrites src to 198.51.100.77:40012, records the mapping, TTL 64 → 63, new MACs toward the ISP
  8. ISP routerlongest-prefix match on 203.0.113.10; TTL 63 → 62; new src/dst MACs; queue on the egress port
  9. Next hop … × nevery hop: lookup, decrement, checksum, re-frame, queue; IP addresses and TCP header untouched
  10. DestinationNIC → kernel → TCP finds the listening socket → data lands in the receive buffer → `recv()` returns

What changes and what does not

The packet is three nested envelopes. The link-layer frame is rewritten completely at every hop: the source MAC becomes the forwarding router’s egress interface and the destination MAC becomes the next hop’s, because MACs only mean something within one link (MAC Addresses: Identity for One Hop). The IP header is mostly preserved end to end — the destination address never changes, and the source changes only at a NAT — but TTL decrements by one per hop and the IPv4 header checksum is recomputed each time because TTL changed (IPv6 has no header checksum, one reason it forwards cheaper). The TCP or UDP header and payload are untouched by ordinary routers; the only middlebox that edits them is NAT, which must rewrite the source port and the transport checksum because the checksum covers a pseudo-header containing the IP addresses.

So at a router in the middle of the internet, the packet still says "from 198.51.100.77:40012 to 203.0.113.10:443, TCP seq 3948211", exactly as the NAT sent it; only the MACs and the TTL reveal how far it has travelled. This is why the destination can reply — the addresses it sees are routable — and why the home router can deliver the reply to the right laptop: the mapping 198.51.100.77:40012 ↔ 192.168.1.42:51742 it recorded on the way out.

Per-hop treatment of each field (IPv4; a real path has more hops)
FieldAt the laptopAt the NAT routerAt every internet routerAt the server
Dst MAC / src MACrouter MAC / laptop MACrewritten: ISP next hop / router WANrewritten every hopserver NIC / last router
IP src192.168.1.42→ 198.51.100.77 (NAT)unchanged198.51.100.77 (the reply goes here)
IP dst203.0.113.10unchangedunchanged — the lookup key203.0.113.10: mine, deliver up
TTL646362, 61, … one less per hop~50; only matters if it hit 0
IP header checksumcomputedrecomputed (src and TTL changed)recomputed (TTL changed)verified
TCP src port51742→ 40012 (NAT)unchanged40012
TCP dst port, seq, ack, flags443, seq 3948211unchangedunchanged — routers do not read themselects the socket; seq orders the bytes
TCP checksumcomputed (pseudo-header)recomputed (addresses/port changed)unchangedverified
Payloadbytes from send()unchangedunchangedcopied into the receive buffer

What a router does in the microseconds it holds the packet

A router receives the frame on an ingress port, checks the frame’s checksum and that the destination MAC is its own, strips the frame, and looks at the IP header. It verifies the header checksum, decrements TTL (if it becomes 0: discard, send ICMP Time Exceeded — the traceroute: Discovering the Path Hop by Hop mechanism), and looks the destination address up in the forwarding table with longest-prefix match: among all prefixes that contain 203.0.113.10, the most specific wins (The Routing Table and Longest-Prefix Match; a Trie is the classic structure, and hardware routers use TCAM or compressed tries to do it in a fixed number of memory accesses). The result is an egress interface and a next-hop address. It recomputes the IP checksum, resolves the next hop’s MAC (usually already cached), builds a new frame, and places it in the egress port’s queue.

Time budget: on a 10 Gbit/s port, a 1500-byte packet takes 1.2 µs to serialise, so the lookup and rewrite have to fit in about that long to keep up with line rate — which is why they are done in hardware. Queueing is where variable latency comes from: if the egress port is busy, the packet waits, and under sustained overload the queue fills and the router drops — the loss that Congestion Control: Protecting the Network reacts to. Bufferbloat is the opposite failure: queues so deep the packet waits tens of milliseconds instead of being dropped.

What a router does not do: read ports, sequence numbers or payload (a stateless router forwards on the IP destination alone), reassemble fragments, or keep per-connection state. Anything that does those things — NAT, a stateful firewall, a load balancer — is a middlebox, and middleboxes are where most surprising packet rewrites happen.

  • Per packet: verify → decrement TTL → longest-prefix lookup → recompute checksum → re-frame with new MACs → enqueue.
  • ~1 µs of work at 10 Gbit/s; queueing adds 0–tens of ms depending on load.
  • IPv6 drops the header checksum and forbids router fragmentation, trimming the per-hop work.

The reply, and what the lab lets you change

The server’s reply carries dst 198.51.100.77:40012 — the NAT’s public address — and travels an independently routed path back. At the home router the mapping turns it into dst 192.168.1.42:51742 and the laptop’s kernel delivers it to the socket whose 4-tuple matches. No device on the return path knows the laptop’s private address ever existed. Switch the lab to UDP and the transport header shrinks to 8 bytes with no sequence numbers, so the destination can no longer tell whether datagrams were lost or reordered — that is the UDP: Datagrams and the Contract You Choose contract. Switch to a destination in the same subnet and the router disappears: ARP resolves the server’s MAC directly and the frame is delivered by the switch with no TTL decrement at all.

This is also the "send a packet" journey seen from the packet’s point of view rather than the OS’s; together they cover both halves of the question "what happens between send() and recv()".

  • Same subnet → no router, no TTL change; the switch forwards by MAC.
  • Different subnet → default gateway, NAT if the source is private, longest-prefix match at every hop after.
  • UDP → 8-byte header, no seq/ack; the lab shows nothing to reorder by.

Key points

  • A packet is nested envelopes: the frame is rewritten every hop, the IP header is preserved except TTL (and src at NAT), the transport header and payload are untouched by routers.
  • The destination IP is the lookup key at every hop and never changes; MACs are per-link and change at every hop.
  • NAT rewrites source IP and port, recomputes the IP and transport checksums, and records a mapping for the reply.
  • A router: verify, decrement TTL, longest-prefix match, recompute checksum, re-frame, enqueue — about a microsecond, plus queueing.
  • Routers do not read ports or payload; anything that does is a middlebox and the usual source of surprises.
  • The reply is routed independently and reaches the private client only because of the NAT mapping created on the way out.

Why does this exist?

Mechanisms are answers to constraints. Open each question before reading the answer.

Why do MACs change at every hop but IPs do not?

A MAC identifies an interface on one link; it has no meaning beyond it. IP addresses are the end-to-end names. Each layer solves delivery at its own scope, and re-framing at each hop is how the link layer hands the same IP packet across a chain of links.

Why decrement TTL at all?

Routing loops happen during convergence. Without TTL a looping packet would circulate forever and the loop would fill with copies; TTL bounds its life to ~64 hops, and the ICMP it triggers gave us traceroute for free.

Why does NAT have to touch the TCP checksum?

The TCP and UDP checksums cover a pseudo-header with the IP addresses, so changing the source address invalidates them. NAT therefore rewrites layer 4 too — one reason NAT breaks protocols that embed addresses in their payload.

Follow one packet

Follow one packet
Fourteen hops from write() to read(). Watch which fields change at each and which never do.
Protocol
SimulatedIPv4
Hops (click to inspect)
Applicationclient host~1 µs (syscall entry)
write(fd, buf, 517): the TLS ClientHello leaves user space as plain bytes. No headers exist yet.
Packet state at this hop (changed fields highlighted)
src MAC
dst MAC
src IP:port
(no header yet)
dst IP:port
(no header yet)
TTL
seq
checksums
Constant end to end
dst 203.0.113.10:443 · protocol 6 (TCP) · seq 0x1a2b3c4d · payload 517 B
1/14 · Application

How it fails

What the failure looks like from inside real software.

  • A TTL that hits zero on a looping path during a routing convergence; the client sees ICMP Time Exceeded, or nothing.
  • A NAT table that fills or expires a mapping mid-connection; replies arrive at the public IP and are dropped — see the NAT table exhaustion challenge.
  • A packet larger than a link’s MTU with DF set: dropped with ICMP Fragmentation Needed, which a firewall then filters — the black hole.
  • A middlebox that rewrites payload it does not understand (an ALG mangling SIP or FTP), corrupting the very thing it tried to help.
  • A checksum offload misconfiguration showing "bad checksum" in a capture on the sending host — an artefact of capturing before the NIC computed it, not corruption.

Follow it through every layer

This lesson is one node of a longer journey. Zoom out, then zoom back in.