Two-layer structure: Sources (raw notes) + Wiki (compile output) Four domains: Dev (40), Venture (3), Homelab (23), Reference (0) Includes CLAUDE.md spec, index pages at all levels, compile log Co-Authored-By: Lovebug <lovebug@herbylab.dev>
5.8 KiB
| created | path | project | status | tags | type | updated | ||
|---|---|---|---|---|---|---|---|---|
| 2026-05-08 | Sources/Homelab | homelab-vlan-renumber | completed |
|
session-notes | 2026-05-08 |
Homelab VLAN Renumber — Switch Chip to Bridge Alignment
Outcome
Renumbered OpenWrt's hardware switch chip VLANs to match the bridge VLAN filtering layer. Lab and private VLANs now use the same VID end-to-end (11 and 21) from the wire through the chip, sub-interface, and bridge. Unblocked Phase 1.3 — Knot LXC successfully reaches 10.0.11.1 over a tagged trunk from PVE.
The Problem
OpenWrt was running two layers of VLAN configuration with different VID numbering:
- Switch chip (swconfig): VID 1, 2, 3 (lab), 4 (private)
- Bridge VLAN filtering: VID 11, 21, 31, 61, 71
Bridge VLAN filtering rules quietly translated between them via :u* (untagged + PVID) on the sub-interface ports. NAS frames came up tagged VID 3 from the chip, hit eth1.3 configured as :u* on bridge VID 11 — bridge applied PVID 11 to ingressing untagged frames. PVE same pattern: VID 4 → eth1.4:u* → bridge PVID 21.
The translation worked, but it meant configuring PVE to send tag=11 for "VLAN 11 lab" actually required sending tag=3 (the chip's wire VID), which is the kind of hidden mismatch that loses an hour of debugging six months later.
The Architecture Change
Switch chip — added entries with bridge-matching VIDs:
VLAN 11: ports='3 4t 6t' (NAS untagged on 3, PVE tagged on 4, CPU tagged on 6)
VLAN 21: ports='4 6t' (PVE untagged on 4, CPU tagged on 6)
Linux — created explicit 8021q sub-interfaces:
config device
option type '8021q'
option ifname 'eth1'
option vid '11'
option name 'eth1.11'
config device
option type '8021q'
option ifname 'eth1'
option vid '21'
option name 'eth1.21'
These were created explicitly rather than relying on the legacy implicit eth1.<N> auto-creation. The previous eth1.3 and eth1.4 ports never had explicit device entries — Linux auto-spawned them when referenced in the bridge ports list. The explicit form is the modern OpenWrt-recommended pattern and makes the config self-documenting.
Bridge — homebridge ports updated:
ports='eth1.3' → ports='eth1.21' 'eth1.11'
(eth1.3 retained for ports 1/2 still on legacy VID 3 — see Open Items.)
Bridge VLAN filtering — sub-interfaces wired to matching VIDs:
VID 11: 'eth1.11' tagged member, 'eth1.3:u*' (NAS legacy translation, retained)
VID 21: 'eth1.21:u*' (PVE untagged + PVID)
How the Migration Stayed Live
Parallel-build pattern. Built the new VID 21 plumbing alongside the existing VID 4 path before tearing anything down. PVE management kept flowing on the legacy path until the new path was verified, then eth1.4 was removed in one cutover. Same approach for VID 11. PVE never dropped, NAS never dropped.
Hybrid Trunk on PVE Port
Port 4 ended as a hybrid trunk: VID 21 untagged (PVE management default) + VID 11 tagged (LXC traffic). This required the LXC's tag=11 parameter to match the wire VID for the first time — previously this would have needed tag=3 due to the translation layer.
Key Learnings
- swconfig VIDs are real wire VIDs. Earlier in the session I (Claude) misframed them as "internal slots" — they're actual 802.1Q tags on frames between the switch silicon and the CPU. The number you see in
swconfigis the number on the wire. - Tagged port ≠ port adds tag. Tagged means "expect frames to already carry an 802.1Q tag and preserve it." The sending device adds the tag. PVID on an untagged port is what tags ingressing untagged frames.
- VLANs only do anything when something downstream enforces them. A VID on a frame is just a number unless a switch or bridge filters on it. The original setup "worked" because the bridge wasn't VLAN-aware yet — turning on
bridge-vlan-aware yesis what surfaced the latent chip/bridge mismatch. - Two VID namespaces stacked on top of each other is a footgun. The translation pattern works but creates cognitive overhead every time you debug. Aligning the layers eliminates a class of "which number do I use here?" questions forever.
- Explicit
8021qdevice entries beat implicit sub-interface auto-spawn. Self-documenting, idiomatic for current OpenWrt, easier to grep for in the config. The implicit form is legacy and hides the dependency on bridgeportslist references. uci add_listis non-destructive on bridge ports. Adding a sub-interface to a bridge port list during a parallel build doesn't disrupt existing members. Both paths run side-by-side until the old one is removed.- Parallel-build > rename for live infrastructure. Renaming
eth1.4→eth1.21in place would have required atomically updating the device, bridge, and switch_vlan in one reload. Buildingeth1.21alongside, verifying, then removingeth1.4is recoverable at every step.
Open Items
- Switch chip still has VID 3 with ports 1 and 2 as members (link down on 1, link up on 2). Need to identify what's on port 2 before retiring VID 3 entirely.
- Cosmetic:
eth1.21listed as redundant tagged member on bridge VID 11 (harmless, future cleanup). - Cosmetic: switch_vlan descriptions still say
lab-new/private-new. Should rename tolab/privateonce VID 3 is fully retired. - Document new wiring on Knot LXC — Phase 1.3 resume notes can now move from "blocked" to "infrastructure ready."
Final State
| Layer | VID 11 (lab) | VID 21 (private) |
|---|---|---|
| Switch chip | 3 4t 6t |
4 6t |
| Sub-interface | eth1.11 (explicit 8021q) |
eth1.21 (explicit 8021q) |
| Bridge port | eth1.11 tagged, eth1.3:u* legacy |
eth1.21:u* |
| End-to-end VID | 11 (NAS untagged on chip, all else 11) | 21 (PVE untagged on chip, all else 21) |
NAS reachable, PVE management reachable, Knot LXC pinging gateway. Phase 1.3 unblocked.