01 / ROUTING TABLE

Define your routes.

Destination CIDR Next-Hop Interface Metric
Preset Scenarios
02 / LOOKUP

Look up a destination.

Try these
03 / HOW IT WORKS

The longest-prefix-match algorithm.

When a router receives a packet, it walks every route in the routing table and asks: "Does this destination IP fall within this route's network?" If multiple routes match, the router picks the one with the longest prefix length — the most specific match wins.

If two routes have the same prefix length, the router uses other tiebreakers: administrative distance (route source preference), then metric. Real Cisco / Juniper boxes have ~10 levels of tiebreakers; this simulator uses the simplest model.

Worked example

Say the table has these three routes:

0.0.0.0/0     → 192.168.1.1     (default route, "everything else")
10.0.0.0/8    → 10.0.0.1        (any 10.x.x.x)
10.5.0.0/16   → 10.5.0.1        (more specific - 10.5.x.x only)

For destination 10.5.10.55:

Three matches, but 10.5.0.0/16 wins because its prefix is longest. Traffic is sent to 10.5.0.1.

CODE EXAMPLES

Longest-prefix-match in code

Routers select the most specific matching route, not the first. The snippets below implement longest-prefix-match against a small routing table and resolve which route wins for the destination 10.0.5.42.

import ipaddress

routes = [
    ("0.0.0.0/0",   "192.0.2.1"),
    ("10.0.0.0/8",  "10.1.1.1"),
    ("10.0.0.0/16", "10.1.2.1"),
    ("192.168.0.0/16", "192.168.1.1"),
]

dest = ipaddress.IPv4Address("10.0.5.42")
matches = []
for cidr, nh in routes:
    net = ipaddress.IPv4Network(cidr)
    if dest in net:
        matches.append((net, nh))

# Longest prefix wins
matches.sort(key=lambda m: m[0].prefixlen, reverse=True)
winner = matches[0]

print(f"Destination: {dest}")
print("Routes considered:")
for net, nh in sorted(matches, key=lambda m: m[0].prefixlen):
    print(f"  {str(net)+'':<12}-> via {nh:<12} (match)")
print(f"Winner: {winner[0]} -> via {winner[1]}")
package main

import (
	"fmt"
	"net"
	"sort"
)

type route struct {
	cidr    string
	nh      string
	ipnet   *net.IPNet
}

func main() {
	specs := []struct{ cidr, nh string }{
		{"0.0.0.0/0", "192.0.2.1"},
		{"10.0.0.0/8", "10.1.1.1"},
		{"10.0.0.0/16", "10.1.2.1"},
		{"192.168.0.0/16", "192.168.1.1"},
	}
	dest := net.ParseIP("10.0.5.42").To4()

	matches := []route{}
	for _, s := range specs {
		_, n, _ := net.ParseCIDR(s.cidr)
		if n.Contains(dest) {
			matches = append(matches, route{s.cidr, s.nh, n})
		}
	}

	sortAsc := make([]route, len(matches))
	copy(sortAsc, matches)
	sort.Slice(sortAsc, func(i, j int) bool {
		pi, _ := sortAsc[i].ipnet.Mask.Size()
		pj, _ := sortAsc[j].ipnet.Mask.Size()
		return pi < pj
	})

	winner := matches[0]
	for _, m := range matches[1:] {
		pw, _ := winner.ipnet.Mask.Size()
		pm, _ := m.ipnet.Mask.Size()
		if pm > pw {
			winner = m
		}
	}

	fmt.Printf("Destination: %s\n", dest)
	fmt.Println("Routes considered:")
	for _, m := range sortAsc {
		fmt.Printf("  %-12s-> via %-12s (match)\n", m.cidr, m.nh)
	}
	fmt.Printf("Winner: %s -> via %s\n", winner.cidr, winner.nh)
}
function ip2int(s) {
  return s.split('.').reduce((a, o) => (a << 8) + +o, 0) >>> 0;
}
function inNet(ip, cidr) {
  const [base, plen] = cidr.split('/');
  const p = +plen;
  const mask = p === 0 ? 0 : (0xffffffff << (32 - p)) >>> 0;
  return (ip2int(ip) & mask) === (ip2int(base) & mask);
}
function prefixOf(cidr) { return +cidr.split('/')[1]; }

const routes = [
  ['0.0.0.0/0',    '192.0.2.1'],
  ['10.0.0.0/8',   '10.1.1.1'],
  ['10.0.0.0/16',  '10.1.2.1'],
  ['192.168.0.0/16', '192.168.1.1'],
];
const dest = '10.0.5.42';

const matches = routes.filter(([c]) => inNet(dest, c));
const asc = [...matches].sort((a, b) => prefixOf(a[0]) - prefixOf(b[0]));
const winner = matches.reduce((w, m) =>
  prefixOf(m[0]) > prefixOf(w[0]) ? m : w);

console.log(`Destination: ${dest}`);
console.log('Routes considered:');
for (const [c, nh] of asc) {
  console.log(`  ${c.padEnd(12)}-> via ${nh.padEnd(12)} (match)`);
}
console.log(`Winner: ${winner[0]} -> via ${winner[1]}`);
#!/usr/bin/env bash
# Longest-prefix-match against a small routing table.

ip2int() { local IFS=.; local -a o=($1)
  echo $(( (o[0]<<24)|(o[1]<<16)|(o[2]<<8)|o[3] )); }

in_net() {
  local ip=$1 cidr=$2
  local base=${cidr%/*} plen=${cidr#*/}
  local mask=0
  if (( plen > 0 )); then mask=$(( (0xFFFFFFFF << (32 - plen)) & 0xFFFFFFFF )); fi
  local i b
  i=$(ip2int "$ip"); b=$(ip2int "$base")
  [[ $(( i & mask )) -eq $(( b & mask )) ]]
}

DEST="10.0.5.42"
declare -a ROUTES=(
  "0.0.0.0/0|192.0.2.1"
  "10.0.0.0/8|10.1.1.1"
  "10.0.0.0/16|10.1.2.1"
  "192.168.0.0/16|192.168.1.1"
)

declare -a MATCHES=()
for r in "${ROUTES[@]}"; do
  cidr=${r%|*}
  if in_net "$DEST" "$cidr"; then MATCHES+=("$r"); fi
done

# Pick winner = longest prefix
winner_pfx=-1
winner=""
for r in "${MATCHES[@]}"; do
  cidr=${r%|*}; pfx=${cidr#*/}
  if (( pfx > winner_pfx )); then winner_pfx=$pfx; winner="$r"; fi
done

# Sort ascending for display
IFS=$'\n' SORTED=($(for r in "${MATCHES[@]}"; do
  cidr=${r%|*}; printf "%02d|%s\n" "${cidr#*/}" "$r"
done | sort | cut -d'|' -f2-))
unset IFS

echo "Destination: $DEST"
echo "Routes considered:"
for r in "${SORTED[@]}"; do
  cidr=${r%|*}; nh=${r#*|}
  printf "  %-12s-> via %-12s (match)\n" "$cidr" "$nh"
done
winner_cidr=${winner%|*}; winner_nh=${winner#*|}
printf "Winner: %s -> via %s\n" "$winner_cidr" "$winner_nh"
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Lpm {
    static long ip2int(String s) {
        String[] p = s.split("\\.");
        return ((Long.parseLong(p[0]) << 24)
              | (Long.parseLong(p[1]) << 16)
              | (Long.parseLong(p[2]) <<  8)
              |  Long.parseLong(p[3])) & 0xFFFFFFFFL;
    }
    static boolean inNet(String ip, String cidr) {
        String[] parts = cidr.split("/");
        int plen = Integer.parseInt(parts[1]);
        long mask = (plen == 0) ? 0L
                  : (0xFFFFFFFFL << (32 - plen)) & 0xFFFFFFFFL;
        return (ip2int(ip) & mask) == (ip2int(parts[0]) & mask);
    }
    static int prefixOf(String cidr) {
        return Integer.parseInt(cidr.split("/")[1]);
    }
    public static void main(String[] args) {
        String[][] routes = {
            {"0.0.0.0/0",       "192.0.2.1"},
            {"10.0.0.0/8",      "10.1.1.1"},
            {"10.0.0.0/16",     "10.1.2.1"},
            {"192.168.0.0/16",  "192.168.1.1"},
        };
        String dest = "10.0.5.42";

        List<String[]> matches = new ArrayList<>();
        for (String[] r : routes) if (inNet(dest, r[0])) matches.add(r);

        List<String[]> asc = new ArrayList<>(matches);
        asc.sort(Comparator.comparingInt(r -> prefixOf(r[0])));
        String[] winner = matches.get(0);
        for (String[] m : matches) if (prefixOf(m[0]) > prefixOf(winner[0])) winner = m;

        System.out.printf("Destination: %s%n", dest);
        System.out.println("Routes considered:");
        for (String[] m : asc) {
            System.out.printf("  %-12s-> via %-12s (match)%n", m[0], m[1]);
        }
        System.out.printf("Winner: %s -> via %s%n", winner[0], winner[1]);
    }
}
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

static uint32_t ip2int(const char *s) {
    unsigned a, b, c, d;
    sscanf(s, "%u.%u.%u.%u", &a, &b, &c, &d);
    return (a << 24) | (b << 16) | (c << 8) | d;
}

typedef struct { const char *cidr; const char *nh; int prefix; } route_t;

static int parse_cidr(const char *cidr, uint32_t *base, int *plen) {
    char ip[20]; int p;
    if (sscanf(cidr, "%19[^/]/%d", ip, &p) != 2) return 0;
    *base = ip2int(ip);
    *plen = p;
    return 1;
}

static int in_net(uint32_t ip, uint32_t base, int plen) {
    uint32_t mask = (plen == 0) ? 0 : (uint32_t)(0xFFFFFFFFu << (32 - plen));
    return (ip & mask) == (base & mask);
}

static int cmp_asc(const void *a, const void *b) {
    return ((route_t*)a)->prefix - ((route_t*)b)->prefix;
}

int main(void) {
    route_t routes[] = {
        {"0.0.0.0/0",       "192.0.2.1",   0},
        {"10.0.0.0/8",      "10.1.1.1",    0},
        {"10.0.0.0/16",     "10.1.2.1",    0},
        {"192.168.0.0/16",  "192.168.1.1", 0},
    };
    int n = sizeof(routes) / sizeof(routes[0]);
    const char *dest = "10.0.5.42";
    uint32_t destInt = ip2int(dest);

    route_t matches[8]; int mc = 0;
    for (int i = 0; i < n; i++) {
        uint32_t b; int p;
        if (!parse_cidr(routes[i].cidr, &b, &p)) continue;
        routes[i].prefix = p;
        if (in_net(destInt, b, p)) matches[mc++] = routes[i];
    }

    route_t asc[8];
    memcpy(asc, matches, mc * sizeof(route_t));
    qsort(asc, mc, sizeof(route_t), cmp_asc);

    route_t winner = matches[0];
    for (int i = 1; i < mc; i++)
        if (matches[i].prefix > winner.prefix) winner = matches[i];

    printf("Destination: %s\n", dest);
    printf("Routes considered:\n");
    for (int i = 0; i < mc; i++) {
        printf("  %-12s-> via %-12s (match)\n", asc[i].cidr, asc[i].nh);
    }
    printf("Winner: %s -> via %s\n", winner.cidr, winner.nh);
    return 0;
}
Destination: 10.0.5.42
Routes considered:
  0.0.0.0/0   -> via 192.0.2.1    (match)
  10.0.0.0/8  -> via 10.1.1.1     (match)
  10.0.0.0/16 -> via 10.1.2.1     (match)
Winner: 10.0.0.0/16 -> via 10.1.2.1
REFERENCES

Common prefix references