OSPF Explained: Flood the Map, Run Dijkstra, and Reroute in Seconds
This post is part of Protocol Lab, a free, hands-on series for learning networking protocols by building and breaking them in a container lab. All the lab material — topologies, configs, and scripts — lives in the repo: github.com/pathvector-studio/protocol-lab.
Labs 01–04 used BGP, the path-vector protocol that carries reachability between autonomous systems. This lab introduces the other half of routing: an interior gateway protocol. OSPF is link-state — every router floods a description of its links, all routers build the same map, and each runs Dijkstra (SPF) to compute shortest paths by cost.
Reading guide: rfc-notes/ospf.md
Prerequisite: BGP Lab 01: Announcing a Prefix over eBGP
Expected time: 45–60 minutes.
The Goal
Three routers form a triangle in OSPF area 0, with a target network behind r3:
r1forms Full adjacencies withr2andr3(link-state databases synced),- SPF picks the direct r1–r3 link (cost 20) over the two-hop r1–r2–r3 path (cost 30) to reach the target,
- when the direct link fails, OSPF floods the change, every router re-runs SPF, and
r1reconverges onto the path viar2(cost 30) — the target stays reachable at the same address.
By the end, you should be able to explain this table:
| State | r1's route to the target | cost |
|---|---|---|
| all links up | via r3 direct (10.0.13.2) |
20 |
| direct r1–r3 down | via r2 (10.0.12.2) |
30 |
What You Will Learn
- How OSPF routers form adjacencies (Hello → Full) and why /30 links use the point-to-point network type.
- How each router floods LSAs so everyone holds the same link-state database.
- How SPF (Dijkstra) picks paths by cost, not hop count.
- How a link failure triggers reflooding + reconvergence.
- How OSPF (link-state IGP) differs from BGP (path-vector EGP).
This lab does not cover:
- Multiple OSPF areas, ABRs, and route summarization.
- DR/BDR election on broadcast segments in depth.
- OSPFv3 (IPv6), authentication, or route redistribution.
Where to Read in the RFCs
| Reference | What to focus on |
|---|---|
| RFC 2328 §7, §10 | Hello, the neighbor state machine (→ Full), network types |
| RFC 2328 §12, §16 | LSAs/LSDB and SPF (Dijkstra) |
| RFC 2328 §1.2 | Areas (this lab uses a single area 0) |
| RFC 5737 / RFC 1918 | Confirming the addresses used here are local/documentation-only |
The Big Picture
r1, r2, and r3 form a triangle. The target network (10.0.30.0/24) sits behind r3.
r1
(eth1) / \ (eth2)
cost 10 / \ cost 10
r2 ------ r3 --- target 10.0.30.1
cost 10 (r3 advertises 10.0.30.0/24 into OSPF)
Every link has cost 10. From r1 to the target, the direct r1–r3 path (10 + r3–target 10 = 20) is cheaper than r1–r2–r3 (10 + 10 + 10 = 30).
flowchart TD
R1["r1"] -->|"cost 10"| R3["r3"]
R1 -->|"cost 10"| R2["r2"]
R2 -->|"cost 10"| R3
R3 -->|"advertises 10.0.30.0/24"| T["target 10.0.30.1"]
R1 -.->|"SPF best: via r3 direct, cost 20"| R3
R1 -.->|"standby: via r2, cost 30"| R2
10.0.0.0/8 is a local, closed range.
Note: Everything here uses local/documentation address space (RFC 1918 / RFC 5737), so nothing in this lab touches the real internet.
What You Need
Recommended environment:
- Linux / WSL2 / a Linux VM
- Docker
- containerlab
Images used:
frrouting/frr:latest— OSPF onr1,r2, andr3.nicolaka/netshoot:latest— the target host.
No additional images are required.
Running the Lab
The quick path, which deploys, verifies, and tears down for you:
./scripts/labctl.sh run ospf-34
Or step through it manually:
1. Move into the working directory
cd protocol-lab/examples/ospf-34
2. Deploy
sudo containerlab deploy -t ospf-34.clab.yml
Each router runs OSPF in area 0. The /30 links between routers are set to ip ospf network point-to-point, so adjacencies go straight to Full with no DR election.
3. Check the adjacencies and the map
docker exec clab-ospf-34-r1 vtysh -c "show ip ospf neighbor" # r2 and r3 are Full
docker exec clab-ospf-34-r1 vtysh -c "show ip ospf database" # 3 Router LSAs (the same map)
4. See the route SPF chose (direct link, cost 20)
docker exec clab-ospf-34-r1 vtysh -c "show ip route ospf"
docker exec clab-ospf-34-r1 ip route get 10.0.30.1 # via 10.0.13.2 (r3 direct)
docker exec clab-ospf-34-r1 ping -c2 10.0.30.1
You should see O>* 10.0.30.0/24 [110/20] via 10.0.13.2.
5. Drop the direct link and watch reconvergence
docker exec clab-ospf-34-r1 ip link set eth2 down
sleep 5
docker exec clab-ospf-34-r1 ip route get 10.0.30.1 # now via 10.0.12.2 (r2)
docker exec clab-ospf-34-r1 vtysh -c "show ip route ospf" # [110/30]
docker exec clab-ospf-34-r1 ping -c2 10.0.30.1 # still reachable
6. Restore it
docker exec clab-ospf-34-r1 ip link set eth2 up
Expected Output
show ip ospf neighbor:r2(2.2.2.2) andr3(3.3.3.3) are Full.show ip ospf database: three Router LSAs (one per router, identical everywhere).- Before the failure:
O>* 10.0.30.0/24 [110/20] via 10.0.13.2(direct link), ping reaches the target. - After the failure:
[110/30] via 10.0.12.2(via r2), ping still reaches the target.
Why It Works
OSPF is a link-state IGP: "flood the map, and everyone computes the same shortest-path tree."
- Adjacencies. Each router discovers its neighbors with Hello packets and walks the state machine Down → … → Full, synchronizing LSDBs along the way. Full means "an adjacency where the map is fully shared." Making the /30 router-to-router links point-to-point type gets them to Full immediately with no DR election (with Ethernet's default broadcast type, there is a DR/BDR election, and two DROther routers tend to stall at 2-Way).
- Distributing the map. Each router writes its own links (neighbor + cost) into a Router LSA and floods it across the area. Everyone ends up with an identical LSDB. This is the heart of link-state: where BGP distributes "paths to destinations" (
AS_PATH), OSPF distributes the map itself. - SPF. Each router feeds the LSDB into Dijkstra and builds a shortest-path tree rooted at itself. Distance is the sum of costs, not hop count. From
r1, the direct path to the target is 20 < 30 via r2, so the direct link wins. - Reconvergence. When the direct link fails, the adjacency drops and the affected routers flood updated LSAs. Every router updates its LSDB and re-runs SPF, and
r1switches to the path viar2(cost 30). The destination address never changes and reachability is preserved.
The key insight: distribute link state (the map), let every router compute shortest paths with Dijkstra, and recompute whenever something changes. It's the intra-domain IGP counterpart to BGP (path-vector, inter-AS).
Common Pitfalls
- Thinking OSPF picks by hop count. It picks by cost. Slower links get higher cost.
- Expecting instant Full on Ethernet. Broadcast-type interfaces run a DR election, and DROther pairs stop at 2-Way. Make router-to-router /30 links point-to-point type (as this lab does).
- Thinking OSPF distributes routes. It distributes link state. Each router computes its own routes with SPF.
- Assuming a cost tie means one path. Equal costs can become ECMP (see Lab 32).
- Confusing it with BGP. BGP is path-vector and inter-AS; OSPF is link-state and intra-domain. Different jobs.
- Expecting instant convergence. Hello/dead intervals and SPF recomputation take time (a few seconds in this environment).
Cleanup
sudo containerlab destroy -t ospf-34.clab.yml --cleanup
If you used labctl.sh run ospf-34, the script runs destroy for you at the end.
Check Your Understanding
- What is an OSPF adjacency? What does the Full state mean?
- Why make the /30 router-to-router links point-to-point type? What happens with the broadcast type?
- Does OSPF distribute routes themselves, or the map? Where does SPF run?
- Why does
r1choose the direct link (cost 20) to the target? How can you tell it isn't hop count? - When the direct link fails, what happens before
r1switches to the path viar2(flood → SPF)? - Contrast OSPF (IGP, link-state) with BGP (EGP, path-vector) in terms of what each distributes and where route computation happens.
References
- RFC 2328: OSPF Version 2
- RFC 5340: OSPF for IPv6 (OSPFv3)
- FRRouting OSPF documentation
- RFC 5737: IPv4 Address Blocks Reserved for Documentation
Verified Run Log (2026-07-07)
This lab has been confirmed reproducible on real hardware.
Environment:
- Ubuntu 26.04 LTS (kernel 7.0.0-27-generic, x86_64)
- Docker 29.1.3
- containerlab 0.77.0
r1/r2/r3:frrouting/frr:latest(ospfd)- target:
nicolaka/netshoot:latest
Running PATH="/tmp/pl-shim:$PATH" ./scripts/labctl.sh run ospf-34 performed deploy → verify → destroy, and verification.json returned "status": "verified".
Full adjacencies and an identical LSDB
Neighbor ID Pri State Address Interface
2.2.2.2 1 Full/- 10.0.12.2 eth1:10.0.12.1
3.3.3.3 1 Full/- 10.0.13.2 eth2:10.0.13.1
r1 holds Full adjacencies with r2 (2.2.2.2) and r3 (3.3.3.3). show ip ospf database lists three Router LSAs (one per router) — everyone has the same map.
SPF picks the cheapest cost (the direct link)
O>* 10.0.30.0/24 [110/20] via 10.0.13.2, eth2 # direct r1-r3, cost 20
# kernel: 10.0.30.1 via 10.0.13.2 dev eth2
The OSPF route to the target has cost 20 (direct r1–r3). It's cheaper than the two-hop r1–r2–r3 path (cost 30), so the direct link is chosen. Ping reached the target.
Direct link failure → reconvergence (cost 30, via r2)
Taking r1's eth2 (the direct link) down, within about a second:
O>* 10.0.30.0/24 [110/30] via 10.0.12.2, eth1 # via r2, cost 30
# kernel: 10.0.30.1 via 10.0.12.2 dev eth1
OSPF floods the change → every router re-runs SPF → r1 reconverges onto the path via r2 (cost 30). The target remained reachable at the same 10.0.30.1. Selection by cost, not hop count, and on failure the map is updated and SPF re-run — link-state behavior in a nutshell.
Cleanup
containerlab destroy -t ospf-34.clab.yml --cleanup
That's OSPF: flood the map, let every router run Dijkstra over the same link-state database, and let ordinary reflooding plus SPF recomputation handle failover — the link-state counterpart to BGP's path-vector world.
Explore the full Protocol Lab series here: github.com/pathvector-studio/protocol-lab. If these labs are useful to you, please ⭐ star the repo on GitHub — it genuinely helps others find the project.
Next up, we'll build on this link-state foundation — think multiple areas, ECMP, and how IGPs and BGP work together inside a real network.