Skip to content
Menu

Networking8 min read

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

console
# 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 100

Reading 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.

console
# 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 0

Static Routes

Manually configured routes. Simple, deterministic, no protocol overhead — but they don't adapt to topology changes.

console
# 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
When to Use Static Routes
  • 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)
When Static Routes Fail
  • 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:

bash
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:

  1. Receive Frame

    L2 frame arrives, router checks dest MAC is its own

  2. Extract IP Packet

    Strip L2 header, validate IP header checksum

  3. Check TTL

    If TTL = 0 or 1 --> drop, send ICMP Time Exceeded

  4. Decrement TTL

    TTL = TTL - 1, recompute header checksum

  1. Route Lookup

    Longest prefix match on destination IP in FIB

  2. Determine Next-Hop

    Match found: get next-hop IP + egress interface

  3. ARP / NDP

    Resolve next-hop IP to MAC address (check cache first)

  4. Re-encapsulate

    New L2 frame: src MAC = router, dst MAC = next-hop

  1. 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.

Data Payload (unchanged)
TCP/UDP Header (unchanged)
IP Header: Src=10.0.0.5, Dst=10.2.0.10 (TTL decremented, checksum updated)
Ethernet: Src MAC = Router egress MAC, Dst MAC = Next-hop MAC (NEW at each hop)

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:

bash
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

💻
Host A
10.0.1.10/24
🛰
R1
10.0.1.1 | 10.0.12.1
🛰
R2
10.0.12.2 | 10.0.23.1
🛰
R3
10.0.23.2 | 10.0.3.1
🖥
Server B
10.0.3.50/24

R1's Routing Table

bash
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.2

R2's Routing Table

bash
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 R3

Tracing: Host A (10.0.1.10) --> Server B (10.0.3.50)

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

Solidnines — solidnines.com