Reported symptoms
It is Monday morning at the Northgate campus and there are four tickets open, against four different teams, and not one of them says the word VLAN.
The wireless team has the loudest one. Access points across the building have dropped to standalone mode. They cannot reach the controller in the DC, roaming between them fails, and the two SSIDs that are defined centrally have simply disappeared.
The storage team has one from the weekend. The nightly replication job from the campus NAS to the DC failed on Saturday night and again on Sunday night, both times with connection timeouts. It is being worked as a storage fault because that is who owns the job.
Physical security has one that nobody has prioritised. The camera NVR is recording locally, so nothing looks broken on the console, but it has not written anything to the off-site target since Saturday morning. That is the ticket with a retention deadline behind it.
And the NOC has one that contradicts itself. Nobody can SSH to the router on its management address. The same router answers on its loopback, every monitoring check against it is green, and the OSPF adjacency to the core has not flapped once all weekend. The router is simultaneously up and unreachable, depending on who you ask.
Two more facts are in play and nobody has connected them.
Devices on the management VLAN can ping each other. The NAS sees the NVR, the NVR sees the access points, everything on that subnet answers everything else on that subnet. That is what let the first responder rule out a VLAN problem inside two minutes.
And the fault appeared to spread. On Saturday it was the NAS and the cameras. By Monday morning it was most of the access points, and the count kept climbing through the morning. A fault that widens on its own hour by hour reads like failing hardware, not like a change.
The estate
rtr-dist-2 is the distribution router for the campus, a VyOS
1.5 LTS box in a router-on-a-stick configuration.
eth0is the trunk to the access switch stacksw-acc-1. Three VLANs cross it: 10 (DATA), 20 (VOICE), 30 (MGMT).eth0.10holds10.20.10.1/24,eth0.20holds10.20.20.1/24, and the management sub-interface holds10.20.30.1/24.eth1is the uplink to the routed core, in OSPF area 0. Every campus prefix is advertised from here, and the loopback10.255.0.2/32is the address the NOC’s monitoring actually polls.- The router is the DHCP server for all three VLANs, and the default gateway for all three.
The management VLAN carries the things nobody thinks about until they stop: the access points, the NAS, the NVR, the console server, and the router’s own management address.
One change happened. On Saturday at 02:10 the onboard NIC that
carried eth0 was replaced under a planned maintenance window,
and because the interface name changed, the sub-interface block
was retyped by hand. The engineer worked from the network wiki,
whose VLAN table lists Northgate and the Eastfield campus in
adjacent rows. Eastfield’s management VLAN is 130.
The handover at 02:40 records: all interfaces up, all connected routes present, core adjacency Full, gateway ping successful from a test laptop. Every one of those statements is true.
Evidence provided
vyos@rtr-dist-2:~$ show interfaces ethernet eth0eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:aa:11:02 brd ff:ff:ff:ff:ff:ff
RX: bytes packets errors dropped overrun mcast
41822901744 38104112 0 0 0 902144
TX: bytes packets errors dropped carrier collisions
39118220913 35990041 0 0 0 0Illustrative output
vyos@rtr-dist-2:~$ ip link show2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
4: eth0.10@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
5: eth0.20@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
6: eth0.130@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000Illustrative output
vyos@rtr-dist-2:~$ show ip route connectedC>* 10.20.10.0/24 is directly connected, eth0.10, 2d09h12m
C>* 10.20.20.0/24 is directly connected, eth0.20, 2d09h12m
C>* 10.20.30.0/24 is directly connected, eth0.130, 2d09h12m
C>* 10.0.0.8/30 is directly connected, eth1, 2d09h12mIllustrative output
vyos@rtr-dist-2:~$ ip -s link show eth0.1306: eth0.130@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:aa:11:02 brd ff:ff:ff:ff:ff:ff
RX: bytes packets errors dropped missed mcast
0 0 0 0 0 0
TX: bytes packets errors dropped carrier collisions
1904688 30328 0 0 0 0Illustrative output
vyos@rtr-dist-2:~$ tcpdump -i eth0 -e -n vlan 30 -c 5tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
09:14:02.118 b4:fb:e4:21:9c:07 > ff:ff:ff:ff:ff:ff, ethertype 802.1Q (0x8100), vlan 30, ethertype ARP, Request who-has 10.20.30.1 tell 10.20.30.41, length 46
09:14:03.121 b4:fb:e4:21:9c:07 > ff:ff:ff:ff:ff:ff, ethertype 802.1Q (0x8100), vlan 30, ethertype ARP, Request who-has 10.20.30.1 tell 10.20.30.41, length 46
09:14:04.126 b4:fb:e4:21:9c:07 > ff:ff:ff:ff:ff:ff, ethertype 802.1Q (0x8100), vlan 30, ethertype ARP, Request who-has 10.20.30.1 tell 10.20.30.41, length 46Illustrative output
The same capture with vlan 130 in place of vlan 30 returns
nothing over a two-minute run. show dhcp server leases shows no
lease issued in the management pool since Saturday 02:10, while
the data and voice pools are busy. show system commit records
one commit, Saturday at 02:10.
Work the evidence before reading on
Everything on this router is up, and the router has no complaint to make about anything.
- Three connected routes are present, selected and installed. What does a connected route prove about the far end of the cable? Be precise: name what has to be true for a connected route to appear, and what does not.
ip link showprints the netdev names. Read them against the site VLAN table before you read anything else on the box. This step is free and it is the whole incident.tcpdump -e -n vlan 130returns nothing. There are at least two different worlds that produce that result. Name them, and name the single command that separates them.- Hosts on the management VLAN reach each other and reach nothing else. Which device does intra-VLAN traffic touch, and which device does inter-VLAN traffic touch? What does the answer eliminate?
- VLAN 10 and VLAN 20 are perfect, over the same cable, through the same switch port, on the same trunk. What does that do to every hypothesis about the physical link, the NIC replacement, and the trunk as a whole?
- The blast radius grew hour by hour on Monday morning. What in this estate has a timer measured in hours, and what does it do when it expires?
Before continuing: a VLAN sub-interface has no peer. Work out
what that means for the value of every show command on this
router, then decide where the evidence has to come from instead.
Root cause
1. A sub-interface is a filter, and this one was set to the wrong channel
The management sub-interface was created as vif 130. The site
uses VLAN 30.
Both are legal VLAN IDs - the range is 1 to 4094 - so nothing
rejected it. The commit created a kernel netdev named eth0.130,
attached the address to it, and the address produced a connected
route which FRR selected and installed. Every subsequent status
command reported a healthy interface, and every one of them was
right. The interface is healthy. It is attached to a VLAN that
nothing else on the campus uses.
The failure is bidirectional, and it breaks at a different box in each direction:
- Outbound. Frames the router emits on
eth0.130leave the trunk tagged 130. The switch drops them, because 130 is not in the trunk’s allowed VLAN list. - Inbound. Frames the switch sends tagged 30 arrive on
eth0. The 802.1Q driver looks for a netdev registered for VLAN 30, finds none, and discards them. That is why the RX counter oneth0.130has never moved: nothing is tagged 130, so nothing is ever delivered to it.
The capture is the proof of both halves at once. The ARP requests
for 10.20.30.1 are on the wire, correctly tagged 30, arriving
at the router’s port - and the router never answers them, because
as far as its IP stack is concerned those frames were never
addressed to any interface it owns.
2. Why every check on the router passed
This is the part worth carrying beyond VLANs.
A VLAN sub-interface negotiates nothing. There is no peer, no
adjacency, no state machine, no far end that can disagree. Its
link state simply follows its parent, so eth0.130 is up for
exactly one reason: eth0 is up. There is no state in which a
sub-interface reports a VLAN-id mismatch, because there is
nothing it could compare against.
Compare that with the protocols on the same router. An OSPF adjacency that disagrees with its neighbour about an area or a timer stops in a specific state and tells you so. An LACP bundle whose partner is misconfigured refuses to aggregate and logs it. Those subsystems can report a mismatch because they are talking to somebody.
So the handover checklist was not lazy. It was asking questions that this fault class is structurally incapable of answering. “All interfaces up” and “all connected routes present” are both true on a router with every VLAN id wrong.
3. Why the VLAN looked alive
Intra-VLAN traffic never touches the router. A host on
10.20.30.0/24 ARPs for another host on 10.20.30.0/24, the
switch forwards the frame within VLAN 30, and the two talk
directly. That path was working all weekend and is still working
now.
Only two things on that VLAN need the router: anything leaving the subnet, and DHCP. Both were dead from 02:10.
So “we can ping each other, the VLAN is fine” is a true statement about the switch and an empty one about the router. It eliminated nothing, and it cost the incident about thirty hours.
4. The spread was a lease timer
The fault did not widen. The consequences of it arrived on a schedule.
DHCP renewal is unicast to the server’s address, which is on the router, which is unreachable from the VLAN. Renewal at T1 failed silently on every device, and each device kept using its existing address until the lease actually expired - at which point it lost the address entirely and started broadcasting DISCOVERs that also went nowhere.
So devices fell off in the order their leases ran out, spread across Monday morning, which is precisely the shape of a failing component and precisely not the shape of a Saturday change. The timer was doing the pacing.
Resolution
- Read the trunk configuration off the switch before typing anything on the router. The allowed VLAN list on sw-acc-1 is the authority on what the management VLAN actually is; the wiki page that lists two campuses in adjacent rows is what caused this, and consulting it again is not a check.
- Decide explicitly whether you are fixing now or holding, and write the decision down. Fixing now is the defensible choice here: the edit does not touch VLAN 10 or 20, nothing on the management VLAN currently has a working path off it so there are no established sessions to disturb, and the camera off-site recording gap has a retention deadline behind it.
- Annotate all four tickets before you start. The storage and wireless teams are investigating faults they do not have, and that continues until somebody tells them otherwise.
- Stage the change without committing: delete the wrong sub-interface, create the right one with the same address, and give it a description.
- Move everything that named the old interface by name. On a 1.5 router the firewall rule sets are reached by a jump from the base chain, keyed on the inbound interface or on an interface group, so a sub-interface that is not in the group is not covered by the policy. Leaving that behind converts one outage into a quieter one.
- Leave the DHCP server alone. VyOS derives the listener from the subnets rather than from an interface binding, which is exactly why it never complained: the declared subnet did match an interface address, just one on the wrong VLAN. Once the address is on eth0.30 the same subnet matches the same way.
- Read the whole change with
comparebefore you commit it. A second transposed digit here costs another weekend, and the diff is the last cheap place to see it. - Commit with
commit-confirm. The confirm window is not protecting you from the VLAN edit - you are reachable over the core, not over this VLAN - it is protecting the two working VLANs from the firewall group edit, which touches a chain their traffic also traverses. - Watch the wire, not the configuration, before you confirm. Run the VLAN 30 capture again and wait for the ARP requests to start drawing replies.
- Save once traffic is flowing, and only then close the confirm window.
# On rtr-dist-2, in configuration mode. Confirm the VLAN id against the
# switch trunk before pasting anything.
delete interfaces ethernet eth0 vif 130
set interfaces ethernet eth0 vif 30 address '10.20.30.1/24'
set interfaces ethernet eth0 vif 30 description 'NORTHGATE-MGMT'
delete firewall group interface-group MGMT-IF interface eth0.130
set firewall group interface-group MGMT-IF interface eth0.30
compare
commit-confirm 5
Verification
- The kernel names the netdev you expect.
ip link showlists eth0.30@eth0 and does not list eth0.130. This is the first check because it is the fault, stated directly. - Frames are arriving.
tcpdump -i eth0 -e -n vlan 30shows tagged traffic, and specifically shows the ARP requests for 10.20.30.1 now drawing replies. The capture is the only evidence in this list that is independent of what the router believes about itself. - The receive counter is advancing.
ip -s link show eth0.30twice, a minute apart, with a higher RX packet count the second time. Advancing, not merely non-zero: a counter that moved once during the commit and stopped is not a working interface. - The test runs from the VLAN, not from the router. On a host on 10.20.30.0/24, ping the gateway, then reach something on the far side of the core. Nobody filed a ticket about router state.
- DHCP is issuing again.
show dhcp server leasesshows leases with fresh timestamps in the management pool, andshow dhcp server statisticsshows counters moving. The pool existing was never the question. - The policy followed the interface.
show firewall grouplists eth0.30, and the forward-filter rule that jumps to the management rule set is incrementing. An interface that is quietly outside every rule set is the second defect this change could have introduced. - Nothing else moved. VLAN 10 and 20 counters are still advancing, the OSPF adjacency on eth1 did not reset, and the rest of
show ip routeis unchanged. - The service owners close their own tickets from their own side: the controller lists its access points, the replication job completes, the NVR writes off-site. Devices whose leases expired will recover on their own DISCOVER; a handful may need a bounce, and how each vendor behaves after an address loss is not something this runbook can promise.
Prevention
- Make traffic the acceptance criterion for an interface
rebuild, not link state. A
vifcomes up because its parent is up. Requiring an advancing RX counter on every sub-interface is one line, and it belongs in the build record next to theshow ip routeoutput that got captured instead. - Alert on a sub-interface with zero received packets over a window. It is cheap, it is scriptable from the same counters, and it catches this entire fault class - the router-side VLAN id mistake and the switch-side pruning that looks identical from the router.
- Validate from the VLAN under test. The Saturday sign-off pinged a gateway successfully and could not have found this, because the laptop was on the one segment that was fine.
- Generate the interface stanza from one machine-readable VLAN table. A wiki page listing two campuses in adjacent rows is not a source of truth, it is a transposition waiting for a 02:10 maintenance window.
- Put a
descriptionon every sub-interface. A diff review can catcheth0.130described asNORTHGATE-MGMT. It cannot catch a sub-interface with no description at all, because there is nothing to contradict. - Keep one list of everything that names an interface. Firewall interface groups, monitoring targets, capture filters, scripts. Moving an interface has to move them too, and the list is the only thing that makes that reliable.
- Retire the reading that started this. Hosts on a VLAN reaching each other is a statement about the switch. It says nothing about the router and it eliminates nothing.