pve-test was briefly clustered with pve1 then deliberately de-clustered so it could move to wifi-primary networking (4addr bridge mode, bonded with a wired LAN backup) - a change not achievable while clustered given corosync's latency requirements. Captures that as a reproducible script plus docs: cluster separation procedure, the wifi network design and the live-cutover pitfalls hit along the way, and node-role/history context. Also adds CLAUDE.md guardrails for pve1 (production) vs pve-test (sandbox) - this repo had none before, despite scripts here being able to make real changes to both. Separately: both nodes' mgmt firewalls were dropping ICMP by default (TCP 22/8006 only), which looked like an outage mid-troubleshooting even though SSH/web UI were fine. Added an explicit ping-allow rule to the firewall template, applied it live on both nodes, and added an audit.sh check so it stays enforced.
9.4 KiB
pve-test's Wifi-Primary Network
Non-standard, deliberately chosen against normal Proxmox guidance (a
hypervisor's management network is not supposed to be wireless). Applies
only to pve-test as a standalone sandbox node — see
05-node-roles.md for why this and cluster membership don't mix, and
never apply this pattern to a node that's joined (or might join) a
cluster.
Why this works at all
Wifi (802.11) normally cannot be bridged the way Ethernet can: an access point only accepts frames whose source MAC matches the MAC that associated with it. A Linux bridge with a wifi port as a member sends frames tagged with whatever MAC actually generated them (the bridge's own MAC, a VM's MAC, etc.) — none of which match the wifi card's hardware MAC, so the AP silently drops them. This isn't a Linux/Proxmox limitation, it's how WiFi association works.
4addr (WDS) client mode is the exception: if both the wifi driver and the AP support it, the client tags frames with a 4th address field carrying the original MAC, and the AP forwards them like a real Ethernet segment. Most consumer routers do not support this (it's common on OpenWrt/DD-WRT/enterprise APs, rare on stock ISP hardware) — it has to be confirmed empirically, per-AP, before trusting it with a management IP.
pve-test's card (Intel AX200, iwlwifi driver, interface wlp3s0)
supports 4addr client-side generically (this is a mac80211 core
feature, not driver-specific). The home AP it associates to
(nbn-fttp-net-5G) was confirmed to honor it — see validation method
below.
Validate before ever touching a live management IP
Don't add the wifi NIC straight into your real bridge to "see if it works" — if 4addr isn't actually honored, you lose access to the box with no diagnostic trail (association succeeds; only the data plane silently fails). Test in an isolated scratch namespace first:
# assumes wlp3s0 already associated with 4addr on (see systemd unit below)
ip link add testbr0 type bridge && ip link set testbr0 up
ip link set wlp3s0 master testbr0 && ip link set wlp3s0 up
ip netns add wifitest
ip link add veth-host type veth peer name veth-ns
ip link set veth-host master testbr0 && ip link set veth-host up
ip link set veth-ns netns wifitest
ip netns exec wifitest ip link set veth-ns address 02:11:22:33:44:55 # deliberately NOT the wifi card's MAC
ip netns exec wifitest ip link set veth-ns up
ip netns exec wifitest ip link set lo up
ip netns exec wifitest dhclient -v -1 veth-ns # real DHCP lease = AP forwards foreign MACs = 4addr genuinely works
If that gets a lease (or at minimum a ping reply from the gateway) from a MAC that was never the one that associated, the AP is forwarding arbitrary-MAC frames — real bridge mode will work. If not, don't proceed to bridge mode; fall back to a routed/NAT design instead (wifi gets its own IP via DHCP, VMs NAT out through it — never covered here since it wasn't needed, but keep this fallback in mind if reproducing on different hardware/AP).
Tear the test namespace/bridge down afterwards (ip netns del wifitest; ip link del veth-host; ip link set wlp3s0 nomaster; ip link del testbr0)
— it's scratch, not part of the real config.
Final architecture
wlp3s0 (wifi, 4addr client mode) ─┐
├─ bond0 (active-backup, wlp3s0 primary) ─ vmbr0 (192.168.2.251/24)
nic0 (wired, LAN backup) ─┘
wpa-4addr-<iface>.service(systemd unit,Before=network-pre.target) — sets 4addr mode and startswpa_supplicantbefore ifupdown2 processes/etc/network/interfaces. Deliberately not usingwpasupplicant's ownwpa-confifupdown2 hook integration — its ordering relative to a custompre-up iw ... set 4addr online is implementation-specific and not worth gambling on for something this hard to debug if it's wrong. The systemd unit gives full, deterministic control over sequencing instead.bond0— active-backup bonding,wlp3s0asbond-primary,nic0as backup,bond-miimon 100. Bonding a wifi interface works here becausemac80211/iwlwifiproperly reports carrier state (netif_carrier_on/off) on association/disassociation, which is what bonding'smiimon(with the defaultuse_carrier=1) actually watches — it doesn't require a "real" MII-capable NIC.vmbr0— unchanged IP (192.168.2.251/24), bridged overbond0instead of directly over a physical NIC.
Reproduce with scripts/setup-wifi-bond-network.sh — see its header
comment for usage and required env vars (SSID/passphrase, management
address/gateway). It stages /etc/network/interfaces and the systemd
unit but does not auto-apply (ifreload -a) — see the next section
for why that step needs care, not automation.
Applying a live bridge-port change: what went wrong once, and what worked
Moving vmbr0's underlying port live — while SSH'd in over the very
address that lives on that bridge — is inherently risky: if the new port
doesn't actually pass traffic, you lose the connection you're using to
fix it.
What failed: doing the port swap via raw ip link set nic0 nomaster
ip link set nic0 down(withwlp3s0already added as a second bridge member) caused an extended outage that needed a physical power-cycle to recover —nic0never came back up on its own even well past a 60s auto-revert watchdog's deadline. Importantly, the host itself never crashed —journalctl -b -1showed completely normal operation (apvestatdpolling loop, no gaps, no panic/OOM/watchdog trigger) right up until a keyboard was plugged in and it was manually reset. So the failure was specifically in the live network transition, not the OS — root cause unconfirmed, but plausibly bridge FDB/MAC-identity handling when a port carrying an already-live IP is swapped out via rawip linkrather than a coordinated reconfiguration.
What worked: writing the final state into /etc/network/interfaces
and applying with ifreload -a (Proxmox's own supported hot-reload path)
instead. Same end state, same risk window, but ifupdown2 evidently
sequences the transition in a way raw ip link surgery didn't. This
succeeded on the first retry using this method.
Either way, expect ~30-60 seconds of apparent breakage even on a
successful change — the upstream switch/AP needs to relearn which port
192.168.2.251's MAC now lives behind. Don't judge success/failure
before that window passes; a ping/SSH failure at the 5-10s mark is not
yet a sign anything is wrong.
Recommended safety net for any future live change here: arm a backgrounded auto-revert-on-timeout before applying, e.g.:
cp /etc/network/interfaces /etc/network/interfaces.bak
cat > /root/revert.sh <<'EOF'
#!/bin/bash
cp /etc/network/interfaces.bak /etc/network/interfaces
ifreload -a
EOF
chmod +x /root/revert.sh
setsid nohup bash -c "sleep 45 && /root/revert.sh" >/root/revert.log 2>&1 < /dev/null &
disown
# now apply the real change, e.g.: ifreload -a
# once confirmed working: pkill -f "sleep 45 && /root/revert.sh"
This only self-heals if the box stays responsive enough for the backgrounded job to run to completion — it is not a substitute for physical/console access being available, just a way to avoid needing it for the common case.
Troubleshooting: "can't ping it, web UI won't load" isn't necessarily an outage
Two independent things can make pve-test look dead when it isn't:
- Stale ARP after any change to
vmbr0's active port.vmbr0's MAC address follows whichever interface is currently active inbond0(wlp3s0's MAC while wifi is primary,nic0's if it fails over) — it is not fixed. Other devices on the LAN (including your own workstation) that cached the old MAC before a change won't notice until their ARP entry naturally expires or gets flushed (ip neigh flush <ip>/arp -d <ip>). Symptom: intermittent or totally dead connectivity from one specific device while others (or the host itself) are fine. - ICMP ping is blocked by the management firewall, unrelated to
network health. See
04-security-hardening.md's firewall section —cluster.fw'spolicy_in: DROPonly explicitly allows TCP 22/8006 (now also ICMP echo-request, after this was hit) from the mgmt network. Before that rule was added,pingfailed 100% even with a perfectly correct, fresh ARP entry and full SSH/web UI access — nothing to do with wifi, bonding, or the network being down. This is exactly what happened once: alarming "can't ping, web UI not loading" turned out to be a stale-ARP moment (which resolved itself) plus a pre-existing firewall policy (ping was never going to work, regardless of wifi).
When pve-test seems unreachable, check in this order before assuming
a real outage: SSH (ssh root@pve-test.sweet.home) → web UI via curl -sk -o /dev/null -w '%{http_code}' https://pve-test.sweet.home:8006/ →
only then worry about ping specifically, and check
/etc/pve/firewall/cluster.fw before blaming the network.
Known limitation history
nic0 (the bond's backup slave) initially had no cable physically
connected when this was first set up — the bond was correctly configured
but inert until a cable was plugged in. As of this writing a cable is
connected and the backup slave is live (confirm with cat /proc/net/bonding/bond0) — if reproducing this setup, verify the same
before assuming LAN failover will actually work.