IP Routing
How routers make forwarding decisions
What a Routing Table Contains
Every host and router maintains a routing table (also called a FIB — Forwarding Information Base). When a packet arrives, the destination IP is looked up in this table to determine where to send it next.
Routing Table Fields
| Field | Description |
|---|---|
| Destination Network | The network prefix being matched (e.g., 10.0.1.0/24) |
| Netmask / Prefix Length | Defines how many bits of the destination address to match |
| Next-Hop | IP address of the next router to forward to. May be "directly connected" if on the same link. |
| Interface | The outgoing network interface (e.g., eth0, ens5, wlan0) |
| Metric | Cost of the route. Lower is preferred. Used to break ties between routes from the same source (e.g., two OSPF paths). |
| Administrative Distance (AD) | Trustworthiness of the route source. Lower is preferred. Used to break ties between routes from different sources (e.g., OSPF vs BGP). |
| Route Source / Protocol | How the route was learned: connected, static, OSPF, BGP, etc. |
Example: Linux Routing Table
# Standard Linux routing table output
$ ip route show
default via 10.0.0.1 dev eth0 proto dhcp metric 100
10.0.0.0/24 dev eth0 proto kernel scope link src 10.0.0.5 metric 100
10.0.1.0/24 via 10.0.0.254 dev eth0 proto static metric 50
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
# More detailed view
$ ip route show table all | head -20
# IPv6 routing table
$ ip -6 route show
::1 dev lo proto kernel metric 256
2001:db8:1::/64 dev eth0 proto ra metric 100
fe80::/64 dev eth0 proto kernel metric 256
default via fe80::1 dev eth0 proto ra metric 100Reading the Linux Route Output
| Entry | Meaning |
|---|---|
default via 10.0.0.1 dev eth0 |
Default route (0.0.0.0/0). If no more specific route matches, send to 10.0.0.1 via eth0. |
10.0.0.0/24 dev eth0 ... scope link |
Connected route. This host is directly on the 10.0.0.0/24 network. No next-hop needed — just ARP and send. |
10.0.1.0/24 via 10.0.0.254 ... proto static |
Static route. To reach 10.0.1.0/24, forward to 10.0.0.254 (a router on the local network). |
172.17.0.0/16 dev docker0 ... linkdown |
Docker bridge network. Connected route for the docker0 bridge interface. |
10.244.0.0/24 dev cni0 |
K8s pod network. Connected route for the local pod CIDR (created by the CNI plugin). |
Default Gateway (0.0.0.0/0)
The default route matches any destination that no more-specific route covers. It has the shortest prefix length (/0), so it is always the last resort in longest prefix match.
Note
Every host must have a default gateway to communicate with networks beyond its own subnet. Without one, the host can only reach directly connected networks. The default gateway is typically the local router's IP address.
# Set default gateway
$ ip route add default via 10.0.0.1 dev eth0
# Or equivalently
$ ip route add 0.0.0.0/0 via 10.0.0.1 dev eth0
# Check current default route
$ ip route get 8.8.8.8
8.8.8.8 via 10.0.0.1 dev eth0 src 10.0.0.5 uid 0Static Routes
Manually configured routes. Simple, deterministic, no protocol overhead — but they don't adapt to topology changes.
# Add a static route: "to reach 10.1.0.0/16, send via 10.0.0.254"
$ ip route add 10.1.0.0/16 via 10.0.0.254 dev eth0
# Add a route with a specific metric
$ ip route add 10.2.0.0/16 via 10.0.0.253 dev eth0 metric 200
# Delete a static route
$ ip route del 10.1.0.0/16 via 10.0.0.254
# Persistent routes (varies by distro)
# Netplan (Ubuntu): /etc/netplan/*.yaml
# NetworkManager: nmcli connection modify "eth0" +ipv4.routes "10.1.0.0/16 10.0.0.254"
# systemd-networkd: [Route] section in .network files- Small networks with predictable topology
- Stub networks with a single exit point
- Default route to the upstream ISP
- Host routes (/32) for specific overrides
- Container/VM networking (routes to overlay networks)
- Large or complex topologies (hundreds of routes)
- Networks that change frequently
- Redundant paths requiring automatic failover
- When you need loop-free convergence guarantees
- Multi-datacenter connectivity
Longest Prefix Match
The fundamental routing algorithm. When multiple routes match a destination, the route with the longest (most specific) prefix wins. This is how the entire internet works.
Example: Which Route Wins?
Given this routing table:
0.0.0.0/0 via 10.0.0.1 # default route
10.0.0.0/8 via 10.0.0.2 # summary route
10.1.0.0/16 via 10.0.0.3 # more specific
10.1.1.0/24 via 10.0.0.4 # even more specific
10.1.1.128/25 via 10.0.0.5 # most specific| Destination | Matching Routes | Winner (Longest Prefix) | Next-Hop |
|---|---|---|---|
10.1.1.200 |
/0, /8, /16, /24, /25 | /25 (10.1.1.128/25) | 10.0.0.5 |
10.1.1.50 |
/0, /8, /16, /24 | /24 (10.1.1.0/24) | 10.0.0.4 |
10.1.2.5 |
/0, /8, /16 | /16 (10.1.0.0/16) | 10.0.0.3 |
10.5.0.1 |
/0, /8 | /8 (10.0.0.0/8) | 10.0.0.2 |
8.8.8.8 |
/0 | /0 (default) | 10.0.0.1 |
Note
Why this matters: Longest prefix match lets you have broad summary routes (for efficiency) and specific overrides (for exceptions). A /32 host route always wins over any network route. This is how K8s service routing works — kube-proxy installs /32 routes for ClusterIPs that override the broader service CIDR.
Tip
Implementation detail: High-performance routers use tries (prefix trees) or TCAM (Ternary Content-Addressable Memory) for O(1) longest-prefix-match lookups. The Linux kernel uses LC-trie (Level-Compressed trie) in the FIB. eBPF-based datapaths (Cilium) can do LPM lookups using BPF_MAP_TYPE_LPM_TRIE.
Router Forwarding Decision: Step by Step
When a router receives an IP packet, it performs this sequence:
Receive Frame
L2 frame arrives, router checks dest MAC is its own
Extract IP Packet
Strip L2 header, validate IP header checksum
Check TTL
If TTL = 0 or 1 --> drop, send ICMP Time Exceeded
Decrement TTL
TTL = TTL - 1, recompute header checksum
Route Lookup
Longest prefix match on destination IP in FIB
Determine Next-Hop
Match found: get next-hop IP + egress interface
ARP / NDP
Resolve next-hop IP to MAC address (check cache first)
Re-encapsulate
New L2 frame: src MAC = router, dst MAC = next-hop
Forward
Transmit frame out the egress interface
Critical Detail: L2 Headers Change, L3 Headers (Mostly) Don't
At each hop, the source and destination MAC addresses change (the frame is re-encapsulated), but the source and destination IP addresses stay the same throughout the journey (unless NAT is involved). Only TTL and checksum change in the IP header.
Warning
No match? If no route matches (including no default route), the router drops the packet and sends ICMP Destination Unreachable (Type 3, Code 0 — Network Unreachable) back to the source. You'll see this as RTNETLINK answers: Network is unreachable on Linux.
Route Types
| Type | How Installed | Example | Characteristics |
|---|---|---|---|
| Connected | Automatically when you configure an IP on an interface | 10.0.0.0/24 dev eth0 scope link |
No next-hop needed (directly attached network). Highest trust (AD = 0). |
| Static | Manually by admin (ip route add) |
10.1.0.0/16 via 10.0.0.254 |
Simple, deterministic. AD = 1. Doesn't adapt to failures. |
| Dynamic | Learned from routing protocols (OSPF, BGP, etc.) | 10.2.0.0/16 via 10.0.0.253 proto ospf |
Automatically adapts to topology changes. AD varies by protocol. |
Administrative Distance
When multiple routing sources provide a route to the same destination, administrative distance (AD) breaks the tie. It represents the trustworthiness of the route source — lower is better.
| Route Source | AD (Cisco) | AD (Linux equivalent) |
|---|---|---|
| Directly connected | 0 | proto kernel, scope link |
| Static route | 1 | proto static |
| eBGP | 20 | proto bgp |
| OSPF | 110 | proto ospf |
| IS-IS | 115 | proto isis |
| RIP | 120 | proto rip |
| iBGP | 200 | proto bgp |
| Unknown / unreachable | 255 | (route not installed) |
Example: AD in Action
Suppose two routing protocols both know about 10.5.0.0/16:
OSPF says: 10.5.0.0/16 via 10.0.0.2 (AD = 110, metric = 20)
eBGP says: 10.5.0.0/16 via 10.0.0.3 (AD = 20, metric = 0)
Winner: eBGP route (AD 20 < AD 110)
The OSPF route is kept in OSPF's database but not installed in the FIB.Note
AD vs Metric: AD selects between different routing protocols. Metric selects within the same routing protocol (e.g., which of two OSPF paths is shorter). AD is checked first; metric only matters when AD is tied.
Multi-Router Network Example
R1's Routing Table
10.0.1.0/24 dev eth0 (connected)
10.0.12.0/30 dev eth1 (connected)
10.0.23.0/30 via 10.0.12.2 # next hop is R2
10.0.3.0/24 via 10.0.12.2 # next hop is R2
default via 10.0.12.2R2's Routing Table
10.0.12.0/30 dev eth0 (connected)
10.0.23.0/30 dev eth1 (connected)
10.0.1.0/24 via 10.0.12.1 # next hop is R1
10.0.3.0/24 via 10.0.23.2 # next hop is R3Tracing: Host A (10.0.1.10) --> Server B (10.0.3.50)
- Host A: Destination 10.0.3.50 is not on 10.0.1.0/24 (not local). Send to default gateway
10.0.1.1(R1). ARP for R1's MAC, encapsulate, send. - R1: Receives frame (MAC matches). Extracts IP packet. Dest = 10.0.3.50. Lookup: matches
10.0.3.0/24 via 10.0.12.2. Decrement TTL. ARP for R2's MAC on eth1. New frame: src=R1-eth1-MAC, dst=R2-eth0-MAC. Forward. - R2: Receives frame. Dest = 10.0.3.50. Lookup: matches
10.0.3.0/24 via 10.0.23.2. Decrement TTL. ARP for R3's MAC on eth1. New frame. Forward. - R3: Receives frame. Dest = 10.0.3.50. Lookup: matches
10.0.3.0/24 dev eth1(connected). ARP for Server B's MAC directly. New frame. Forward. - Server B: Receives frame (MAC matches). Extracts IP packet. Dest = 10.0.3.50 = its own address. Delivers to the appropriate upper-layer protocol (TCP/UDP).
Tip
K8s routing context: In a K8s cluster, each node has routes for pod CIDRs on other nodes. With Calico (BGP mode), nodes peer via BGP and exchange routes like 10.244.1.0/24 via 192.168.1.11. With Cilium (eBPF), the forwarding is done in the kernel datapath, bypassing iptables — but the same longest-prefix-match logic applies at the FIB level.