From e652b0f7bda1a5ea6b5d7c77c22745de5467d248 Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Mon, 6 Jul 2026 10:03:16 -0400 Subject: [PATCH] ap: parallel 2.4GHz AP (hostapd-2g) on the Linksys WUSB6300v2 dongle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second radio (wlxd8ec5e2faa8c, RTL8822BU/rtw88), same VanLink SSID+PSK, ch6 HT20, bridged into br0 next to the 5GHz AP. Runs as its own hostapd instance + own watchdog so a USB wedge on one radio never darkens the other; van-ap-watchdog now takes conf path + unit name as args (defaults unchanged). rtw88.conf disables deep power-save (rtw89 gotcha analog) and the driver's USB2->3 self-upgrade (USB3 noise in the 2.4GHz band — note: dongle currently sits in a USB3 port, so it still enumerates SuperSpeed; move to a USB2 port if 2.4GHz range disappoints). Co-Authored-By: Claude Fable 5 --- README.md | 15 ++++++++++----- ap/11-van-ap-2g.network | 11 +++++++++++ ap/hostapd-2g.conf | 25 +++++++++++++++++++++++++ ap/hostapd-2g.service | 15 +++++++++++++++ ap/rtw88.conf | 10 ++++++++++ ap/van-ap-unmanaged.conf | 6 +++--- ap/van-ap-watchdog | 15 ++++++++++----- ap/van-ap-watchdog-2g.service | 13 +++++++++++++ deploy.sh | 13 +++++++++---- 9 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 ap/11-van-ap-2g.network create mode 100644 ap/hostapd-2g.conf create mode 100644 ap/hostapd-2g.service create mode 100644 ap/rtw88.conf create mode 100644 ap/van-ap-watchdog-2g.service diff --git a/README.md b/README.md index e94551f..ff97e8f 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ This directory is the source of truth. The live system files live under `/etc`, | Web UI | **Cockpit** + `vanrouter` plugin | ### Key network facts -- LAN: `10.42.0.0/24`, gateway `10.42.0.1`, DHCP `.10–.254` — shared by Wi-Fi (`VanLink`) and the wired port `enx00e04c331140`, both bridged into `br0`. +- LAN: `10.42.0.0/24`, gateway `10.42.0.1`, DHCP `.10–.254` — shared by Wi-Fi (`VanLink`, dual-band: 5GHz `wlxc83a35a4ee55` + 2.4GHz `wlxd8ec5e2faa8c`) and the wired port `enx00e04c331140`, all bridged into `br0`. - WAN priority (metrics): **wifi 100 → eth/Starlink 200 → 4G 300** (lower = preferred). - Management / recovery: ethernet `192.168.10.251`, wifi `192.168.10.27`, ZeroTier `192.168.196.22`. - Cockpit: `https://192.168.10.251:9090` (or `.27`, or ZeroTier). Log in with a Unix account; enable *Administrative access* for action buttons. @@ -73,7 +73,11 @@ This directory is the source of truth. The live system files live under `/etc`, | `default-hostapd` | `/etc/default/hostapd` | `DAEMON_CONF=...` | | `van-ap-dnsmasq.conf` | `/etc/van-ap/dnsmasq.conf` | DHCP/DNS bound to AP iface (`bind-dynamic`, so it does not clash with systemd-resolved) | | `van-ap-dnsmasq.service` | `/etc/systemd/system/van-ap-dnsmasq.service` | dedicated dnsmasq unit (uses the `dnsmasq-base` binary; the distro dnsmasq service is NOT used) | +| `hostapd-2g.conf` | `/etc/hostapd/hostapd-2g.conf` | 2.4GHz AP (Linksys WUSB6300v2 / RTL8822BU): same SSID+PSK, ch6, HT20. Own hostapd instance so one radio wedging never kills the other. | +| `hostapd-2g.service` | `/etc/systemd/system/hostapd-2g.service` | dedicated unit for the 2.4GHz hostapd (same Restart=always / no-start-limit semantics as the 5GHz drop-in) | +| `van-ap-watchdog-2g.service` | `/etc/systemd/system/van-ap-watchdog-2g.service` | second watchdog instance: `van-ap-watchdog /etc/hostapd/hostapd-2g.conf hostapd-2g` | | `10-van-ap.network` | `/etc/systemd/network/10-van-ap.network` | brings the AP wlan up with **no** IP (it's a `br0` member; hostapd enslaves it) | +| `11-van-ap-2g.network` | `/etc/systemd/network/11-van-ap-2g.network` | same, for the 2.4GHz wlan | | `20-van-br0.netdev` | `/etc/systemd/network/20-van-br0.netdev` | creates the LAN bridge `br0` (STP off) | | `21-van-br0.network` | `/etc/systemd/network/21-van-br0.network` | static `10.42.0.1/24` on `br0` (`ConfigureWithoutCarrier`) | | `22-van-lan.network` | `/etc/systemd/network/22-van-lan.network` | enslaves the wired LAN port `enx00e04c331140` to `br0` | @@ -82,6 +86,7 @@ This directory is the source of truth. The live system files live under `/etc`, | `99-van-router.conf` | `/etc/sysctl.d/99-van-router.conf` | `net.ipv4.ip_forward=1` | | `regdomain.service` | `/etc/systemd/system/regdomain.service` | `iw reg set CA` at boot, before NetworkManager | | `rtw89.conf` | `/etc/modprobe.d/rtw89.conf` | `options rtw89_core disable_ps_mode=Y` (else AP drops beacon when idle) | +| `rtw88.conf` | `/etc/modprobe.d/rtw88.conf` | 2.4GHz dongle: `disable_lps_deep=Y` (same PS reasoning) + `switch_usb_mode=N` (don't self-upgrade to USB3 — it radiates into 2.4GHz) | ### `failover/` — multi-WAN | file | → installs to | purpose | @@ -141,12 +146,12 @@ Not touched by `deploy.sh` — the VM's LAN address/name come from `ap/van-ap-dn ## 3. Components & how to operate ### Access Point -- Change SSID / channel / password: edit `ap/hostapd.conf`, then `sudo cp` it to `/etc/hostapd/hostapd.conf` (or `./deploy.sh`) and `sudo systemctl restart hostapd`. -- Width is 80MHz VHT + HE (WiFi-6). Channel 149 (UNII-3, non-DFS). +- **Dual band, one SSID**: 5GHz (`hostapd.conf` → unit `hostapd`, ch149 80MHz VHT+HE) and 2.4GHz (`hostapd-2g.conf` → unit `hostapd-2g`, ch6 HT20, for range + 2.4-only IoT). Same SSID/PSK — clients pick their band. Deliberately two hostapd processes: a USB wedge on one radio never takes the other down, and each has its own watchdog (`van-ap-watchdog`, `van-ap-watchdog-2g`). +- Change SSID / channel / password: edit `ap/hostapd.conf` and/or `ap/hostapd-2g.conf`, then `sudo cp` to `/etc/hostapd/` (or `./deploy.sh`) and restart the matching unit. - DHCP range / DNS options: `ap/van-ap-dnsmasq.conf` + restart `van-ap-dnsmasq`. -- Services: `hostapd`, `van-ap-dnsmasq`, `systemd-networkd`, `nftables`, `regdomain`. +- Services: `hostapd`, `hostapd-2g`, `van-ap-dnsmasq`, `systemd-networkd`, `nftables`, `regdomain`. - **Do not** add `noscan` to hostapd.conf — Ubuntu's hostapd rejects it and fails to start. -- Verify it's actually beaconing with `iw dev wlxc83a35a4ee55 info` (ssid+channel+width present), not just `systemctl is-active hostapd`. +- Verify each is actually beaconing with `iw dev wlxc83a35a4ee55 info` / `iw dev wlxd8ec5e2faa8c info` (ssid+channel+width present), not just `systemctl is-active`. ### Multi-WAN failover (`van-failover`) - Probes each WAN's real internet every few seconds (HTTP-204 check, captive-portal-aware), demotes a failed WAN by raising its route-metric, fails back on recovery (hysteresis). diff --git a/ap/11-van-ap-2g.network b/ap/11-van-ap-2g.network new file mode 100644 index 0000000..d55f3ce --- /dev/null +++ b/ap/11-van-ap-2g.network @@ -0,0 +1,11 @@ +[Match] +Name=wlxd8ec5e2faa8c + +[Link] +RequiredForOnline=no + +# No IP here: like the 5GHz AP wlan (10-van-ap.network), this is a br0 member — +# hostapd-2g adds it to the bridge after putting the radio into AP mode. networkd +# must NOT enslave it; this file only brings the link up. +[Network] +ConfigureWithoutCarrier=yes diff --git a/ap/hostapd-2g.conf b/ap/hostapd-2g.conf new file mode 100644 index 0000000..b96800f --- /dev/null +++ b/ap/hostapd-2g.conf @@ -0,0 +1,25 @@ +interface=wlxd8ec5e2faa8c +# Second, independent hostapd instance (hostapd-2g.service) — 2.4GHz band of the same +# VanLink network, bridged into the same br0 segment as the 5GHz AP and the wired port. +# Separate process on purpose: a USB wedge on one radio must never take down the other. +bridge=br0 +# Same ctrl dir as the 5GHz AP — sockets are per-interface, hostapd_cli/watchdog use -i. +ctrl_interface=/var/run/hostapd +driver=nl80211 +# Same SSID + PSK as 5GHz: dual-band clients pick a band themselves, 2.4-only IoT +# devices just join. Rename here (e.g. VanLink-2G) if band pinning is ever needed. +ssid=VanLink +country_code=CA +ieee80211d=1 +hw_mode=g +channel=6 +wmm_enabled=1 +# HT20 only — 40MHz in 2.4GHz overlaps most of the band and coexistence would force +# it back to 20MHz near any neighbor anyway. +ieee80211n=1 +ht_capab=[LDPC][SHORT-GI-20] +auth_algs=1 +wpa=2 +wpa_passphrase=1foot11foot11 +wpa_key_mgmt=WPA-PSK +rsn_pairwise=CCMP diff --git a/ap/hostapd-2g.service b/ap/hostapd-2g.service new file mode 100644 index 0000000..f6c3c43 --- /dev/null +++ b/ap/hostapd-2g.service @@ -0,0 +1,15 @@ +[Unit] +Description=VanLink 2.4GHz AP (hostapd on wlxd8ec5e2faa8c, RTL8822BU) +# Same USB re-enumeration hazards as the 5GHz AP (see hostapd-restart.conf for the +# full story): never stop retrying, and back off a few seconds so the USB device can +# re-enumerate before the next attempt. +StartLimitIntervalSec=0 +After=network.target + +[Service] +ExecStart=/usr/sbin/hostapd /etc/hostapd/hostapd-2g.conf +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/ap/rtw88.conf b/ap/rtw88.conf new file mode 100644 index 0000000..b552803 --- /dev/null +++ b/ap/rtw88.conf @@ -0,0 +1,10 @@ +# 2.4GHz AP dongle (Linksys WUSB6300 v2, RTL8822BU, rtw88_8822bu). +# Deep power-save has no business on an always-on AP radio — same reasoning as the +# rtw89 disable_ps_mode gotcha on the 5GHz dongle (rtw89.conf). +options rtw88_core disable_lps_deep=Y +# Don't let the driver switch a USB2-enumerated dongle up to USB3: USB3 signalling +# radiates broadband noise right across the 2.4GHz band, and HT20 rates fit in USB2 +# easily. NOTE: this only blocks the driver's own USB2->3 switch — a dongle sitting in +# a USB3 port still enumerates at SuperSpeed (it currently does); move it to a USB2 +# port if 2.4GHz range/interference ever looks poor. +options rtw88_usb switch_usb_mode=N diff --git a/ap/van-ap-unmanaged.conf b/ap/van-ap-unmanaged.conf index 9901a1c..2d966fd 100644 --- a/ap/van-ap-unmanaged.conf +++ b/ap/van-ap-unmanaged.conf @@ -1,4 +1,4 @@ [keyfile] -# The AP wlan and the wired LAN port are both owned by systemd-networkd/hostapd -# (bridged into br0), so NetworkManager must not touch either of them. -unmanaged-devices=interface-name:wlxc83a35a4ee55;interface-name:enx00e04c331140 +# The AP wlans (5GHz + 2.4GHz) and the wired LAN port are all owned by +# systemd-networkd/hostapd (bridged into br0), so NetworkManager must not touch them. +unmanaged-devices=interface-name:wlxc83a35a4ee55;interface-name:wlxd8ec5e2faa8c;interface-name:enx00e04c331140 diff --git a/ap/van-ap-watchdog b/ap/van-ap-watchdog index 418ba00..3880355 100644 --- a/ap/van-ap-watchdog +++ b/ap/van-ap-watchdog @@ -18,6 +18,10 @@ exited, so Restart=always never fired. The link check catches exactly that. If t unhealthy for `fail_threshold` checks in a row, it clears any failed state and restarts hostapd. If the interface is simply gone (mid re-enumeration) it waits — there is nothing to restart onto, and Restart=always reclaims it when it reappears. Stdlib only. + +Usage: van-ap-watchdog [hostapd.conf path] [systemd unit] +Defaults watch the 5GHz AP (/etc/hostapd/hostapd.conf, unit hostapd); the 2.4GHz +instance (van-ap-watchdog-2g.service) passes hostapd-2g.conf + hostapd-2g. """ import re @@ -26,7 +30,8 @@ import sys import time from pathlib import Path -HOSTAPD_CONF = "/etc/hostapd/hostapd.conf" +HOSTAPD_CONF = sys.argv[1] if len(sys.argv) > 1 else "/etc/hostapd/hostapd.conf" +HOSTAPD_UNIT = sys.argv[2] if len(sys.argv) > 2 else "hostapd" CTRL_DIR = "/var/run/hostapd" INTERVAL = 15 # seconds between health checks (backstop cadence, not first response) FAIL_THRESHOLD = 2 # consecutive bad checks before restarting (debounces re-enum blips) @@ -98,15 +103,15 @@ def ap_enabled(ifname): def recover(ifname): - log(f"AP {ifname} not beaconing — clearing failed state and restarting hostapd", "warn") + log(f"AP {ifname} not beaconing — clearing failed state and restarting {HOSTAPD_UNIT}", "warn") # reset-failed first in case some other path parked the unit in a failed state; # harmless when it isn't. - subprocess.run(["systemctl", "reset-failed", "hostapd"], + subprocess.run(["systemctl", "reset-failed", HOSTAPD_UNIT], capture_output=True, text=True) - r = subprocess.run(["systemctl", "restart", "hostapd"], + r = subprocess.run(["systemctl", "restart", HOSTAPD_UNIT], capture_output=True, text=True) if r.returncode != 0: - log(f"hostapd restart returned {r.returncode}: {r.stderr.strip()}", "warn") + log(f"{HOSTAPD_UNIT} restart returned {r.returncode}: {r.stderr.strip()}", "warn") def main(): diff --git a/ap/van-ap-watchdog-2g.service b/ap/van-ap-watchdog-2g.service new file mode 100644 index 0000000..ac617ac --- /dev/null +++ b/ap/van-ap-watchdog-2g.service @@ -0,0 +1,13 @@ +[Unit] +Description=VanLink 2.4GHz AP watchdog — recover hostapd-2g if the radio wedges after a USB glitch +After=hostapd-2g.service +Wants=hostapd-2g.service + +[Service] +Type=simple +ExecStart=/usr/local/sbin/van-ap-watchdog /etc/hostapd/hostapd-2g.conf hostapd-2g +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/deploy.sh b/deploy.sh index fa5e66a..e7ee87a 100755 --- a/deploy.sh +++ b/deploy.sh @@ -13,6 +13,11 @@ install -D -m0644 ap/hostapd-restart.conf /etc/systemd/system/hostapd.service. install -D -m0644 ap/default-hostapd /etc/default/hostapd install -D -m0755 ap/van-ap-watchdog /usr/local/sbin/van-ap-watchdog install -D -m0644 ap/van-ap-watchdog.service /etc/systemd/system/van-ap-watchdog.service +install -D -m0644 ap/hostapd-2g.conf /etc/hostapd/hostapd-2g.conf +install -D -m0644 ap/hostapd-2g.service /etc/systemd/system/hostapd-2g.service +install -D -m0644 ap/11-van-ap-2g.network /etc/systemd/network/11-van-ap-2g.network +install -D -m0644 ap/van-ap-watchdog-2g.service /etc/systemd/system/van-ap-watchdog-2g.service +install -D -m0644 ap/rtw88.conf /etc/modprobe.d/rtw88.conf install -D -m0644 ap/van-ap-dnsmasq.conf /etc/van-ap/dnsmasq.conf install -D -m0644 ap/van-ap-dnsmasq.service /etc/systemd/system/van-ap-dnsmasq.service install -D -m0644 ap/10-van-ap.network /etc/systemd/network/10-van-ap.network @@ -85,7 +90,7 @@ systemctl mask systemd-networkd-wait-online.service >/dev/null 2>&1 || true # pick up the lid drop-in (re-execs logind; does NOT drop the network) systemctl restart systemd-logind >/dev/null 2>&1 || true systemctl unmask hostapd >/dev/null 2>&1 || true -systemctl enable regdomain.service hostapd van-ap-dnsmasq nftables systemd-networkd van-failover van-thermal van-battery van-ap-watchdog >/dev/null 2>&1 || true +systemctl enable regdomain.service hostapd hostapd-2g van-ap-dnsmasq nftables systemd-networkd van-failover van-thermal van-battery van-ap-watchdog van-ap-watchdog-2g >/dev/null 2>&1 || true systemctl restart van-thermal van-battery # Heartbeat: only enable/start once the client binary is installed (README §4). if [ -x /home/andreas/bin/hbc ]; then @@ -98,9 +103,9 @@ nmcli general reload 2>/dev/null || systemctl reload NetworkManager 2>/dev/null # restart in dependency order: br0 + AP iface + LAN member first, then hostapd adds # the wlan to br0, then dnsmasq binds br0, then NAT/failover systemctl restart systemd-networkd -systemctl restart hostapd van-ap-dnsmasq nftables van-failover -# AP watchdog last, after hostapd is back up (it only ever restarts a wedged hostapd) -systemctl restart van-ap-watchdog +systemctl restart hostapd hostapd-2g van-ap-dnsmasq nftables van-failover +# AP watchdogs last, after hostapd is back up (they only ever restart a wedged hostapd) +systemctl restart van-ap-watchdog van-ap-watchdog-2g networkctl reload 2>/dev/null || true echo