The Router: Forwarding vs Routing

A packet leaves your laptop bound for a server on the other side of the planet. Nothing on your laptop knows the whole route — it simply hands the packet to the first router it can reach and trusts the rest of the network to sort it out. That packet will now pass through perhaps fifteen or twenty routers, and at each one, in a few microseconds, a decision is made: which of my outgoing links moves this packet one hop closer to its destination? Get that decision right, billions of times a second, and the Internet works. Get it wrong and packets loop, pile up, or vanish.

This is the job of the network layer: host-to-host delivery of datagrams. The transport layer worries about which program gets the bytes; the network layer's single obsession is getting a packet from this machine to that machine across a mesh of intermediate routers. And the deepest idea on this page is that a router does two utterly different jobs that beginners constantly merge into one — forwarding and routing. Separating them cleanly is the key that unlocks everything else in the network layer.

Two speeds, two questions: the data plane and the control plane

Imagine a giant postal sorting office. On the floor, conveyor belts fling parcels into chutes: a worker glances at each address label, reads a chart pinned to the wall, and drops the parcel down the matching chute. That happens thousands of times a minute — fast, local, mechanical. Meanwhile, up in an office, planners spend the afternoon redrawing that wall chart — deciding, given today's road closures and truck schedules, which chute should feed which onward depot. Two activities, wildly different timescales, and they meet only at the chart on the wall.

A router is exactly this. Its two jobs are:

They meet at exactly one artefact: the forwarding table. Routing writes it; forwarding reads it. This is the single most important sentence on the page — say it twice.

Inside the box: the anatomy of a router

Open a real router and you find four functional pieces. Bytes flow left to right: in through an input port, across a switching fabric, out through an output port — with queues buffering the crush wherever packets arrive faster than they can leave.

The lookup itself: longest-prefix match

Now zoom into that microsecond-long lookup. A forwarding table does not hold one row per possible destination address — there are over four billion IPv4 addresses; no table could list them. Instead it holds rows for address prefixes (blocks of addresses, CIDR-style), each pointing at an output link:

PrefixOutput link
200.23.16.0/200
200.23.16.0/231
200.23.24.0/242
(default) 0.0.0.0/03

A destination address may match several prefixes at once. The rule that breaks the tie is longest-prefix match: choose the matching prefix with the most leading bits specified — the most specific route. An address in 200.23.16.0/23 also sits inside 200.23.16.0/20, but the /23 row is more specific, so it wins. The all-zeros /0 default route matches everything and therefore only wins when nothing else does — it is the fallback. Longest-prefix match is what makes hierarchical addressing and route aggregation possible: a distant router can carry one coarse /20 entry for a whole region while a nearby router carries the fine-grained exceptions.

The demo below is a forwarding-table lookup. It stores each prefix as a (network, mask-length) pair, tests a destination against every row by masking off the host bits, and keeps the match with the longest prefix. This is precisely the decision a router's input port makes, millions of times a second — here in a few lines of pure bit arithmetic.

// A tiny forwarding table: [prefix base, prefix length, output link]. // We store the base as a 32-bit integer for clean bit masking. function ip(a: number, b: number, c: number, d: number): number { // >>> 0 forces an UNSIGNED 32-bit result (JS bit ops are signed). return ((a << 24) | (b << 16) | (c << 8) | d) >>> 0; } function show(n: number): string { return [n >>> 24, (n >>> 16) & 255, (n >>> 8) & 255, n & 255].join("."); } const table = [ { base: ip(200, 23, 16, 0), len: 20, link: 0 }, { base: ip(200, 23, 16, 0), len: 23, link: 1 }, { base: ip(200, 23, 24, 0), len: 24, link: 2 }, { base: ip(0, 0, 0, 0), len: 0, link: 3 }, // default route ]; // Longest-prefix match: keep the matching row with the MOST prefix bits. function forward(dest: number): { link: number; len: number } { let best = { link: -1, len: -1 }; for (const row of table) { // Build a mask with `len` leading 1s. len === 0 -> mask 0 (matches all). const mask = row.len === 0 ? 0 : (0xffffffff << (32 - row.len)) >>> 0; if (((dest ^ row.base) & mask) === 0 && row.len > best.len) { best = { link: row.link, len: row.len }; } } return best; } for (const d of [ip(200, 23, 16, 8), ip(200, 23, 24, 5), ip(200, 23, 20, 1), ip(8, 8, 8, 8)]) { const r = forward(d); console.log(`${show(d).padEnd(15)} -> link ${r.link} (matched /${r.len})`); }

Read the output carefully: 200.23.16.8 matches both the /20 and the /23, and the /23 wins by being longer; 200.23.20.1 falls outside the /23 so the coarser /20 catches it; and 8.8.8.8 matches nothing specific and drops through to the default route. That is longest-prefix match, live.

What the router reads: the IP datagram header

The address the router matches on lives in the packet's IP header — a compact block of fields prepended to every datagram. You do not need every bit, but a handful matter enormously for how forwarding behaves:

It looks stingy — surely you'd want to protect the payload too? But the network layer deliberately checksums only its own header, for two reasons. First, speed: recomputing a checksum over a 1500-byte payload at every one of twenty hops would be enormously more work than over a 20-byte header, and the header is the only part the router actually changes (via TTL). Second, layering: the payload's integrity is the transport layer's business — TCP and UDP carry their own end-to-end checksums that cover the data, checked once at the destination rather than at every router. It is a neat example of each layer minding exactly its own correctness and trusting the others to mind theirs. IPv6 takes this logic to its conclusion and drops the header checksum entirely, leaning on the link and transport layers to catch corruption.

This is the misconception that muddles half of all network-layer exams. People say "the router routes the packet" and mean the everyday act of sending a packet onward — but that everyday act is forwarding, not routing. Nail the distinction:

A one-line test you can apply: "Is a packet in flight being moved?" If yes, that's forwarding. "Are routers exchanging information to figure out the paths?" If yes, that's routing. One is the parcel worker reading the chart; the other is the planners redrawing it. Confuse them and nothing else in this chapter will fit together.