VLANs
Design Philosophy
Three VLANs segment traffic by trust level and use case. IoT devices get the strictest isolation since they run unknown/research firmware. Trusted devices get full internet access through Mullvad VPN. Lab devices can communicate freely within the VLAN and have controlled outbound access.
VLAN Definitions
VLAN 10 — Lab
Purpose: Infrastructure and compute devices that stay in (or near) the lab.
| Property | Value |
|---|---|
| Subnet | 192.168.10.0/24 |
| Gateway | 192.168.10.1 |
| DNS | 192.168.10.1 (router) |
| Internet | Direct (no VPN) |
| Inter-VLAN | → Trusted: blocked. → IoT: blocked. |
Devices: Proxmox node, Raspberry Pis, dell-mini, acer-lap, hp-lap (Debian/XFCE)
VLAN 20 — Trusted
Purpose: Personal devices — phones, laptops, daily drivers.
| Property | Value |
|---|---|
| Subnet | 192.168.20.0/24 |
| Gateway | 192.168.20.1 |
| DNS | 192.168.20.1 (router) |
| Internet | Switchable: Mullvad (trusted-vpn on) or university WAN (trusted-vpn off) |
| Inter-VLAN | → Lab: blocked by default (allow specific services as needed). → IoT: blocked. |
Devices: MacBook M1 Pro, Dell XPS 16, Pixel 6, Pixel 3, iPhone 7 (all on dfair_lab as of 2026-10-08)
VLAN 20 is selectable between VPN and WAN modes using
trusted-vpnon Flint 2. VPN-mode routed IPv4 traffic uses Mullvad and a WAN guard; a controlled Mullvad outage test passed. Router cold-boot and alternate-egress audits are still pending. See Mullvad.
VLAN 30 — IoT
Purpose: Research IoT devices with untrusted/experimental firmware.
| Property | Value |
|---|---|
| Subnet | 192.168.30.0/24 |
| Gateway | 192.168.30.1 |
| DNS | 192.168.30.1 (router) or dedicated resolver |
| Internet | Blocked by router-side iot → wan forwarding removal on 2026-10-22; client verification pending |
| Inter-VLAN | Fully isolated — no access to Lab or Trusted. |
Devices: Research IoT hardware, Samsung phone (for IoT testing), TP-Link OpenWrt AP
IoT devices connect wirelessly to the OpenWrt AP on a dedicated SSID. This AP uplinks to the switch on a VLAN 30 access port.
Inter-VLAN Traffic Policy
flowchart LR
L10[VLAN 10\nLab]
L20[VLAN 20\nTrusted]
L30[VLAN 30\nIoT]
WAN[🌐 Internet]
VPN[Mullvad VPN]
L10 -->|Direct| WAN
L20 -->|Tunneled| VPN --> WAN
L30 -.->|Blocked by firewall| WAN
L10 -. blocked .- L20
L10 -. blocked .- L30
L20 -. blocked .- L30
L20 -.->|"selective allow\n(e.g. Proxmox UI)"| L10
| Source | Destination | Policy |
|---|---|---|
| Lab | Trusted | ❌ Blocked |
| Lab | IoT | ❌ Blocked |
| Trusted | Lab | ⚠️ Selective (e.g. allow :8006 for Proxmox) |
| Trusted | IoT | ❌ Blocked |
| IoT | Lab | ❌ Blocked |
| IoT | Trusted | ❌ Blocked |
| Any | Internet | See per-VLAN policy above |
VLAN 30 Internet block (2026-10-22)
Removed iot → wan forwarding on the Flint 2. The active network is
br-lan.30 (192.168.30.0/24), firewall zone iot with input and
forward policies DROP. fw4 check passed and firewall was reloaded.
VLAN 10 and 20 WAN forwardings were preserved. Client-side IPv4/IPv6
connectivity tests remain pending. Router-side DHCP/DNS exception was
not changed; see firewall details.
Rollout Plan (Phased)
Earlier attempt at doing all VLANs in one pass on the Flint 2 failed at the
switch/router VLAN tagging step. Flint 2 runs stock OpenWrt (not GL.iNet's
own firmware), which uses DSA + bridge-vlan-filtering rather than classic
swconfig — tagging must be done via br-lan VLAN filtering + per-port
tagged/untagged bridge-vlan members, not per-interface .VLANID subinterfaces.
Rolling out incrementally to isolate failures to one layer at a time:
- [x] Phase 1 — Flint 2 as plain main router (flat LAN, no VLANs). Replaces
TP-Link as the WAN-facing device. Confirms WAN/NAT/DHCP works before any
switch complexity is introduced.
Notes: TL-SG108E needed a factory reset (leftover VLAN config from the
earlier failed attempt was blocking L2 forwarding entirely — 802.1Q mode
is currently
Disable). Proxmox is reachable via static192.168.1.197(falls inside the.100–.249DHCP pool — flag for exclusion/reservation later). Acer/HP/Dell-mini debian boxes skipped for now (no network config done on them yet, not blocking). - [x] Phase 2a — VLAN 30 (IoT) interface live on the Flint 2. Enabled
vlan_filteringonbr-lan, taggedlan1(2.5G uplink) for VLAN 1 + 30, leftlan2-lan5untagged/PVID in VLAN 1.br-lan.1(192.168.1.1) andbr-lan.30(192.168.30.1) both up, SSH survived the reload. Root cause of every prior lockout on this exact router (GL-MT6000): oncevlan_filteringis enabled, thelaninterface must bind to the VLAN 1 subinterface (option device 'br-lan.1'), not the raw bridge device (br-lan). Binding to the raw bridge while filtering is active is what caused the "changes apply, device goes unreachable" failure that led to repeated reflashes in the earlier attempt — confirmed as a known GL-MT6000-specific gotcha via the OpenWrt forum, not user error. (See: forum.openwrt.org/t/vlan-config-problems-on-gl-inet-gl-mt6000/200882 and forum.openwrt.org/t/gl-inet-flint-2-gl-mt6000-vlan-best-practices-on-openwrt-snapshot/251673) - [x] Phase 2b — DHCP server on
iotinterface (.100-.249, 12h lease), firewall zone (input/forwardDROP by default, explicit allow only for DHCP/DNS-to-router; initial forwarding towanwas removed 2026-10-22). Switch VLAN 30 added via native-VLAN-1 pattern (see above) — zero disruption to existing traffic. Switch port 6 is now VLAN 30 untagged/PVID 30, ready for the IoT AP. (Its VLAN 1 untagged membership was left in place by mistake until 2026-10-08 — removed, port 6 is now VLAN 30 only.) Old TP-Link (15.05.1 "Chaos Calmer", ar71xx — its own switch/VLAN config untouched, only became a dumb AP) reconfigured: LAN static192.168.30.2, own DHCP server disabled (dhcp.lan.ignore=1), WAN disabled (proto=none), wired into switch port 6. Verified end-to-end: phone joined itsOpenWrtSSID, got a192.168.30.xlease from the Flint 2, and had internet access at that time (prior to the 2026-10-22 WAN block). Phase 2 complete. - [x] Phase 3a — VLAN 10 (Lab) and VLAN 20 (Trusted) router-side infra
live:
br-lan.10(192.168.10.1),br-lan.20(192.168.20.1), DHCP scopes (.100-.200), firewall zones (ACCEPT/ACCEPT/ACCEPT, forward towanonly, no cross-zone rules yet). Hostname set toflint2(was causing tab mix-ups with the identically-prompted TP-Link). No devices migrated yet, no switch access ports assigned — infra only, per the decision to stand up VLANs before moving working devices. - [x] Phase 3b — Switch: VLAN 10 + 20 added to 802.1Q table, tagged port 1 only (trunk), no access ports assigned. Verified zero disruption — VLAN 1 and VLAN 30 traffic unaffected.
- [x] Phase 3c — Device migration done for core infra.
dfair_labSSID live on both radios, bound to VLAN 20. Idle debian boxes (Dell Micro/Acer/HP) switch-migrated to VLAN 10 (no OS network config on them yet — not blocking). Proxmox host + its VM/LXC guests migrated to VLAN 10 — key lesson: the Proxmox host's ownvmbr0IP is independent of each VM/LXC's guest-level network config; migrating the host doesn't migrate the guests. LXCs onip=dhcp(nas) picked up new leases on reboot automatically; VMs (gha-general-01, monitor-01) needed the same via their own guest OS, reachable only through the Proxmox noVNC console once the host itself was unreachable over the network. Scopedlan/trusted→ Lab firewall rules added per-host as needed (proxmox :8006/:22, monitor-01 :3000/:3001 for Grafana/Prometheus, gha-general-01 :22) — see devices/README.md for the full table. gha-general-01 given a static DHCP reservation (was floating DHCP, now pinned to its MAC). MacBook Pro reconnected todfair_lab(VLAN 20), confirmed. All 10 current IoT devices given static DHCP host reservations (MAC-pinned) so addresses don't drift — see devices/README.md. Completed 2026-10-25: Mullvad selective egress and switchable VLAN 20 kill switch; router cold-boot validation remains outstanding. - [ ] Phase 3d — OS-level networking for the 3 idle debian boxes (Dell
Micro/Acer/HP), tracked in issue #19.
dell-miniandacer-lapdone:systemd-networkd(chosen for a config method that works the same whether a box is headless or running a desktop env like HP's XFCE, instead of per-box ifupdown/NetworkManager), DHCP host reservations pinned to192.168.10.117/.199,lan/trusted→:22firewall rules added.acer-lapis run closed-lid and headless (rack-mounted laptop, built-in panel disconnected). The lid-switch fix was originally documented but never actually applied — it suspended and dropped off the network once the external display was unplugged. Fixed 2026-10-08 with a drop-in/etc/systemd/logind.conf.d/10-headless.conf(HandleLidSwitch/ExternalPower/Docked,HandleSuspendKey,HandleHibernateKey,IdleActionallignore), masked sleep/suspend/ hibernate targets, and default target set tomulti-user.target; verified withsystemd-analyze cat-configand a refusedsystemctl suspend; survives a reboot.hp-lapdone 2026-10-08: was on the IoT AP's WiFi (VLAN 30) — moved to wiredenp0s25viasystemd-networkd(.networkfile matched on MAC), NetworkManager disabled, WiFi rfkill-blocked, DHCP host reservation pinned to192.168.10.147,Allow-LAN-to-HP-Lap/Allow-Trusted-to-HP-Lap:22rules added, same logind drop-in + masked sleep targets as acer-lap. Hostname renamedpoolTab→hp-lap. Still on Debian 12 (bookworm); upgrade to 13 to match the other boxes is a follow-up.
TODO / Implementation Notes
- [x] Configure 802.1Q VLAN tagging on TL-SG108E
- [x] Create VLAN interfaces on GL.iNet Flint 2 (stock OpenWrt — DSA bridge-vlan-filtering via UCI, not LuCI — see Phase 2a note on the GL-MT6000 lockout bug)
- [x] Create SSID for VLAN 20 on Flint 2 WiFi (
dfair_lab, both radios). VLAN 30 uses the old TP-Link's own AP instead of a Flint 2 radio SSID; VLAN 10 is wired-only per design, no SSID needed. - [x] Configure DHCP server per VLAN on router (10, 20, 30 all live; static host reservations for known devices)
- [x] Set up Mullvad WireGuard and optional Trusted VLAN routing (2026-10-25;
trusted-vpn on|off) - [x] Configure and outage-test nftables WAN egress guard for VLAN 20 (router boot-order test pending)
- [x] Set up OpenWrt IoT AP with VLAN 30 (simplified from the original "SSID + trunk" plan to a dumb-AP-on-a-single-access-port — no trunking needed on the AP itself since it only ever serves one VLAN)