---
title: "Routing Protocols"
description: "A stale DV cost loops; a link-state map drops"
source: https://vizlearn.app/cs/networks/routing-protocols
access: premium (interactive player requires Lifetime; this doc is free)
---

# Routing Protocols

Routing protocols fill a next-hop table on routers that cannot see the whole graph. Distance vector exchanges one number — I can reach C at cost X — and a stale number can loop two neighbors. Link state floods the map, then each router runs Dijkstra; after this three-node map loses an edge, the lookup drops.

Think of two ways a town learns the road to the bakery. In the first, each person tells the next door "I can get bread in N minutes" and people trust the number. When the bakery burns down, someone is still quoting an old N, and two neighbors send each other walking in a circle. In the second, someone photocopies the street map. When a street is closed the map loses that line, and you stay home. The packet does not carry the map. It only reads the next street sign.

Dijkstra and Bellman-Ford already compute shortest paths when one machine holds the whole graph. This lesson is the distributed conversation that *gives* each router that input — and the way a stale number turns into a loop.

## How routing protocols work

A forwarding table has one job: given dest C, name the neighbor to hand the packet to. Routing fills that table. Forwarding only reads it. The data card in this lesson says `dest C`. It never lists A—B—C.

**Distance vector** is distributed Bellman-Ford. Neighbor `nbr` claims dest at `nbr_cost`. You compute `cand = nbr_cost + link_cost`. You write the new row if `cand` is better, *or* if `nbr` is already your next-hop — even when the cost went up. That second arm is load-bearing. Without it a next-hop that got worse would be ignored, and you would keep sending into a neighbor who is now sending back at you, at a frozen cost. With it, the two of you count toward RIP infinity (16) until the route dies.

RIP added split horizon and poison reverse to stop the two-cycle version of this. A triangle still counts. This lesson animates the naive algorithm. Split horizon is not shown; the quiz asks what it would change on this line.

**Link state** (OSPF-flavoured) does not exchange costs to C. Each router originates an LSA — "here are my live links" — and floods it. The LSDB is the map. When the map is complete, `table = dijkstra(lsdb, dest)`. Same map, same tables. When a link dies, the new LSA deletes that edge. Dijkstra on a map with no path writes `∞`. The next lookup is a drop, not a loop.

That drop is the result of *this* experiment: three routers on a line, after the new LSA is installed. It is not a claim that link-state never loops. Real OSPF routers can hold different LSDBs for a while; this canvas shows one shared map.

The two protocols can agree on a quiet line. They disagree the moment the last hop to dest dies. The default run is that disagreement, not the quiet line.

## Routing protocols step by step

The default run is **Distance Vector** on **Contrast**: line `A —1— B —1— C`, dest = C. Empty tables, then a delivered packet, then B—C dies. Distance vector loops. Link state drops. Both endings stay on screen.

1. **A has a packet for C and no next-hop.** A's and B's rows are `∞`. C holds `0 / local`. A teal `dest C` card sits on A, waiting. The header's running cost is hidden — A has nothing honest to print.
2. **C advertises `C: 0` to B.** The amber card carries a number, not a path.
3. **B writes `1 via C`.** `cand = 0 + 1 = 1 < ∞`.
4. **B advertises `C: 1` to A.** Triggered: B's row just changed.
5. **A writes `2 via B`.** `cand = 1 + 1 = 2`. The header opens: `A = 2 via B`.
6. **Tables are stable.** Next-hops are A → B → C. (The isolated Converge run also shows A advertising `C: 2` back to B, which B ignores. Contrast skips that bounce.)
7. **A data packet appears at A.** Teal card: `dest C`. A's next is B.
8. **A forwards to B.** `forward` reads next and nothing else.
9. **B forwards to C.**
10. **C is dest → deliver.** The packet never carried A—B—C.
11. **B—C dies.** A red X lands on the last hop to dest.
12. **B writes `∞`.** Its next-hop neighbor is gone. `on_link_down` schedules `advertise`.
13. **Wire order.** Two cards park before either moves: `1 A:C2` is already on the wire; `2 B: pending` is queued behind it. B's update reads B's table only when it sends. This race is the whole failure.
14. **A's periodic `C: 2` arrives at B first.** The stale card wins.
15. **B writes `3 via A`.** `cand = 2 + 1 = 3 < ∞`. A's 2 still uses B as next-hop.
16. **B advertises `C: 3` to A.**
17. **A writes `4 via B`.** `nbr == route.next`, so A accepts the worse cost.
18. **A new packet appears at A.** A's next is still B — and B's next is now A.
19. **A forwards to B.**
20. **B forwards to A.** That reverse hop *is* the loop.
21. **The count climbs.** The inspector shows `3/4 → 5/6 → … → 15/16` in one beat. Tables snap to `B = 15 via A` and `A = ∞`. RIP caps the climb at 16. We do not play sixteen exchanges. The loop already happened at 3/4.
22. **Handoff.** The left still pins the DV loop. The tables now follow the link-state map. Same dest C, same dead B—C.
23. **B floods `LSA B {A:1}`.** The card names neighbors, not a cost to C.
24. **B—C leaves the LSDB.** One edge left. There is no path to C on this line.
25. **Dijkstra writes `∞` at A and B.** Nobody counted.
26. **A data packet appears at A.** A's next is None.
27. **next is None → drop.** Same dest C the DV half looped.
28. **Two endings stay.** DV: `A=4 via B`, `B=3 via A`, packet looping. LS: both `∞`, packet dropped at A.

Switch **Protocol** to Link State under Contrast and the same line leads with the map: two LSAs, one Dijkstra, a delivered packet, then the drop, with the DV loop pinned as a still. Isolated **Converge** and **Link down** replay one machine without the other ending.

## When routing protocols matter

You will meet this the first time a link flaps and traffic does not die cleanly.

A RIP-style table that still shows a finite metric after the last hop is gone is the DV half of this lesson: someone is advertising a number built from a neighbor who is advertising you back. Check next-hop, not just the cost. Two routers that point at each other will TTL-expire packets while the control plane is still counting.

An OSPF table that goes empty after an LSA withdraw is the LS half. The thing to read is the LSDB / adjacency log, not the data packet. If the map has no path, the lookup should drop. If two routers disagree about the map, you are in the window this lesson does not animate — flooding delay, SPF hold-down, a router that has not installed yet.

The practical split is the same as the canvas: routing writes the table, forwarding only reads it. A forwarding loop with a sane-looking table is a next-hop problem. A drop with an empty table is the map telling the truth.

## Why a number is not a map

People treat "routing" as a synonym for Dijkstra. It is not. Dijkstra needs the graph. A routing protocol is how the graph — or a poor substitute for it — arrives at each router.

A distance-vector advertisement is a *claim about a cost*. It does not say how that cost was built. That is why A's stale `2` can look like a path after B—C is gone: A is reporting a number that still names B as next-hop. B cannot see that.

A link-state advertisement is a *claim about a neighborhood*. Once every neighborhood is in the LSDB, every router is running the same centralized algorithm the Dijkstra lesson already taught you. The flood is the new idea. The compute is a function call.

Forwarding is dumber than both. After either protocol converges, a packet at A does not know it is going A—B—C. It knows `next = B`. That is why the DV loop is possible: two next-hops that point at each other are a perfectly legal pair of lookups.

## Routing protocol edge cases

- **Worse advertisement, happy path.** Isolated Converge shows A's `C: 2` back to B ignored. The `<` guard is not optional decoration.
- **Self-route at dest.** C holds `0 / local` on every beat of every run. An advert never overwrites it.
- **Link-down race.** B does schedule an update. The stale periodic from A is already on the wire and lands first, so when B's update reads the table it sends `3`, not `∞`. The `raceOrder` beat is that queue, before either card moves.
- **Count to 16.** After the loop is visible, the trail `3/4 → … → 15/16` is the rest of the count, compressed. At 16, A's next becomes None. The packet already looped at 4.
- **No remaining path.** There is no A—C backup. After B—C dies, C is unreachable. That is what turns a stale number into a loop instead of "count until the backup wins."
- **Empty next-hop.** LS after the map loses B—C: `forward` hits `nxt is None` and drops.
- **Split horizon, not shown.** If A refused to advertise a route back to B, this 2-cycle could not count. A triangle still would. That is an extension, not a hidden extra scene.

## Common mistakes in routing protocols

- **Treating this as Dijkstra.** Dijkstra assumes one machine already holds every edge. The DV half never builds a map. The LS half builds one, then calls Dijkstra once.
- **Thinking the packet carries the path.** Pause the first `fwdHop`. The card says `dest C`. The reason it goes to B lives in A's table.
- **Dropping the `nbr == route.next` arm.** If A refused a worse update from B, A would stay `2 via B` after B advertised `3`. They would still loop — they just would not count. The arm trades a silent loop for a climbing one.
- **Believing split horizon kills count-to-infinity.** On this line it would. A triangle still counts. The footnote and the quiz say so because the canvas does not.
- **Reading "LS drops, not a loop" as a universal law.** On this three-node line, after the LSA installs, yes. Shared LSDB and simultaneous table writes are a visual simplification. Real link-state can loop while copies of the map disagree.
- **Calling `∞` on the canvas "16".** RIP's infinity is the integer 16 in the code. The table draws `∞` so a cost and a hop count stay the same currency as Dijkstra's unreached glyph.

## A Note on Simplification

This is an explainer, not a router. The distance-vector run is naive Bellman-Ford: no split horizon, no poison reverse, no hold-down, no RIP invalid/flush timers. Infinity is 16. The link-state run is OSPF-flavoured flooding plus one Dijkstra call — no areas, no designated router, no LSA types, no hello/dead intervals. One destination. One shared LSDB on screen; the code still keeps `lsdb[node]` per router, and flooding is what makes the copies equal. C's own LSA is omitted because it adds no new undirected edge. BGP, policy, and longest-prefix match are a different lesson.
