From 7deb5a21a57566301c85f9cb398396c77b5f964d Mon Sep 17 00:00:00 2001 From: Firas Shaari Date: Tue, 4 Aug 2026 21:38:57 -0400 Subject: [PATCH] command: fix wifiscan scanning every channel on each trigger Enabling metrics.wifi-scan takes the radio off its operating channel for around 23 seconds on every scan interval, long enough that associated clients deassociate. scan_trigger() described a single channel scan with wiphy_freq, but NL80211_CMD_TRIGGER_SCAN does not consume NL80211_ATTR_WIPHY_FREQ. That attribute configures an operating channel, it is not a scan parameter. Netlink silently ignores attributes a command does not use, so the request was accepted and the scan succeeded, while the kernel, finding no scan_frequencies, fell back to scanning every supported channel. trigger_scan_width() therefore triggered a full band sweep on each of its iterations instead of visiting one channel, six sweeps of 28 channels on a 5GHz radio operating at 80MHz. Pass scan_frequencies so that a single channel scan really is one channel. With that corrected the width keyed frequency list would only cover six channels, so use the full list, and dwell on the operating channel between visits so clients continue to be served while the sweep progresses. Finally collect results after every channel and merge them by BSSID. The kernel expires cached BSSes after roughly 30 seconds while a full sweep takes longer than that, so a single GET_SCAN once the sweep finished dropped the channels that had been scanned first. Measured on a YunCore AX820 with a client running continuous traffic, the worst observed client inactivity drops from a 23 second outage with deassociation to 50ms, and the reported neighbour list gains the channels that were previously missing. Fixes: WIFI-14822 Signed-off-by: Firas Shaari --- command/cmd_wifiscan.uc | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/command/cmd_wifiscan.uc b/command/cmd_wifiscan.uc index e1653974..2df5e813 100644 --- a/command/cmd_wifiscan.uc +++ b/command/cmd_wifiscan.uc @@ -12,6 +12,7 @@ if (!ctx) { } const SCAN_FLAG_AP = (1<<2); +const CHANNEL_DWELL = 1000; const frequency_list_2g = [ 2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452, 2457, 2462, 2467, 2472, 2484 ]; const frequency_list_5g = { '3': [ 5180, 5260, 5500, 5580, 5660, 5745 ], @@ -78,9 +79,7 @@ function scan_trigger(wdev, frequency, width) { params.scan_frequencies = frequency; } else if (frequency && width) { - params.wiphy_freq = frequency; - params.center_freq1 = frequency + frequency_offset[width]; - params.channel_width = frequency_width[width]; + params.scan_frequencies = [ frequency ]; } if (active) @@ -105,9 +104,23 @@ function scan_trigger(wdev, frequency, width) { warn("Scan aborted by kernel\n"); } +/* merge scan results by BSSID, the kernel expires them while we are still sweeping */ +let bss_seen = {}; + +function collect_scan(dev) { + let res = nl.request(def.NL80211_CMD_GET_SCAN, def.NLM_F_DUMP, { dev }); + + for (let entry in res || []) + if (entry?.bss?.bssid) + bss_seen[entry.bss.bssid] = entry; +} + function trigger_scan_width(wdev, freqs, width) { - for (let freq in freqs) + for (let freq in freqs) { scan_trigger(wdev, freq, width); + collect_scan(wdev); + sleep(CHANNEL_DWELL); + } } function phy_get(wdev) { @@ -174,11 +187,15 @@ function wifi_scan() { printf("scanning on phy%d\n", phy.wiphy); + bss_seen = {}; + let freqs = phy_get_frequencies(phy); - if (length(intersect(freqs, frequency_list_2g))) + if (length(intersect(freqs, frequency_list_2g))) { scan_trigger(iface.dev, frequency_list_2g); + collect_scan(iface.dev); + } - let ch_width = iface.channel_width; + let ch_width = '1'; if (frequency_width[bandwith]) ch_width = frequency_width[bandwith]; let freqs_5g = intersect(freqs, frequency_list_5g[ch_width]); @@ -189,7 +206,8 @@ function wifi_scan() { } trigger_scan_width(iface.dev, freqs_5g, ch_width); } - let res = nl.request(def.NL80211_CMD_GET_SCAN, def.NLM_F_DUMP, { dev: iface.dev }); + collect_scan(iface.dev); + let res = values(bss_seen); for (let bss in res) { bss = bss.bss; let res = {