WLAN neu mit OpenWrt: vier Access Points, VLANs je Funknetz und Roaming mit usteer

Schema: Vier Zyxel-Access-Points mit OpenWrt 25.12 spannen über VLAN-Trunks getrennte Funknetze für Hauptnetz, Gäste und IoT auf und liefern Daten an VictoriaLogs und Checkmk

Verfasst von

in

Bisher teilten sich drei Huawei WiFi AX3 in einem abgetrennten Altnetz und eine FRITZ!Box 7590 AX als vierte Funkzelle das WLAN. Keines der vier Geräte konnte Funknetze auf VLANs legen, für die Huawei-Router gab es wegen des signierten Bootloaders kein OpenWrt und die 61 bis 67 Clients verteilten sich ungesteuert auf zwei getrennte Aufbauten. Vier Zyxel T-56 mit OpenWrt 25.12.6 übernehmen jetzt den gesamten Funkbetrieb, trennen Hauptnetz, Gäste und IoT-Geräte per VLAN über dieselben vier Zellen und schicken Roaming- sowie Funkdaten an Checkmk und einen zentralen Log-Server.

Schema: Vier Zyxel-Access-Points mit OpenWrt 25.12 spannen über VLAN-Trunks getrennte Funknetze für Hauptnetz, Gäste und IoT auf und liefern Daten an VictoriaLogs und Checkmk

Hardware und Umbau zum reinen Access Point

Der Zyxel T-56 ist eine Anbieter-Variante des Zyxel EX5601-T1, entspricht also dem EX5601-T0 ohne SFP+-Käfig. Darin stecken ein MediaTek MT7986a (Filogic, vier Cortex-A53-Kerne), 1 GiB DDR4-RAM und 512 MiB SPI-NAND, wovon im U-Boot-Layout von OpenWrt rund 400 MB frei bleiben. Für das WLAN sorgt ein MT7976 mit zwei Funkeinheiten (4×4 Wi-Fi 6 auf 2,4 GHz und 5 GHz). Dazu kommen fünf Ethernet-Ports: der WAN-Port (eth1) und lan1 unterstützen 2,5 Gbit/s, lan2 bis lan4 laufen mit 1 Gbit/s. PoE beherrscht das Gerät nicht, es braucht an jedem Standort das mitgelieferte 12-Volt-Netzteil.

Auf allen vier Geräten lief bei der Anlieferung bereits OpenWrt 25.12.6 mit dem Profil zyxel_ex5601-t0-ubootmod. Wenn du von älteren OpenWrt-Versionen kommst, fällt sofort die Paketverwaltung auf, denn seit Version 25.12 nutzt OpenWrt apk anstelle von opkg. Außerdem hat der 2,5-Gbit-Port im 1-Gbit-Betrieb einen bekannten Link-Fehler, den du entweder per ethtool in /etc/rc.local umgehst oder vermeidest, indem du bis zum Switch-Tausch bei den 1-Gbit-Ports bleibst.

Vor dem Einstecken ins produktive LAN mussten zwei Fallen entschärft werden:

  • Ab Werk startet der T-56 als Router mit aktivem DHCP-Server, IPv6 Router Advertisements, NAT und Firewall. Steckst du ihn unvorbereitet an den Switch, antwortet ein zweiter DHCP-Server im flachen Netz und zieht einzelnen Clients die interne Namensauflösung weg.
  • Am alten Haupt-Switch (Netgear GS724Tv3) war Spanning Tree noch ausgeschaltet. Vier Geräte mit je fünf Ports im laufenden Betrieb umzustecken, führt ohne klare Port-Zuordnung schnell zu einer Bridge-Schleife. Das Spanning Tree ist weiterhin nicht aktiv; ich habe die Geräte deshalb einzeln und vorsichtig umgesteckt.

Ich habe die vier Geräte einzeln vorbereitet und über DHCP-Reservierungen auf den Domänencontrollern feste Adressen im Hauptnetz vergeben (der Umzug in ein eigenes Verwaltungs-VLAN folgt später mit der Netztrennung) (im Beispiel 192.0.2.2, 192.0.2.3, 192.0.2.4 und 192.0.2.8). Die Pakete firewall4 und nftables blieben installiert, damit keine Paketabhängigkeiten aufbrechen; abgeschaltet wurden lediglich die Dienste dnsmasq, odhcpd und firewall. Dazu kamen per apk add die Pakete lldpd, iw, iwinfo, ethtool und usteer. Den SSH-Dienst dropbear habe ich auf das Management-Interface gebunden und Kennwort-Logins abgeschaltet, die Weboberfläche LuCI (uhttpd) deaktiviert und als NTP-Server die lokalen Domänencontroller sowie die OPNsense-Firewall eingetragen.

Einen Punkt übersiehst du bei Access Points mit mehreren VLAN-Bridges leicht: Ist auf den Bridge-Interfaces der Gast- und IoT-VLANs IPv6 aktiv, gibt sich der Access Point dort selbst eine Link-Local-Adresse (fe80::...) und ist aus dem Gäste- oder IoT-Netz direkt erreichbar. Auf allen Nicht-Management-VLANs schaltet das Härtungsskript deshalb IPv6 am Interface des Access Points explizit ab.

Das WLAN der FRITZ!Box 7590 AX ließ sich anschließend per Skript abschalten. Ausbauen konnte ich die Box trotzdem nicht, weil sie weiterhin als DECT-Basisstation für die Heizkörperthermostate in Home Assistant dient. Die freien LAN-Ports der Zyxel-Geräte nehmen an den vier Standorten außerdem gleich vorhandene Kabelgeräte auf — an AP 1 hängt auf lan1 beispielsweise ein 5G-Mobilfunk-Modem (Deco X50-5G) in einem eigenen VLAN als Backup-WAN für die OPNsense.

Ein Funk-Skript für vier Zellen und die ujail-Falle bei PPSK

Für vier Access Points lohnt sich noch kein eigener Controller wie OpenWISP, wenn ein sauberes Bereitstellungsskript vom Admin-System ausreicht. Das Skript spielt das gesamte Funk- und VLAN-Layout per SSH und UCI auf einen Access Point auf. Weil ein Fehler in /etc/config/wireless oder bei den VLAN-Trunks die Verbindung kappen kann, startet bei jedem Lauf eine Rückfalluhr von zwei Minuten auf dem Gerät: Bleibt die Quittungsdatei /tmp/ap-ok vom aufrufenden Skript aus, stellt der Access Point automatisch den vorherigen Stand wieder her. Zusätzlich zieht ein nächtlicher Job von allen vier Geräten einen bereinigten uci export ins Git-Archiv.

Beim Bau des Skripts zeigten sich gleich mehrere Eigenheiten von SSH und OpenWrt 25.12. In einem Shell-Skript verschluckt ssh -n ein Heredoc auf der Standardeingabe, weshalb Aufrufe mit Heredoc-Blöcken ohne -n laufen müssen. Zudem löscht uci keine Optionen, die ein Skript bei einem späteren Lauf weglässt. Nimmst du eine Einstellung aus der Vorlage heraus, bleibt der alte Wert auf dem Gerät aktiv, solange du ihn vor dem Setzen nicht ausdrücklich mit uci -q delete entfernst.

Eine weitere Hürde betraf das IoT-Netz. Auf den alten Huawei-Routern gab es getrennte Namen für 2,4 GHz und 5 GHz ohne VLAN-Trennung. Auf den OpenWrt-Geräten spannen jetzt alle vier Zellen dieselben Netze auf: das Hauptnetz, ein separates Netz und das Gästenetz laufen jeweils mit einem gemeinsamen Namen über beide Bänder (2,4 und 5 GHz) und liegen auf eigenen getaggten VLANs. Für die IoT-Geräte gibt es eine eigene 2,4-GHz-SSID mit reinem WPA2-CCMP (psk2+ccmp), deaktiviertem 802.11r, abgeschalteten Protected Management Frames (PMF) und max_inactivity=900, damit schlafende ESP- und Tasmota-Sensoren nicht aus dem WLAN fliegen.

Diese 2,4-GHz-SSID teilt die Geräte je nach verwendetem WLAN-Schlüssel per Private Pre-Shared Key (PPSK) auf drei verschiedene IoT-VLANs auf, was zwei zusätzliche SSIDs und deren Beacon-Airtime spart. Klassisch verweist du dafür in hostapd auf eine eigene Schlüsseldatei unter /etc. Unter OpenWrt 25.12 läuft hostapd jedoch in einem ujail-Container und sieht selbst angelegte Dateien unter /etc überhaupt nicht. PPSK funktioniert dort erst, wenn du die Schlüssel als wifi-station-Abschnitte in UCI anlegst, sodass OpenWrt die PSK-Datei selbst erzeugt und in das Jail hineinreicht:

# Alte Optionen gezielt räumen, da UCI weggelassene Werte sonst behält
uci -q delete wireless.iot24.ieee80211r
uci -q delete wireless.iot24.ieee80211w

uci set wireless.iot24=wifi-iface
uci set wireless.iot24.device='radio0'
uci set wireless.iot24.mode='ap'
uci set wireless.iot24.ssid='IoT-Netz'
uci set wireless.iot24.encryption='psk2+ccmp'
uci set wireless.iot24.max_inactivity='900'
uci set wireless.iot24.multicast_to_unicast='1'

# PPSK-Zuordnung über wifi-station (wird von OpenWrt ins ujail gereicht)
uci set wireless.ppsk_gruppe_a=wifi-station
uci set wireless.ppsk_gruppe_a.iface='iot24'
uci set wireless.ppsk_gruppe_a.vid='30'
uci set wireless.ppsk_gruppe_a.key='...'

uci set wireless.ppsk_gruppe_b=wifi-station
uci set wireless.ppsk_gruppe_b.iface='iot24'
uci set wireless.ppsk_gruppe_b.vid='31'
uci set wireless.ppsk_gruppe_b.key='...'

uci commit wireless
wifi reload

Kanalplan, Sendeleistung und die stündliche Messreihe

Auf den alten Access Points liefen alle Zellen mit 100 Prozent Sendeleistung. Weil ein Smartphone oder Sensor deutlich schwächer zurücksendet als ein Access Point mit vier Antennenketten, führt überhöhte Sendeleistung zu Klebeverhalten: Das Endgerät empfängt den weit entfernten Access Point noch gut und hält die Verbindung, obwohl seine eigenen Pakete dort kaum noch ankommen.

Vor der Kanalvergabe habe ich die Belegung per iw survey gemessen. Auf 2,4 GHz laufen alle vier Zellen mit 20 MHz Kanalbreite, auf 5 GHz mit 80 MHz. Die Wetterradar-Kanäle 120 bis 128 bleiben frei. Einen ersten Versuch mit Kanal 149 auf 5 GHz habe ich wieder verworfen: In Europa fällt dieser Bereich unter die Short Range Devices (SRD) mit höchstens 13 dBm (20 mW) Sendeleistung, weshalb sich dort kein einziger Client einbuchte. Um Airtime zu sparen, steht cell_density auf 1 (2,4 GHz) beziehungsweise 2 (5 GHz), was die alten 802.11b-Datenraten abschaltet. Zudem ist auf den SSIDs multicast_to_unicast aktiv, weil Home Assistant, Streaming-Geräte und Spielkonsolen sonst das Funkmedium mit Multicast-Paketen auf der niedrigsten Basisrate belegen.

GerätIP-Adresse (Beispiel)2,4 GHz (20 MHz)5 GHz (80 MHz)Besonderheit im Betrieb
AP 1192.0.2.2Kanal 1Kanal 365G-Modem (Backup-WAN) an lan1
AP 2192.0.2.3Kanal 1Kanal 52 (DFS)Technikbereich am Serverschrank
AP 3192.0.2.4Kanal 11Kanal 36Hauptaufenthaltsbereich
AP 4192.0.2.8Kanal 6Kanal 100 (DFS)Test-AP für Updates (48 h Vorlauf)

Jede Änderung am Funkplan geht zuerst auf AP 4 als Testgerät und bei Systemupdates erst nach 48 Stunden Beobachtung auf AP 1 bis AP 3. Damit du nach einer Anpassung nicht raten musst, ob sich die Verteilung verbessert hat, läuft auf den vier Geräten ein Checkmk-Agent über SSH samt Local-Check für die Funkwerte. Zusätzlich sammelt ein Skript zu jeder vollen Stunde (Minute 17) Kanal, Grundrauschen, Client-Anzahl und die Airtime-Auslastung je Funkmodul ein und baut daraus eine Verlaufsseite.

Schema: Steuerung falsch verbundener Shellys per MAC-Filter in das IoT-Netz sowie Roaming- und Airtime-Diagnose mit usteer, VictoriaLogs und stündlicher Messreihe

Wie nützlich diese stündliche Messreihe ist, zeigte sich gleich mehrfach. Gleich zu Beginn fiel ein Parser-Fehler im Auswerteskript auf, der bei AP 2 die falsche Spalte für den Kanal las. Zwei Tage später zog AP 4 physisch eine Etage höher um; anfangs lief er auf 2,4 GHz gedrosselt mit 17 dBm und zog auf 5 GHz (Kanal 100, DFS) überproportional viele Clients an, am neuen Standort konnte die Drosselung wieder raus. In der Messreihe war die Umbaupause zwischen 19:17 Uhr und 22:17 Uhr ebenso sofort zu sehen wie die neue Verteilung danach.

Den wichtigsten Befund lieferte die Airtime-Spalte an AP 3: Ab etwa 20:00 Uhr meldete dessen 5-GHz-Modul null verbundene Clients, stand aber gleichzeitig bei 83 Prozent Airtime-Auslastung — davon 78 Prozent eigene Sendezeit des Access Points. Ein weiches wifi reload änderte daran nichts. Erst ein vollständiges wifi down && wifi up um 00:37 Uhr senkte die Airtime wieder auf rund 25 Prozent. Ohne die Messung der eigenen Sendezeit wäre ein Funkmodul, das ohne einen einzigen Client fast vier Fünftel der Kanalzeit belegt, in einer reinen Erreichbarkeitsprüfung unbemerkt geblieben.

Dreiminütiger Ausfall durch die falsche MAC-Liste

Nach dem Abschalten der drei Huawei-Geräte und des FRITZ!Box-WLANs waren zwar alle 61 bis 67 Clients auf den vier OpenWrt-Zellen, allerdings am falschen Ort: 8 von 13 Shelly-Aktoren hatten sich ins Hauptnetz eingebucht statt in die 2,4-GHz-IoT-SSID, vermutlich weil sie die Zugangsdaten des Hauptnetzes noch als Alternative gespeichert hatten.

Anstatt acht Shelly-Aktoren einzeln über deren Weboberfläche anzufassen, wollte ich die 13 Shelly-MAC-Adressen auf dem Hauptnetz per macfilter='deny' abweisen, damit sie von selbst auf das hinterlegte IoT-WLAN wechseln. Spätabends spielte ich den Filter mit einer fehlerhaften MAC-Liste aus. Das sperrte reguläre Geräte aus und legte das gesamte WLAN für rund drei Minuten lahm, bis die Änderung zurückgenommen war.

Beim zweiten Anlauf habe ich die MAC-Liste noch einmal geprüft, den macfilter='deny' ausschließlich auf einem einzelnen Access Point gesetzt, den UCI-Sollstand geprüft und erst nach erfolgreichem Wechsel der ersten Aktoren auf die übrigen drei Geräte ausgerollt. Kurz nach Mitternacht waren alle 13 Shellys im IoT-VLAN.

Roaming mit 802.11v, usteer und zentralen Logs in VictoriaLogs

Warum wpad-basic das Funkmodul beim Einschalten von 802.11v abbricht

Damit Endgeräte beim Gang durchs Haus rechtzeitig die Funkzelle wechseln, sollten 802.11k (Radio Resource Measurement) und 802.11v (BSS Transition Management) zusammen mit dem OpenWrt-Dienst usteer laufen. Beim ersten Aktivieren von bss_transition=1 in /etc/config/wireless startete hostapd jedoch nicht mehr und schaltete das gesamte Funkmodul ab.

Die Ursache liegt im vorinstallierten Paketumfang von OpenWrt: Standardmäßig ist wpad-basic (beziehungsweise wpad-basic-mbedtls) installiert, das die 802.11v-Optionen bss_transition und wnm_sleep_mode nicht kennt. Sobald netifd diese Parameter in die erzeugte hostapd-Konfiguration schreibt, bricht der Dienst den Start mit einem Konfigurationsfehler ab. Erst nach dem Tausch gegen das volle Paket wpad-mbedtls liefen 802.11k und 802.11v (mit bss_transition=1, ohne wnm_sleep_mode) auf allen vier Geräten.

# wpad-basic durch volles wpad-mbedtls ersetzen (OpenWrt 25.12 mit apk)
apk del wpad-basic-mbedtls
apk add wpad-mbedtls usteer

# usteer nur auf den Dualband-Netzen aktivieren, IoT-SSID ausnehmen
uci set usteer.@usteer[0].network='lan gast separat'
uci set usteer.@usteer[0].ssid_list='Hauptnetz Gaeste Separat'
uci set usteer.@usteer[0].assoc_steering='1'
uci set usteer.@usteer[0].roam_trigger_snr='10'
uci set usteer.@usteer[0].signal_diff='10'
uci commit usteer
/etc/init.d/usteer restart

usteer-Schwellen und gemeinsames Syslog

usteer lief zunächst kurz passiv mit, um zu prüfen, ob sich alle vier Access Points untereinander sehen und Nachbarschaftsdaten austauschen. Danach habe ich die aktive Steuerung (assoc_steering=1) für das Hauptnetz, das separate VLAN und das Gästenetz eingeschaltet; die 2,4-GHz-IoT-SSID bleibt ausgenommen, damit ortsfeste Aktoren und Sensoren keine Wechselaufforderungen bekommen. Den Schwellwert roam_trigger_snr habe ich zuerst auf 15 bei signal_diff=10 gesetzt und am nächsten Mittag nach kurzen WLAN-Aussetzern an einem Notebook auf 10 gesenkt (zusammen mit dtim_period=1).

Roaming-Vorgänge über vier getrennte Ringpuffer per logread zu verfolgen, ist mühsam, weil Abmeldung, 802.11v-Antwort und Neuanmeldung zeitgleich auf zwei verschiedenen Access Points anfallen. Deshalb habe ich einen eigenen Linux-Container mit VictoriaLogs und Grafana (per HTTPS mit internem ACME-Zertifikat) aufgesetzt und das Syslog aller vier OpenWrt-Geräte dorthin geleitet. Dort liegen hostapd-Meldungen, usteer-Entscheidungen und DFS-Radarereignisse aller vier Funkzellen in einer gemeinsamen Zeitleiste.

Offen sind derzeit noch zwei Punkte: Fast Transition (802.11r mit ft_over_ds=0) und die Umstellung des Hauptnetzes auf WPA2/WPA3-Mischbetrieb (sae-mixed) sind als nächster Schritt geplant und warten, bis auch die restlichen IoT-Altgeräte aus dem Hauptnetz in die 2,4-GHz-IoT-SSID umgezogen sind. Unmittelbar davor prüfe ich nach dem nächsten stündlichen Messlauf um 01:17 Uhr im VictoriaLogs-Dashboard noch das 5-GHz-Modul von AP 3: Dort hatte wifi down && wifi up zwar die Airtime-Auslastung von 83 auf 25 Prozent gedrückt, es buchte sich aber weiterhin kein 5-GHz-Client ein — gut möglich, dass roam_trigger_snr=10 in usteer die Geräte nach dem Ausfall auf 2,4 GHz festhält oder AP 3 außerhalb des nächtlichen Backup-Fensters ab 04:00 Uhr einmal komplett neu starten muss.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht, deine IP-Adresse wird nicht gespeichert. Kommentare erscheinen erst nach Freigabe. Was mit deinen Angaben passiert, steht in der Datenschutzerklärung. Pflichtfelder sind mit * markiert.