Reported symptoms
It is Monday morning and branch site 14 has two tickets open, at two different priorities, filed by two different people.
The first says the HQ file server is unreachable. So are the print queues, the intranet and the internal wiki. Everything at the HQ campus, from every machine at the site, since Friday morning at the latest — one user thinks it may have been broken on Thursday evening but was not sure enough to call.
The second says the ERP is slow. The ERP is hosted at the DC, not at HQ. It works. Page loads take four or five seconds instead of well under one. It has been filed as a performance complaint and routed to the application team.
Internet browsing from the site is fine. Video calls are fine. That single fact is why the first responder closed out the possibility of a WAN problem within about ninety seconds and started looking at HQ.
Two more things are true and nobody has connected them yet.
The site 14 router was replaced on Thursday evening. The original died of a power supply failure on Wednesday; a spare was couriered out, built on site, and handed over at 21:40. The handover note is one line: OSPF neighbour table empty at handover, expect convergence overnight.
And the WAN carrier ran scheduled maintenance on the branch aggregation circuit that same night. That is the change everybody has reached for, and a fault has already been raised against it.
The estate
rtr-agg-3 is the branch aggregation router at the regional
hub. eth0 faces the core in area 0. eth1 is a trunk toward
the branch WAN with one sub-interface per site, and every one of
those sub-interfaces is in area 0.0.0.20, which is configured
as a stub area. Twelve branches hang off it.
rtr-br-14 is the replacement router at site 14, a VyOS 1.5 LTS
install built on Thursday. eth0 is the WAN tail,
198.51.100.130/30, facing 198.51.100.129 on rtr-agg-3.
eth1 carries the site LANs, summarised as 10.14.0.0/16. Its
router-id is 192.0.2.114.
Three things about the branch design matter here, and all three predate the incident:
- Every branch has its own internet breakout and a static default route to it at administrative distance 1. That static beats the OSPF default the ABR injects into the stub area, so branch internet access has never depended on the IGP.
- Every branch has an IPsec tunnel to the DC carrying floating static routes for the DC supernet, at a distance high enough that OSPF wins when OSPF has something to offer.
- The HQ campus prefixes are learned only through OSPF. Nobody ever wrote a backup static for them, because the private WAN was considered the primary and the tunnel was built for DC replication.
Evidence provided
vyos@rtr-br-14:~$ show ip ospf neighborNeighbor ID Pri State Dead Time Address InterfaceIllustrative output
vyos@rtr-agg-3:~$ show ip ospf neighborNeighbor ID Pri State Dead Time Address Interface
192.0.2.106 1 Full/DR 36.117s 198.51.100.114 eth1.6
192.0.2.111 1 Full/DR 38.902s 198.51.100.126 eth1.11
192.0.2.115 1 Full/DR 33.480s 198.51.100.134 eth1.15
... eight more, all Full, none of them site 14Illustrative output
vyos@rtr-br-14:~$ ping -c 5 198.51.100.129PING 198.51.100.129 (198.51.100.129) 56(84) bytes of data.
64 bytes from 198.51.100.129: icmp_seq=1 ttl=64 time=3.42 ms
--- 198.51.100.129 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4006msIllustrative output
vyos@rtr-agg-3:~$ tcpdump -ni eth1.14 proto ospf -vv10:22:31.417 IP 198.51.100.130 > 224.0.0.5: OSPFv2, Hello, length 48
Router-ID 192.0.2.114, Area 0.0.20.0, Authentication Type: none (0)
10:22:34.902 IP 198.51.100.129 > 224.0.0.5: OSPFv2, Hello, length 48
Router-ID 192.0.2.103, Area 0.0.0.20, Authentication Type: none (0)Illustrative output
vyos@rtr-br-14:~$ show ip ospf interface eth0eth0 is up
Internet Address 198.51.100.130/30, Area 0.0.20.0
MTU mismatch detection: enabled
Router ID 192.0.2.114, Network Type BROADCAST, Cost: 10
Timer intervals configured, Hello 10s, Dead 40s
Neighbor Count is 0, Adjacent neighbor count is 0Illustrative output
show ip route ospf on the branch returns nothing. show system commit shows a single commit, Thursday at 21:31.
Work the evidence before reading on
Everything on this router is up, and the router has no complaint to make about anything.
- The neighbour table is empty, not stuck. Compare that with the sibling scenario where an adjacency sits in ExStart. What does each of those two shapes tell you about how far the protocol got before it gave up?
- The Hellos arrive. The
tcpdumpproves it, and it costs one command. What half of the search space does that single result eliminate, and what is left in the other half? - OSPF has a short list of parameters that must match before a neighbour structure is created at all. Name them, then work out which of them you can read directly off the capture you already have.
- Internet works, the ERP is slow, the HQ prefixes are dead. Three populations of traffic, three outcomes. What does each one depend on, and what single missing thing produces all three?
- Eleven other branches on the same aggregation router, over the same carrier maintenance window, are fine. What does that do to the carrier hypothesis, and why has nobody used it yet?
Before continuing: an empty neighbour table is not an absence of information. It is the single most specific state OSPF has.
Root cause
1. Two valid areas, one character apart
The branch area is 0.0.0.20. The rebuilt router was configured
with 0.0.20.0.
The Area ID in the OSPF header is a 32-bit field. VyOS accepts
it written as a decimal or as a dotted quad, and FRR renders it
back as a dotted quad. 0.0.0.20 is area 20. 0.0.20.0 is area
5120. Both are legal. Neither is a syntax error, so the commit
succeeded and the validation the engineer ran — show ip ospf interface, which printed an area and an interface that was up —
returned exactly what he expected to see.
The router then did the only sensible thing available to it: it started area 5120, put one interface in it, and waited for somebody else to show up.
2. Why the table is empty rather than stuck
RFC 2328 has the receiving router validate the OSPF header before anything else happens, and the Area ID is part of that validation. A packet whose Area ID does not match the area configured on the receiving interface is discarded there and then.
That is the whole mechanism, and its consequence is the most useful diagnostic signal in this incident. The packet never reaches the neighbour state machine, so no neighbour structure is created, so there is nothing to print. Every state after the header check — Init, 2-Way, ExStart, Exchange, Loading — exists only because a Hello was accepted. An adjacency that reaches any of them has already proved that the area matches.
So the states form a sieve, and where the adjacency stops tells you which check rejected it:
- Nothing in the table at all. Either no Hello is arriving
(the interface is not in OSPF, or is
passive, or something is filtering multicast to 224.0.0.5), or Hellos are arriving and failing header or parameter validation: Area ID, hello and dead intervals, authentication, or the E-bit and N-bit that encode area type. - Init. Hellos are arriving and being accepted, but the neighbour is not listing our router-id back. The path is one-way.
- ExStart or Exchange. Every parameter agreed. The Database Description exchange is failing — MTU, a duplicate router-id, or a path that cannot carry the packet.
One tcpdump splits the first bullet in half, which is why it
is the cheapest check available and should have been the second
command anybody ran. The Hellos are arriving. That kills the
carrier fault, the filtering hypothesis and the passive-interface
hypothesis in one capture, and leaves parameter validation — a
short, enumerable list, most of which is printed in the same
capture.
3. The estate hid it for four days
The three symptoms are three different dependencies on the missing IGP.
Internet traffic follows a static default at distance 1. It has never depended on OSPF and did not notice. The DC prefixes are covered by floating statics over the IPsec tunnel, which is exactly what a floating static is for: when OSPF has nothing to offer, they install, and traffic keeps flowing over a longer, slower path. That is the ERP ticket, and the application team will never find anything wrong with the application. The HQ campus prefixes have no backup at all, so they simply stopped existing at site 14.
Resolution
- Read the area off both routers before touching anything.
show ip ospf interface eth0on the branch and on the matching sub-interface at the hub. The correction is derived from the hub value, not from the design document, because the eleven working branches are the authority on what the area actually is. - Decide now whether you are fixing this minute or holding until the evening, and write the decision down. Bringing the adjacency up moves live traffic off the IPsec tunnel and onto the private WAN, which will disturb established sessions. The site is degraded, not down. A hold is defensible; an undeclared hold is not.
- If you hold, lift the monitoring suppression immediately, name an owner and an end time, and annotate both service-desk tickets so the ERP performance ticket stops being worked as an application fault.
- Withdraw the carrier fault. Eleven branches crossed the same maintenance window on the same aggregation router without incident, which is stronger evidence than anything the carrier will produce, and leaving a false fault open costs somebody else a day.
- Move the branch interface and the branch LAN networks into the correct area, and delete the orphaned one so the router is not left holding a stub area with no members. Enter configuration mode and stage the change without committing.
- Read the change with
comparebefore you commit it. This is a routing-protocol change on a router reachable only over the backup path; the diff is the last cheap opportunity to notice a second transposed digit. - Commit with
commit-confirmand a short window. If the area value is wrong a second time the router reverts on its own, which matters more than usual here because a branch router that loses both paths is a site visit. - Watch the adjacency come up before you confirm the commit. It should reach Full within a dead interval. If it does not, you have not finished diagnosing.
- Save the configuration once the adjacency is stable, and only then close the confirm window.
# On rtr-br-14, in configuration mode. Confirm the area value against
# the hub before pasting anything.
delete protocols ospf area 0.0.20.0
set protocols ospf area 0.0.0.20 network 198.51.100.128/30
set protocols ospf area 0.0.0.20 network 10.14.0.0/16
set protocols ospf area 0.0.0.20 area-type stub
compare
commit-confirm 5
Verification
- Check from both ends.
show ip ospf neighboron rtr-br-14 shows rtr-agg-3 in Full, and the same command on rtr-agg-3 shows twelve adjacencies rather than eleven. Checking one end is the exact omission that created this incident. - Check twice, several minutes apart and longer than the dead interval. An adjacency caught between two resets looks healthy for a moment.
- The databases agree.
show ip ospf databaseon the branch now holds the area LSAs and the Type-3 default the ABR injects into the stub area. A Full adjacency with an empty database is not finished. - The routes are installed, not merely learned.
show ip route ospfon the branch returns the HQ campus prefixes it returned nothing for before. - Traffic actually moved. On a core router,
show ip route 10.14.0.0/16shows the OSPF path rather than the floating static. That is the statement that the backup path has been released, and it is the one that a check run on the branch alone cannot make. - The user-visible symptom is gone, proven from a workstation at the site rather than from a router: open the HQ file server and print a page.
- Monitoring is telling the truth again. Lift the suppression, confirm the neighbour check clears on its own rather than because you closed it, and confirm the neighbour count on the aggregation router reads twelve.
Prevention
- Make the adjacency count the acceptance criterion for a router build. Not “OSPF configured” — the expected number of neighbours Full, confirmed on both ends, pasted into the build record. Every other step in this incident is downstream of a checklist that let a router be handed over unfinished.
- Alert on OSPF neighbour count per device. A drop is a page, not a dashboard entry. The course covers reading FRR JSON output, which makes this a few lines of scripting, and it would have fired at 21:41 on Thursday while the engineer was still in the car park.
- Give every monitoring suppression a mandatory expiry. The alert here was correct and it fired on time. A suppression opened for a change window and never closed is functionally a deleted alert, and it cost four days.
- Write area identifiers one way, and prefer the decimal
form.
20cannot be transposed into another valid area.0.0.0.20can be transposed into three of them, and every one of those will commit without complaint. - Build branch routers from a parameterised template, not from a sibling configuration edited by hand. The automation and config-as-code parts of the course exist for exactly this, and the validation step should compare rendered operational output against expected state rather than configuration text against a document.
- Write backup paths down as coverage, not as a list of tunnels. Nobody at this site knew that the DC prefixes had a fallback and the HQ prefixes did not. That asymmetry decided which tickets got raised, which team got them, and how long the incident lasted.