Reported symptoms
For three days the office has had flaky internet, and the service desk has closed three tickets as user error because it keeps failing to reproduce.
The failure is real and it has a shape nobody has written down yet. Short things work. DNS resolves instantly. Pings to anywhere are clean, with no loss and no jitter. Web pages load. A speed test runs at line rate. Every quick test anybody has run has passed.
Long things fail. Client VPN tunnels to a partner site drop every few minutes and reconnect on their own. Uploads to a customer portal stall, at a different percentage each attempt. A large page will occasionally half-render, sitting with images outstanding until it is reloaded.
And staff are being logged out of a SaaS application at random, mid-task, several times a day. That one has been raised with the vendor as an application fault and the vendor has found nothing.
Everything inside the LAN is perfect. The intranet, the file shares, the print queues, the internal ticketing system - nobody has a complaint about any of it, which is why the working hypothesis is that the ISP is having a bad week.
Three more facts are on the table and nobody has put them together.
show vrrp on both routers of the HA pair says MASTER. Both have
said so continuously since Tuesday. The HA dashboard is green,
because the check it runs asks the primary router whether it is
master and the answer is yes.
The access switch has been logging MAC address flapping for a
00:00:5e address between two ports since Tuesday afternoon. The
network team looked at it, recognised the address as a VRRP
virtual MAC, and filed it as normal VRRP behaviour.
And the only change in the window was a firewall hardening rollout, pushed by the configuration pipeline on Tuesday afternoon and signed off as input-filter-only with no impact on forwarded traffic. No OSPF adjacency dropped during it or since.
The estate
rtr-ha-a and rtr-ha-b are a VyOS 1.5 LTS pair providing the
default gateway for the office LAN.
eth1on each faces the LAN,10.20.0.0/24.rtr-ha-ais10.20.0.252andrtr-ha-bis10.20.0.253.- VRRP group
LAN, VRID 10, virtual address10.20.0.1, which is the default gateway every host on the segment uses. Priority 200 on A, 100 on B, preemption enabled, default advertisement interval. eth0on each is a separate ISP tail. Each router source-NATs LAN traffic to its own public address:203.0.113.10on A,198.51.100.10on B. Each holds its own connection-tracking state and neither shares any of it.eth2on each goes to the core, running OSPF in area 0. This is how the site reaches the DC, and it is the part of the control plane everyone thinks of as the routing.
On Tuesday afternoon the security programme rolled out a
default-deny input filter to every router in the estate: an
explicit allow-list for management and control-plane traffic and
a base-chain default action of drop. rtr-ha-a committed at
14:06 and rtr-ha-b at 14:07.
The allow-list contains rules for established and related traffic, ICMP, SSH from the management prefix, OSPF and BGP.
Evidence provided
vyos@rtr-ha-a:~$ show vrrpName Interface VRID State Priority Last Transition
---- --------- ---- ----- -------- ---------------
LAN eth1 10 MASTER 200 6d 21h 04mIllustrative output
vyos@rtr-ha-b:~$ show vrrpName Interface VRID State Priority Last Transition
---- --------- ---- ----- -------- ---------------
LAN eth1 10 MASTER 100 3d 02h 51mIllustrative output
vyos@rtr-ha-a:~$ tcpdump -ni eth1 'ip proto 112' -c 4 -vv17:02:11.114 IP (tos 0xc0, ttl 255, proto VRRP (112), length 40)
10.20.0.252 > 224.0.0.18: VRRPv3, Advertisement, vrid 10, prio 200, authtype none, intvl 100cs, length 20
17:02:11.802 IP (tos 0xc0, ttl 255, proto VRRP (112), length 40)
10.20.0.253 > 224.0.0.18: VRRPv3, Advertisement, vrid 10, prio 100, authtype none, intvl 100cs, length 20
17:02:12.117 IP (tos 0xc0, ttl 255, proto VRRP (112), length 40)
10.20.0.252 > 224.0.0.18: VRRPv3, Advertisement, vrid 10, prio 200, authtype none, intvl 100cs, length 20
17:02:12.805 IP (tos 0xc0, ttl 255, proto VRRP (112), length 40)
10.20.0.253 > 224.0.0.18: VRRPv3, Advertisement, vrid 10, prio 100, authtype none, intvl 100cs, length 20Illustrative output
The identical capture on rtr-ha-b shows the same two streams.
Both print the same version, the same vrid 10 and authtype none, so there is nothing in the packets for the two routers to
disagree about.
vyos@rtr-ha-a:~$ show configuration commands | match 'firewall ipv4 input'set firewall ipv4 input filter default-action 'drop'
set firewall ipv4 input filter rule 10 action 'accept'
set firewall ipv4 input filter rule 10 state 'established'
set firewall ipv4 input filter rule 11 action 'accept'
set firewall ipv4 input filter rule 11 state 'related'
set firewall ipv4 input filter rule 20 action 'accept'
set firewall ipv4 input filter rule 20 protocol 'icmp'
set firewall ipv4 input filter rule 25 action 'accept'
set firewall ipv4 input filter rule 25 protocol 'tcp'
set firewall ipv4 input filter rule 25 destination port '22'
set firewall ipv4 input filter rule 25 source address '10.20.9.0/24'
set firewall ipv4 input filter rule 30 action 'accept'
set firewall ipv4 input filter rule 30 protocol 'ospf'
set firewall ipv4 input filter rule 31 action 'accept'
set firewall ipv4 input filter rule 31 protocol 'tcp'
set firewall ipv4 input filter rule 31 destination port '179'Illustrative output
show firewall shows the input filter default-drop counter
advancing at roughly one packet per second on each router.
show vrrp detail on rtr-ha-b records its transition to master
at 14:07 on Tuesday. On the access switch, the virtual MAC
00:00:5e:00:01:0a appears against two ports.
Work the evidence before reading on
Both routers are up, both are healthy, and neither has a complaint to make.
- The lesson’s first cause for split brain is a layer-2 partition - the routers cannot hear each other. One command settles that. Run it in your head against the capture above and say what it eliminates.
- The capture also carries the fields that a parameter mismatch would show. Name them, read them off the output, and cross off what they rule out.
- You have now eliminated the wire and the packet contents, and the advertisements are still not being acted on. Where in a Linux router can a packet be, such that a capture on the interface sees it and the process that cares about it does not?
- Short exchanges work and long ones fail. What does each router hold that the other does not, and what happens to a flow the moment it changes routers?
- Both routers report MASTER and the HA dashboard is green. Write down what the dashboard’s check actually asserts, then write down what you would have to ask to detect this.
- OSPF adjacencies never dropped, and that is what the change was signed off on. Look at where VRRP lives in the configuration tree compared with OSPF and BGP, and explain the omission without blaming anybody.
Before continuing: split brain is a property of the pair, not of a router. Every command run so far has been addressed to one member. Which question can only be answered by looking at both?
Root cause
1. Protocol 112 is not on the allow-list
VRRP advertisements are not TCP and not UDP. They are carried directly in IP as protocol 112, addressed to the multicast group 224.0.0.18, with a TTL of 255 so they cannot leave the segment. They are delivered locally to the router rather than forwarded through it, so the chain that evaluates them is the input chain - the one the hardening change converted to default-drop.
The allow-list covers established and related traffic, ICMP, SSH from the management prefix, OSPF and BGP. It does not cover protocol 112. From the moment each router committed, it stopped accepting its partner’s advertisements.
The backup did exactly what RFC 5798 requires of it. Having heard nothing for longer than the master-down interval, it concluded the master was gone, took the virtual IP, sent a gratuitous ARP, and entered MASTER. That is not a bug and not a race; it is the protocol working correctly on the only information it was allowed to have.
The primary never noticed. It was dropping the same advertisements in the same way - and even if it had heard them, a master that receives a lower-priority advertisement keeps the role. There is no state in which this pair recovers on its own.
2. The packets were visible and unreachable at the same time
This is the part worth carrying to every other Linux-router incident.
tcpdump attaches to the interface. On the receive path that tap
sits before the netfilter hooks, so a capture shows every frame
the NIC delivered, whether or not the firewall later dropped it.
The advertisements are genuinely on the wire, genuinely received
by the kernel, and genuinely never delivered to the daemon that
was waiting for them.
That looks like a contradiction and it is the most useful diagnostic gap the platform has. Read it as a sequence:
- A capture shows nothing. The packet did not arrive. Suspect the wire, the switch, the VLAN, the sender.
- A capture shows it and the process ignores it. The packet arrived and something between the NIC and the process consumed it, or the process rejected it on its contents.
- A capture shows it, the contents agree, and the process still does not act. The packet arrived and something between the NIC and the process consumed it. That is a very short list, and on a router that has just had its input chain rewritten it is a list of one.
The second and third bullets are separated by reading the fields
in the capture you already have - version, vrid, priority,
authtype - which is why this incident collapses in two commands
once somebody runs tcpdump instead of comparing configurations.
3. Two masters, one virtual MAC, and a switch that cannot know
Both routers now own 10.20.0.1 and both answer ARP for it with
the same virtual MAC, 00:00:5e:00:01:0a, because the virtual
MAC is derived from the VRID and the VRID matches.
The access switch learns that MAC from whichever router last sent
a frame with it as the source, and relearns it when the other one
does. From the LAN’s point of view the default gateway is behind
a port that changes every second or so. Frames to the gateway
land on rtr-ha-a or rtr-ha-b depending on nothing the client
can influence.
The switch reported this accurately, from the first hour, as MAC
flapping on a 00:00:5e address. That address is only ever
supposed to be behind one port at a time. The alarm was correct
and it was dismissed.
4. Why short things work and long things break
Each router keeps its own connection-tracking table and NATs to its own upstream address. Neither shares any state with the other.
A DNS lookup, an ICMP echo, a small object fetch - each of these completes within one or two round trips, almost always on whichever router it started on. Nothing is wrong with them and nothing will be.
A long flow eventually straddles a relearn. The packet that lands on the other router finds no state for the connection, gets source-NATed as a brand new one to that router’s own public address, and arrives at a server that has been talking to a different address for the last ten minutes. The server rejects it or drops it, and the flow stalls or resets.
That is the VPN tunnels, the stalled uploads and the
half-rendered pages, all at once. The SaaS logouts are the same
mechanism observed from outside: a session bound to a source
address that keeps alternating between 203.0.113.10 and
198.51.100.10.
Resolution
- Confirm the fault is the pair, not a router, before acting. Read
show vrrpon both boxes within the same minute and record both outputs. Every command so far has been addressed to one member, and one member cannot report this. - Decide whether you are fixing now or at an agreed window, and write it down with an owner and an end time. The repair is briefly worse than the fault for a subset of users, which makes a short hold defensible rather than a delay.
- Consolidate the tickets first, whichever you choose. The VPN drops, the portal stalls and the SaaS logouts are one fault, and somebody is currently chasing an application bug at a vendor who does not have one.
- Fix the backup first. As soon as rtr-ha-b can hear rtr-ha-a again it sees a higher-priority master, demotes itself and releases the virtual IP. That single commit ends the split brain.
- Expect the demotion to hurt. Connection-tracking state does not migrate: every flow currently pinned to rtr-ha-b ends at that instant. On this estate that means client VPN tunnels and in-flight uploads - the same users who have been raising tickets.
- Then fix the primary. This commit restores nothing you can see and matters more than the first: without it the pair still has no working election, and the next genuine failure would find rtr-ha-a unable to observe anything at all.
- Scope the rule rather than blanket-permitting the protocol. Inbound interface, source address of the peer, destination 224.0.0.18. Accepting protocol 112 from anywhere on any interface reintroduces a spoofing surface on a router that was being hardened for a reason.
- Read the change with
compareand commit withcommit-confirm. You are editing the input chain of a router you are reachable on; a mistake here locks you out, and the confirm window is what turns that into a five-minute wait rather than a site visit. - Do not roll back the hardening change. The defect is one missing rule inside it, and reverting the whole thing to make a symptom disappear discards work that is otherwise correct.
# On rtr-ha-b first - the backup. This commit ends the split brain.
# The matching change on rtr-ha-a restores the election itself.
set firewall ipv4 input filter rule 40 description 'VRRP advertisements from the HA peer'
set firewall ipv4 input filter rule 40 action 'accept'
set firewall ipv4 input filter rule 40 protocol '112'
set firewall ipv4 input filter rule 40 inbound-interface name 'eth1'
set firewall ipv4 input filter rule 40 source address '10.20.0.252'
set firewall ipv4 input filter rule 40 destination address '224.0.0.18'
compare
commit-confirm 5
Verification
- Ask the pair, not the members.
show vrrpon both routers within the same minute must show exactly one MASTER and one BACKUP. Read it again several minutes later, so a transition caught mid-cycle is not mistaken for a stable state. - The rule is matching, not merely present. The packet counter on the new input rule is incrementing on both routers, and the input filter default-drop counter has stopped advancing at one packet per second.
- The wire agrees. A capture on each router still shows both advertisement streams - that never changed - and the state above proves they are now being acted on rather than dropped.
- The switch sees one gateway. The virtual MAC 00:00:5e:00:01:0a appears against exactly one port, and no further flapping entries appear in the switch log.
- The redundancy works, which is a larger claim than the symptom being gone. In an agreed window, take the primary out of the group and confirm the backup takes over within the master-down interval and sends its gratuitous ARP; then restore the primary and confirm the backup steps back down. Nobody here has had a working failover for three days.
- The user-visible fault is gone, proven from a user machine: a file transfer of several minutes completes, a client VPN tunnel survives a coffee break, and an external service reports one stable source address rather than two alternating ones.
- The hardening still hardens. The default drop is still counting, and the new rule is scoped to the peer address, the interface and the multicast destination rather than accepting protocol 112 from anywhere.
- The other routers in the estate are checked, not assumed. The same allow-list was pushed everywhere; any other VRRP pair in the fleet has the same defect and the same three days of it.
Prevention
- Derive a default-deny input allow-list from what the router speaks, not from what it listens on. Ports are easy to enumerate and are the part every template covers. The protocols that keep a router in a cluster ride directly in IP and have no ports to find: OSPF as 89, VRRP as 112, ESP as 50, plus IKE and NAT-T on their UDP ports. Generate the list from the configuration rather than writing it by hand.
- Alert on VRRP master count across the pair. One master per
group, asserted over both members. It is a two-line check over
show vrrp jsonfrom each router and it would have fired four seconds after the Tuesday commit. Add transition count alongside it, which catches the flapping cases a count check cannot. - Retire the dashboard check that asks the primary whether it is master. That assertion is true in a healthy pair and true in the worst failure this pair can have, which makes it worse than no check at all.
- Stagger control-plane changes across HA members and check the pair between commits. Rolling both out in one window removed the only comparison that would have exposed this within a minute.
- Validate a hardening change against control-plane state. Adjacency counts, cluster membership, tunnel state - not whether SSH still works, which is the one thing the engineer running the change has already proved by being logged in.
- Treat MAC flapping on an address in the IANA VRRP range as a page. That address is only ever meant to exist behind one port. The switch diagnosed this correctly on day one and was overruled.
- Write the estate’s protocol inventory down once, so the next hardening change has a list to check against instead of a section of the configuration tree to skim.