Routing: From a Destination IP to a Next Hop
Every host and every router answers the same question for every packet — "given this destination address, which interface and which neighbour?" — by looking the address up in a routing table and taking the most specific match.
The problem
93.184.216.34. It has a Wi-Fi radio, an Ethernet port and a VPN tunnel. Which one does the packet leave through, to which machine — and how does a backbone router with a million routes answer the same question for tens of millions of packets a second?The question every hop asks
IP delivery is hop by hop. No machine knows the whole path to a destination; each one knows only where to hand the packet next. A host, a home router, an ISP core router and a data-centre switch all run the same procedure: take the destination IP out of the header, look it up in a routing table, and get back two things — the interface to send on and the next hop, the neighbour’s IP on that interface (or "deliver directly").
The distinction between directly connected and via a gateway is the whole trick of IP. If the destination is on a network attached to one of my interfaces, I resolve its link-layer address with ARP and Neighbor Discovery: From an IP to a Local MAC and put the frame on the wire myself. If it is not, I do not try to reach it; I hand the packet to a router that is on my network, with the router’s MAC address in the frame but the *final* destination’s IP still in the IP header. The IP header does not change at each hop (apart from the TTL decrement); the frame around it is rebuilt every hop — see Encapsulation: Data, Segment, Packet, Frame and Switches vs Routers.
A default route is the entry that matches everything: 0.0.0.0/0 for IPv4, ::/0 for IPv6. A laptop typically has one default route (the home router) and one or two connected networks; a core router may have no default at all, because it is expected to know a specific route for every reachable prefix.
- Application calls send()destination 93.184.216.34, port 443↓
- IP layer: routing table lookupwhich prefix matches most specifically?↓
- Directly connected?yes → resolve the destination MAC; no → resolve the gateway MAC↓
- Frame out of the chosen interfacewlan0 / eth0 / tun0↓
- Next router repeats the lookupwith its own table, its own interfaces
Longest-prefix match
A routing table is a list of prefixes, each written as a network address and a length: 10.0.0.0/8, 10.10.0.0/16, 0.0.0.0/0. A destination "matches" a prefix when its first *length* bits equal the prefix’s. Because prefixes nest, one destination frequently matches several entries — 10.10.42.7 matches all three above. The rule that resolves this is longest-prefix match (LPM): the entry with the most matching bits wins, because it is the most specific statement anyone made about that address.
The default route has length 0, so it matches every address and loses to everything else. That is exactly what you want: it is the route of last resort. Metric (also called cost, distance or priority depending on the vendor) breaks ties only between entries with the *same* prefix — two default routes, say, one via Wi-Fi with metric 600 and one via Ethernet with metric 100. Metric never overrides prefix length; a more specific route with a terrible metric still wins over a less specific one with a great metric.
The Routing Table and Longest-Prefix Match walks through a concrete lookup bit by bit, and explains why LPM — rather than, say, "first match" or "lowest metric" — is what makes the internet’s routing tables compressible.
- Match = first *n* bits equal, where *n* is the prefix length.
- Most specific (longest) matching prefix wins. Always.
- Metric is a tiebreaker among equal prefixes, never a substitute for specificity.
- No match at all (no default route) → the kernel returns
ENETUNREACH, "Network is unreachable", before anything leaves the machine.
Your own machine has one
Routing is not something only routers do. Every host with an IP stack has a routing table and consults it for every outgoing packet; it is usually just very short. On Linux, ip route prints the main table, and ip route get <addr> shows the exact decision the kernel would make for one destination — the single most useful command when a packet is leaving through the wrong interface.
Read the transcript below top to bottom. The default via line is the gateway route. The proto kernel scope link lines were added automatically when an interface got an address: "this /24 is directly reachable on wlan0, and use src 192.168.1.42 as the source address". The metric 600 on the Wi-Fi routes is NetworkManager’s way of preferring wired over wireless when both are up. The VPN client added a /32 for its own server via the physical gateway (so the tunnel’s own packets do not get routed into the tunnel) and then a pair of /1 routes that together cover the whole address space and — being longer than /0 — beat the default route without deleting it.
- macOS and the BSDs:
netstat -rn; Windows:route print. Same model, different rendering. - Linux actually has several tables and a set of policy rules (
ip rule) that choose among them by source address, mark or interface;ip routeshows only themaintable. Container runtimes and VPNs use this heavily — see Network Namespaces.
$ ip route default via 192.168.1.1 dev eth0 proto dhcp metric 100 default via 192.168.1.1 dev wlan0 proto dhcp metric 600 0.0.0.0/1 via 10.8.0.1 dev tun0 # VPN: covers 0.0.0.0–127.255.255.255 128.0.0.0/1 via 10.8.0.1 dev tun0 # VPN: covers 128.0.0.0–255.255.255.255 10.8.0.0/24 dev tun0 proto kernel scope link src 10.8.0.6 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.42 metric 100 192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.42 metric 600 203.0.113.7 via 192.168.1.1 dev eth0 # VPN server itself: never through the tunnel $ ip route get 93.184.216.34 93.184.216.34 via 10.8.0.1 dev tun0 src 10.8.0.6 uid 1000 $ ip route get 192.168.1.10 192.168.1.10 dev eth0 src 192.168.1.42 uid 1000
Static vs dynamic routing
The entries above were static or kernel-generated: put there by DHCP, by an administrator, or by an interface coming up. A static table is fine for a host with one way out, and for small networks with no redundancy. It is useless the moment there are two paths and one of them fails, because nothing updates it.
Dynamic routing means routers run a protocol that exchanges reachability information and recomputes the table when the topology changes. Inside one organisation the common choices are link-state protocols — OSPF and IS-IS — in which every router learns the full graph of links and costs and runs a shortest-path computation locally. Older or smaller networks use distance-vector protocols like RIP, where each router only learns its neighbours’ distance estimates. Between organisations, the entire internet uses one protocol, BGP, which is a *path-vector* protocol driven by policy more than by distance; Internet Routing: Autonomous Systems and BGP covers it.
The property to care about is convergence time: the interval between a link failing and every affected router agreeing on the new table. During that interval packets loop, black-hole, or take detours. OSPF converges in under a second on a well-tuned network; global BGP can take minutes. Both are why "the site was unreachable for 90 seconds and then fixed itself" is a routing story far more often than a server story.
| Source | Example | Who updates it | Reacts to failure? |
|---|---|---|---|
| Connected | 192.168.1.0/24 dev eth0 | kernel, when the interface gets an address | only if the interface goes down |
| Static / DHCP | default via 192.168.1.1 | administrator or DHCP lease | no |
| Link-state IGP | OSPF, IS-IS inside an AS | every router computes shortest paths from the full graph | yes, sub-second to seconds |
| Distance-vector IGP | RIP | routers exchange distance estimates with neighbours | yes, slowly |
| Path-vector EGP | BGP between ASes | policy-filtered announcements of prefixes with AS paths | yes, seconds to minutes |
Routing is a data-structures problem
Two things you already know from DSA are the literal machinery here. Longest-prefix match over binary strings is a trie problem. The routing table is a binary trie keyed on address bits; a lookup walks from the root following the destination’s bits and remembers the last node that carried a route — that node is the longest matching prefix. Linux’s IPv4 forwarding table is a compressed variant of exactly this structure (an LC-trie, visible at /proc/net/fib_trie); hardware routers use TCAM, which The Routing Table and Longest-Prefix Match explains. A hash table cannot do this job: hashing needs an exact key, and a prefix lookup is an inexact one.
Topology is a graph, and choosing routes is shortest path. OSPF’s SPF computation is Dijkstra over a weighted graph of routers and links. Distance-vector protocols are distributed Bellman–Ford: each node relaxes its estimates from what its neighbours report, and the "count to infinity" problem is what happens when Bellman–Ford runs without a global view. When you implement Dijkstra's Algorithm on a weighted graph, you are re-deriving an interior gateway protocol.
- Trie → longest-prefix lookup on address bits.
- Dijkstra's Algorithm over a Weighted Graph → link-state routing (OSPF, IS-IS).
- Bellman-Ford → distance-vector routing (RIP), and the ancestor of BGP’s path-vector approach.
Key points
- Every hop — hosts included — runs the same lookup: destination IP → routing table → interface + next hop.
- Directly connected destinations get the frame delivered by the sender; everything else is handed to a gateway with the final IP still in the header.
- Longest-prefix match decides among overlapping routes; the default route
0.0.0.0/0is the least specific and therefore the last resort. - Metric breaks ties between routes of the same prefix length only.
- Static tables cannot react to failure; dynamic protocols (OSPF/IS-IS, RIP, BGP) recompute them, and convergence time is the interval of pain.
ip route get <addr>shows the kernel’s decision for one destination; use it before blaming the network.- LPM is a trie; route computation is shortest path over a graph.
Why does this exist?
Mechanisms are answers to constraints. Open each question before reading the answer.
▸Why hop-by-hop instead of a full path in every packet?
A full path would have to be computed by someone who knows the whole internet, and every failure would invalidate every path in flight. Hop-by-hop lets each router keep only local knowledge and recover locally; the cost is that nobody can guarantee the path, which is why paths change and are asymmetric.
▸Why a default route?
A host would otherwise need an entry for every network in the world. "If I have nothing more specific, send it to the router" compresses the table to a handful of lines and pushes the knowledge to where it is actually maintained.
▸Why does a more specific route beat a lower metric?
A more specific prefix is a stronger claim: "I know exactly where this /24 lives" outranks "I have a good general way out". If metric could override that, you could never carve an exception out of an aggregate, and aggregates are what keep the tables small.
How it fails
What the failure looks like from inside real software.
- Two interfaces up with two default routes and the wrong metric: traffic leaves through slow Wi-Fi while Ethernet is docked;
ip route getshowsdev wlan0and nobody looks. - A VPN that installs
0.0.0.0/1and128.0.0.0/1and then crashes without removing them: everything black-holes into a deadtun0, and "restart the machine" fixes it because the routes are not persistent. - No default route inside a container or a fresh cloud VM:
connect()fails instantly withENETUNREACHrather than timing out — a symptom that says "routing", not "firewall". - A static route left behind for a subnet that moved: packets for the new location are sent to the old gateway, which drops or loops them; the failure affects only that subnet, which is the tell.
- Slow convergence after a link failure: 30–90 seconds of loss during which retries succeed intermittently, then everything recovers with no change on the servers.
Follow it through every layer
This lesson is one node of a longer journey. Zoom out, then zoom back in.