From 780b31f4cf6fa877682d3a952fd95cd99e656988 Mon Sep 17 00:00:00 2001 From: Tomaz Zaman Date: Sun, 16 Aug 2026 19:07:22 +0200 Subject: [PATCH 66/72] hostapd, wifi-scripts: reuse the fullmac driver's native uAP MAC NXP mwifiex pre-creates one vif per role, each with a firmware-assigned MAC and a matching firmware BSS. netifd's softmac macaddr_next() derives a base-relative MAC that does not match the uAP vif; the firmware then drops the client's association as "unknown BSSID" and the 4-way handshake never completes (WPA2-PSK and WPA3-SAE alike). Reuse the native uAP vif and its MAC instead: - common.uc: find_reusable_wdev prefers a down vif already of the target type (the real uAP vif, not wifi-direct); the reuse path keeps the vif's own MAC and skips SET_INTERFACE; add a reusable_macaddr() helper. - hostapd.uc (wpad): the primary auto-MAC BSS uses reusable_macaddr() instead of macaddr_next() so the bssid matches the firmware BSS. - ap.uc: skip the bssid= line for an auto-assigned MAC so a generated base MAC cannot leak into a bssid that collides with the STA vif. mac80211 is unaffected (reusable_macaddr returns null on non-fullmac). Co-Authored-By: Claude Opus 4.8 --- .../files-ucode/usr/share/ucode/wifi/ap.uc | 9 ++- .../files/usr/share/hostap/common.uc | 68 ++++++++++++++++--- .../network/services/hostapd/files/hostapd.uc | 18 ++++- 3 files changed, 83 insertions(+), 12 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc index 27ef4e2ac3..41e9c12c8f 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc @@ -46,7 +46,14 @@ function iface_setup(config) { if (config.proxy_arp) set_default(config, 'na_mcast_to_ucast', true); - append('bssid', config.macaddr); + /* + * Only pin an explicit bssid when the user configured a MAC. For an + * auto-assigned MAC, let hostapd use the interface's own address: + * fullmac drivers may own the vif MAC and reject a forced one, which + * desyncs the bssid from the vif and breaks the 4-way handshake. + */ + if (!config.default_macaddr) + append('bssid', config.macaddr); config.ssid2 = config.ssid; config.wmm_enabled = 1; append_string_vars(config, [ 'ssid2' ]); diff --git a/package/network/config/wifi-scripts/files/usr/share/hostap/common.uc b/package/network/config/wifi-scripts/files/usr/share/hostap/common.uc index f801c4940c..6b1baa7c84 100644 --- a/package/network/config/wifi-scripts/files/usr/share/hostap/common.uc +++ b/package/network/config/wifi-scripts/files/usr/share/hostap/common.uc @@ -59,7 +59,7 @@ function phy_is_fullmac(phy) return __phy_is_fullmac(phyidx); } -function find_reusable_wdev(phyidx) +function find_reusable_wdev(phyidx, iftype) { if (!__phy_is_fullmac(phyidx)) return null; @@ -68,10 +68,24 @@ function find_reusable_wdev(phyidx) nl80211.const.NL80211_CMD_GET_INTERFACE, nl80211.const.NLM_F_DUMP, { wiphy: phyidx }); - for (let res in data) - if (trim(readfile(`/sys/class/net/${res.ifname}/operstate`)) == "down") - return res.ifname; - return null; + let fallback = null; + for (let res in data) { + if (trim(readfile(`/sys/class/net/${res.ifname}/operstate`)) != "down") + continue; + /* + * Prefer reusing a wdev that is already of the requested type. + * Fullmac drivers that pre-create one vif per role (e.g. NXP + * mwifiex: mlanN/uapN/wfdN) mishandle converting a STA/P2P vif + * into an AP via SET_INTERFACE: the firmware BSS/EAPOL path is + * left broken, so clients associate but the 4-way handshake never + * completes. Reusing the existing AP vif avoids the type change. + */ + if (iftype != null && res.iftype == iftype) + return res; + if (fallback == null) + fallback = res; + } + return fallback; } function wdev_set_radio_mask(name, mask) @@ -108,13 +122,25 @@ function wdev_create(phy, name, data) nl80211.error(); - let reuse_ifname = find_reusable_wdev(phyidx); + let reuse = find_reusable_wdev(phyidx, req.iftype); + let reuse_ifname = reuse ? reuse.ifname : null; if (reuse_ifname && (reuse_ifname == name || rtnl.request(rtnl.const.RTM_SETLINK, 0, { dev: reuse_ifname, ifname: name}) != false)) { req.dev = req.ifname; delete req.ifname; - nl80211.request(nl80211.const.NL80211_CMD_SET_INTERFACE, 0, req); + /* + * Keep the reused fullmac vif's own MAC. Vendor fullmac drivers + * (e.g. NXP mwifiex) pre-assign a per-role MAC to each vif and + * reject a foreign one ("Bad address"), re-deriving a MAC that no + * longer matches the firmware BSS -- clients then associate but the + * 4-way handshake never completes. And if the reused vif is already + * of the requested type, skip SET_INTERFACE entirely: a plain rename + * leaves the driver's native AP vif (and its working BSS) untouched. + */ + delete req.mac; + if (reuse.iftype != req.iftype) + nl80211.request(nl80211.const.NL80211_CMD_SET_INTERFACE, 0, req); } else { nl80211.request( nl80211.const.NL80211_CMD_NEW_INTERFACE, @@ -420,4 +446,30 @@ function vlist_new(cb) { }, vlist_proto); } -export { wdev_remove, wdev_create, wdev_set_mesh_params, wdev_set_radio_mask, wdev_set_up, is_equal, vlist_new, phy_is_fullmac, phy_open }; +/* + * For fullmac drivers that pre-create one vif per role (e.g. NXP mwifiex: + * mlanN/uapN/wfdN), the generated softmac-style MAC is the phy base address, + * which already belongs to the STA vif. Assigning it to the AP collides, the + * driver keeps the AP vif's own MAC instead, and hostapd's bssid ends up out of + * sync with the actual vif -- breaking the EAPOL path (clients associate but the + * 4-way handshake never completes). Return the reusable (matching-mode, down) + * vif's real MAC so the caller programs that as both the vif MAC and the bssid. + */ +function reusable_macaddr(phy, mode) +{ + let phyidx = int(trim(readfile(`/sys/class/ieee80211/${phy}/index`))); + if (!__phy_is_fullmac(phyidx)) + return null; + + let iftype = iftypes[mode]; + if (iftype == null) + return null; + + let wdev = find_reusable_wdev(phyidx, iftype); + if (!wdev) + return null; + + return wdev_macaddr(wdev.ifname); +} + +export { wdev_remove, wdev_create, wdev_set_mesh_params, wdev_set_radio_mask, wdev_set_up, is_equal, vlist_new, phy_is_fullmac, phy_open, reusable_macaddr }; diff --git a/package/network/services/hostapd/files/hostapd.uc b/package/network/services/hostapd/files/hostapd.uc index 7f8c55da70..9cc18d2767 100644 --- a/package/network/services/hostapd/files/hostapd.uc +++ b/package/network/services/hostapd/files/hostapd.uc @@ -1,6 +1,6 @@ let libubus = require("ubus"); import { open, readfile, access } from "fs"; -import { wdev_remove, is_equal, vlist_new, phy_is_fullmac, phy_open, wdev_set_radio_mask, wdev_set_up } from "common"; +import { wdev_remove, is_equal, vlist_new, phy_is_fullmac, phy_open, wdev_set_radio_mask, wdev_set_up, reusable_macaddr } from "common"; let ubus = libubus.connect(null, 60); @@ -330,8 +330,20 @@ function iface_restart(phydev, config, old_config) iface_macaddr_init(phydev, config, iface_config_macaddr_list(config)); for (let i = 0; i < length(config.bss); i++) { let bss = config.bss[i]; - if (bss.default_macaddr) - bss.bssid = phydev.macaddr_next(); + if (bss.default_macaddr) { + /* + * Fullmac vendor drivers (e.g. NXP mwifiex) pre-create one + * vif per role with a firmware-assigned per-role MAC and a + * matching firmware BSS. macaddr_next() derives a softmac- + * style address from the phy base that does NOT match the + * uAP vif's native MAC/BSS; hostapd then programs that MAC + * as the BSSID, the firmware drops the client's uplink as + * "unknown BSSID" and the 4-way handshake never completes. + * For the primary BSS reuse the native uAP MAC instead. + */ + let reuse = i == 0 ? reusable_macaddr(phydev.phy, "ap") : null; + bss.bssid = reuse ?? phydev.macaddr_next(); + } } iface_pending_init(phydev, config); -- 2.47.3