* [PATCH] wifi: nl80211: say why the association BSS lookup failed
@ 2026-07-11 4:34 Louis Kotze
2026-07-21 15:31 ` Johannes Berg
0 siblings, 1 reply; 10+ messages in thread
From: Louis Kotze @ 2026-07-11 4:34 UTC (permalink / raw)
To: Johannes Berg; +Cc: linux-wireless, linux-kernel, loukot
The BSS lookup for an association request can fail for three distinct
reasons: cfg80211 has no scan entry at all for the BSSID/channel, an
entry exists but is older than IEEE80211_SCAN_RESULT_EXPIRE (and not
held), or a fresh entry exists but its use_for flags do not allow
this use. All three currently surface as the same generic extack
message "Error fetching BSS for link" on the MLO path, and as a bare
-ENOENT with no message on the non-MLO path.
Since wpa_supplicant logs the extack message verbatim ("nl80211:
kernel reports: ..."), that message is often the only diagnostic a
user sees when an MLO association degrades to fewer links, and it
does not say whether rescanning would help. In practice the expired
case is common for MLO partner links: 6 GHz is passive-scan in many
regulatory domains, so the partner-link entry is routinely stale by
the time userspace requests the association even though the link is
perfectly usable.
Add a cfg80211 helper that classifies, on the failure path only, why
matching entries were rejected, and set a distinct extack message
for each case in nl80211_assoc_bss(). Also give the -EINVAL paths
there proper messages while at it, and keep pointing the bad_attr at
the failing link.
Signed-off-by: Louis Kotze <loukot@gmail.com>
---
Compile-tested (W=1 clean) and exercised on a UML/hwsim rig: an MLO
reassociation whose partner-link entry had aged out now gets
"nl80211: kernel reports: BSS not found in scan results" in the
wpa_supplicant debug log instead of the generic message.
net/wireless/core.h | 12 +++++++++++
net/wireless/nl80211.c | 37 +++++++++++++++++++++++--------
net/wireless/scan.c | 49 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 89 insertions(+), 9 deletions(-)
diff --git a/net/wireless/core.h b/net/wireless/core.h
index df47ed6208a5..a3778c6e07e4 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -332,6 +332,18 @@ void ieee80211_set_bitrate_flags(struct wiphy *wiphy);
void cfg80211_bss_expire(struct cfg80211_registered_device *rdev);
void cfg80211_bss_age(struct cfg80211_registered_device *rdev,
unsigned long age_secs);
+
+/* why __cfg80211_get_bss() found no usable BSS entry */
+#define CFG80211_BSS_MISS_EXPIRED BIT(0)
+#define CFG80211_BSS_MISS_USE_FOR BIT(1)
+
+u32 cfg80211_get_bss_miss_reasons(struct wiphy *wiphy,
+ struct ieee80211_channel *channel,
+ const u8 *bssid,
+ const u8 *ssid, size_t ssid_len,
+ enum ieee80211_bss_type bss_type,
+ enum ieee80211_privacy privacy,
+ u32 use_for);
void cfg80211_update_assoc_bss_entry(struct wireless_dev *wdev,
unsigned int link,
struct ieee80211_channel *channel);
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 242071ad10d6..a68390ae6d2c 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -13036,6 +13036,7 @@ static int nl80211_crypto_settings(struct cfg80211_registered_device *rdev,
}
static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device *rdev,
+ struct genl_info *info,
const u8 *ssid, int ssid_len,
struct nlattr **attrs,
int assoc_link_id, int link_id)
@@ -13043,10 +13044,12 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device
struct ieee80211_channel *chan;
struct cfg80211_bss *bss;
const u8 *bssid;
- u32 freq, use_for = 0;
+ u32 freq, miss, use_for = 0;
- if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_WIPHY_FREQ])
+ if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_WIPHY_FREQ]) {
+ NL_SET_ERR_MSG(info->extack, "BSSID or frequency missing");
return ERR_PTR(-EINVAL);
+ }
bssid = nla_data(attrs[NL80211_ATTR_MAC]);
@@ -13055,8 +13058,10 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device
freq += nla_get_u32(attrs[NL80211_ATTR_WIPHY_FREQ_OFFSET]);
chan = nl80211_get_valid_chan(&rdev->wiphy, freq);
- if (!chan)
+ if (!chan) {
+ NL_SET_ERR_MSG(info->extack, "invalid or disabled channel");
return ERR_PTR(-EINVAL);
+ }
if (assoc_link_id >= 0)
use_for = NL80211_BSS_USE_FOR_MLD_LINK;
@@ -13068,8 +13073,23 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device
IEEE80211_BSS_TYPE_ESS,
IEEE80211_PRIVACY_ANY,
use_for);
- if (!bss)
+ if (!bss) {
+ miss = cfg80211_get_bss_miss_reasons(&rdev->wiphy, chan,
+ bssid, ssid, ssid_len,
+ IEEE80211_BSS_TYPE_ESS,
+ IEEE80211_PRIVACY_ANY,
+ use_for);
+ if (miss & CFG80211_BSS_MISS_USE_FOR)
+ NL_SET_ERR_MSG(info->extack,
+ "BSS cannot be used for this association");
+ else if (miss & CFG80211_BSS_MISS_EXPIRED)
+ NL_SET_ERR_MSG(info->extack,
+ "BSS entry is expired, scan again");
+ else
+ NL_SET_ERR_MSG(info->extack,
+ "BSS not found in scan results");
return ERR_PTR(-ENOENT);
+ }
return bss;
}
@@ -13106,13 +13126,12 @@ static int nl80211_process_links(struct cfg80211_registered_device *rdev,
return -EINVAL;
}
links[link_id].bss =
- nl80211_assoc_bss(rdev, ssid, ssid_len, attrs,
+ nl80211_assoc_bss(rdev, info, ssid, ssid_len, attrs,
assoc_link_id, link_id);
if (IS_ERR(links[link_id].bss)) {
err = PTR_ERR(links[link_id].bss);
links[link_id].bss = NULL;
- NL_SET_ERR_MSG_ATTR(info->extack, link,
- "Error fetching BSS for link");
+ NL_SET_BAD_ATTR(info->extack, link);
return err;
}
@@ -13328,8 +13347,8 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
if (req.link_id >= 0)
return -EINVAL;
- req.bss = nl80211_assoc_bss(rdev, ssid, ssid_len, info->attrs,
- -1, -1);
+ req.bss = nl80211_assoc_bss(rdev, info, ssid, ssid_len,
+ info->attrs, -1, -1);
if (IS_ERR(req.bss))
return PTR_ERR(req.bss);
ap_addr = req.bss->bssid;
diff --git a/net/wireless/scan.c b/net/wireless/scan.c
index 05b7dc6b766c..f0e13b178b74 100644
--- a/net/wireless/scan.c
+++ b/net/wireless/scan.c
@@ -1655,6 +1655,55 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy,
}
EXPORT_SYMBOL(__cfg80211_get_bss);
+/*
+ * Report why __cfg80211_get_bss() with the same arguments found no
+ * usable entry: matching entries exist but are expired, or are not
+ * usable for the requested use. Returns 0 if no entry matches at all.
+ */
+u32 cfg80211_get_bss_miss_reasons(struct wiphy *wiphy,
+ struct ieee80211_channel *channel,
+ const u8 *bssid,
+ const u8 *ssid, size_t ssid_len,
+ enum ieee80211_bss_type bss_type,
+ enum ieee80211_privacy privacy,
+ u32 use_for)
+{
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+ struct cfg80211_internal_bss *bss;
+ unsigned long now = jiffies;
+ u32 reasons = 0;
+ int bss_privacy;
+
+ spin_lock_bh(&rdev->bss_lock);
+
+ list_for_each_entry(bss, &rdev->bss_list, list) {
+ if (!cfg80211_bss_type_match(bss->pub.capability,
+ bss->pub.channel->band, bss_type))
+ continue;
+
+ bss_privacy = (bss->pub.capability & WLAN_CAPABILITY_PRIVACY);
+ if ((privacy == IEEE80211_PRIVACY_ON && !bss_privacy) ||
+ (privacy == IEEE80211_PRIVACY_OFF && bss_privacy))
+ continue;
+ if (channel && bss->pub.channel != channel)
+ continue;
+ if (!is_valid_ether_addr(bss->pub.bssid))
+ continue;
+ if (!is_bss(&bss->pub, bssid, ssid, ssid_len))
+ continue;
+
+ if (time_after(now, bss->ts + IEEE80211_SCAN_RESULT_EXPIRE) &&
+ !atomic_read(&bss->hold))
+ reasons |= CFG80211_BSS_MISS_EXPIRED;
+ else if ((bss->pub.use_for & use_for) != use_for)
+ reasons |= CFG80211_BSS_MISS_USE_FOR;
+ }
+
+ spin_unlock_bh(&rdev->bss_lock);
+
+ return reasons;
+}
+
static bool rb_insert_bss(struct cfg80211_registered_device *rdev,
struct cfg80211_internal_bss *bss)
{
base-commit: ac798f757d6475dc6fee2ec899980d6740714596
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH] wifi: nl80211: say why the association BSS lookup failed 2026-07-11 4:34 [PATCH] wifi: nl80211: say why the association BSS lookup failed Louis Kotze @ 2026-07-21 15:31 ` Johannes Berg 2026-07-21 18:11 ` [PATCH v2 0/2] wifi: cfg80211: say why the auth/assoc " Louis Kotze 0 siblings, 1 reply; 10+ messages in thread From: Johannes Berg @ 2026-07-21 15:31 UTC (permalink / raw) To: Louis Kotze; +Cc: linux-wireless, linux-kernel On Sat, 2026-07-11 at 06:34 +0200, Louis Kotze wrote: > > +/* why __cfg80211_get_bss() found no usable BSS entry */ > +#define CFG80211_BSS_MISS_EXPIRED BIT(0) > +#define CFG80211_BSS_MISS_USE_FOR BIT(1) > + > +u32 cfg80211_get_bss_miss_reasons(struct wiphy *wiphy, > + struct ieee80211_channel *channel, > + const u8 *bssid, > + const u8 *ssid, size_t ssid_len, > + enum ieee80211_bss_type bss_type, > + enum ieee80211_privacy privacy, > + u32 use_for); This seems ... messy, and also racy? I really don't like it, so you're going to have to work extra hard to convince me of it. > @@ -13068,8 +13073,23 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device > IEEE80211_BSS_TYPE_ESS, > IEEE80211_PRIVACY_ANY, > use_for); can't we just pass the info (or NULL) to __cfg80211_get_bss() directly and have that fill the right problem message there? There aren't even any callers to it outside of cfg80211, and cfg80211_get_bss() can just pass NULL. johannes ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 0/2] wifi: cfg80211: say why the auth/assoc BSS lookup failed 2026-07-21 15:31 ` Johannes Berg @ 2026-07-21 18:11 ` Louis Kotze 2026-07-21 18:11 ` [PATCH v2 1/2] " Louis Kotze 2026-07-21 18:11 ` [PATCH v2 " Louis Kotze 0 siblings, 2 replies; 10+ messages in thread From: Louis Kotze @ 2026-07-21 18:11 UTC (permalink / raw) To: johannes; +Cc: linux-wireless, linux-kernel, loukot The BSS lookup for an authentication or association request can fail because no scan entry exists, because the entry is expired, or because the entry's use_for flags forbid the requested use. All three currently produce the same generic (or no) diagnostic, which wpa_supplicant relays verbatim as the only hint a user gets when e.g. an MLO association degrades to fewer links. Patch 1 makes __cfg80211_get_bss() record the reason via an optional extack while it walks the BSS table, and wires the auth and assoc paths up to it. Patch 2 adds a KUnit test for the reported reasons. v2: per Johannes' feedback on v1, drop the separate failure-path classification helper (which re-walked the table and could race against it changing); the reason is now captured in __cfg80211_get_bss() itself, during the same bss_lock walk that fails the lookup, and the public cfg80211_get_bss() wrapper just passes NULL. Since the reporting now lives in the lookup, the previously silent authentication path gets the same messages for free, and a KUnit test (new in v2) can cover all the reasons deterministically. Demonstrated end-to-end in a mac80211_hwsim VM: with an unpatched wpa_supplicant failing an MLD reassociation on a stale per-link BSS, the supplicant log now shows nl80211: kernel reports: BSS not found in scan results nl80211: kernel reports error for link: 1 instead of the undiscriminating "Error fetching BSS for link". Louis Kotze (2): wifi: cfg80211: say why the auth/assoc BSS lookup failed wifi: cfg80211: tests: check BSS lookup failure reasons include/net/cfg80211.h | 7 ++- net/wireless/nl80211.c | 28 +++++---- net/wireless/scan.c | 35 ++++++++--- net/wireless/tests/scan.c | 122 +++++++++++++++++++++++++++++++++++++- 4 files changed, 170 insertions(+), 22 deletions(-) base-commit: ac798f757d6475dc6fee2ec899980d6740714596 -- 2.55.0 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 1/2] wifi: cfg80211: say why the auth/assoc BSS lookup failed 2026-07-21 18:11 ` [PATCH v2 0/2] wifi: cfg80211: say why the auth/assoc " Louis Kotze @ 2026-07-21 18:11 ` Louis Kotze 2026-07-21 22:10 ` Johannes Berg 2026-07-21 18:11 ` [PATCH v2 " Louis Kotze 1 sibling, 1 reply; 10+ messages in thread From: Louis Kotze @ 2026-07-21 18:11 UTC (permalink / raw) To: johannes; +Cc: linux-wireless, linux-kernel, loukot The BSS lookup for an authentication or association request can fail for three distinct reasons: cfg80211 has no scan entry at all for the BSSID/channel, an entry exists but is older than IEEE80211_SCAN_RESULT_EXPIRE (and not held), or a fresh entry exists but its use_for flags do not allow this use. All three currently surface as the same generic extack message "Error fetching BSS for link" on the MLO association path, and as a bare -ENOENT with no message at all on the authentication and non-MLO association paths. Since wpa_supplicant logs the extack message verbatim ("nl80211: kernel reports: ..."), that message is often the only diagnostic a user sees when an MLO association degrades to fewer links, and it does not say whether rescanning would help. In practice the expired case is common for MLO partner links: 6 GHz is passive-scan in many regulatory domains, so the partner-link entry is routinely stale by the time userspace requests the association even though the link is perfectly usable. Let __cfg80211_get_bss() take an optional extack and record, during the same bss_lock walk that fails the lookup, whether any matching entry was rejected only for being expired or only for not being usable for the requested use, and set a distinct message for each case. Reorder the checks in the walk so that an entry's identity (type, privacy, channel, BSSID/SSID) is established before the usability checks; this doesn't change which entry is returned since an entry is only used when all checks pass. When matching entries were rejected for both reasons, the use_for message wins: it is only set for a current (non-expired) entry, so suggesting a rescan would be misleading. Also give the -EINVAL paths in nl80211_assoc_bss() proper messages while at it, and keep pointing the bad_attr at the failing link on the MLO path. Signed-off-by: Louis Kotze <loukot@gmail.com> --- v2: reworked per Johannes' feedback: the reason is captured inside __cfg80211_get_bss() during the single bss_lock walk (no separate re-walk helper, no race), reported via a new optional extack parameter. The authentication path is now covered too, and the patch grew a KUnit companion (patch 2). Retitled nl80211 -> cfg80211. include/net/cfg80211.h | 7 +++++-- net/wireless/nl80211.c | 28 +++++++++++++++++----------- net/wireless/scan.c | 35 +++++++++++++++++++++++++++-------- net/wireless/tests/scan.c | 2 +- 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index b8e9fbb89e69..15c08b24502f 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -8424,6 +8424,8 @@ cfg80211_inform_bss(struct wiphy *wiphy, * @bss_type: type of BSS, see &enum ieee80211_bss_type * @privacy: privacy filter, see &enum ieee80211_privacy * @use_for: indicates which use is intended + * @extack: (optional) extack that is filled with the reason when no + * usable entry was found; may be %NULL * * Return: Reference-counted BSS on success. %NULL on error. */ @@ -8433,7 +8435,8 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, const u8 *ssid, size_t ssid_len, enum ieee80211_bss_type bss_type, enum ieee80211_privacy privacy, - u32 use_for); + u32 use_for, + struct netlink_ext_ack *extack); /** * cfg80211_get_bss - get a BSS reference @@ -8457,7 +8460,7 @@ cfg80211_get_bss(struct wiphy *wiphy, struct ieee80211_channel *channel, { return __cfg80211_get_bss(wiphy, channel, bssid, ssid, ssid_len, bss_type, privacy, - NL80211_BSS_USE_FOR_NORMAL); + NL80211_BSS_USE_FOR_NORMAL, NULL); } static inline struct cfg80211_bss * diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 242071ad10d6..7fb6786a5e8e 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -12889,9 +12889,11 @@ static int nl80211_authenticate(struct sk_buff *skb, struct genl_info *info) return -EINVAL; } - req.bss = cfg80211_get_bss(&rdev->wiphy, chan, bssid, ssid, ssid_len, - IEEE80211_BSS_TYPE_ESS, - IEEE80211_PRIVACY_ANY); + req.bss = __cfg80211_get_bss(&rdev->wiphy, chan, bssid, ssid, ssid_len, + IEEE80211_BSS_TYPE_ESS, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, + info->extack); if (!req.bss) return -ENOENT; @@ -13036,6 +13038,7 @@ static int nl80211_crypto_settings(struct cfg80211_registered_device *rdev, } static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device *rdev, + struct genl_info *info, const u8 *ssid, int ssid_len, struct nlattr **attrs, int assoc_link_id, int link_id) @@ -13045,8 +13048,10 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device const u8 *bssid; u32 freq, use_for = 0; - if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_WIPHY_FREQ]) + if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_WIPHY_FREQ]) { + NL_SET_ERR_MSG(info->extack, "BSSID or frequency missing"); return ERR_PTR(-EINVAL); + } bssid = nla_data(attrs[NL80211_ATTR_MAC]); @@ -13055,8 +13060,10 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device freq += nla_get_u32(attrs[NL80211_ATTR_WIPHY_FREQ_OFFSET]); chan = nl80211_get_valid_chan(&rdev->wiphy, freq); - if (!chan) + if (!chan) { + NL_SET_ERR_MSG(info->extack, "invalid or disabled channel"); return ERR_PTR(-EINVAL); + } if (assoc_link_id >= 0) use_for = NL80211_BSS_USE_FOR_MLD_LINK; @@ -13067,7 +13074,7 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device ssid, ssid_len, IEEE80211_BSS_TYPE_ESS, IEEE80211_PRIVACY_ANY, - use_for); + use_for, info->extack); if (!bss) return ERR_PTR(-ENOENT); @@ -13106,13 +13113,12 @@ static int nl80211_process_links(struct cfg80211_registered_device *rdev, return -EINVAL; } links[link_id].bss = - nl80211_assoc_bss(rdev, ssid, ssid_len, attrs, + nl80211_assoc_bss(rdev, info, ssid, ssid_len, attrs, assoc_link_id, link_id); if (IS_ERR(links[link_id].bss)) { err = PTR_ERR(links[link_id].bss); links[link_id].bss = NULL; - NL_SET_ERR_MSG_ATTR(info->extack, link, - "Error fetching BSS for link"); + NL_SET_BAD_ATTR(info->extack, link); return err; } @@ -13328,8 +13334,8 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info) if (req.link_id >= 0) return -EINVAL; - req.bss = nl80211_assoc_bss(rdev, ssid, ssid_len, info->attrs, - -1, -1); + req.bss = nl80211_assoc_bss(rdev, info, ssid, ssid_len, + info->attrs, -1, -1); if (IS_ERR(req.bss)) return PTR_ERR(req.bss); ap_addr = req.bss->bssid; diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 05b7dc6b766c..f26e8436dbc1 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -1609,10 +1609,12 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, const u8 *ssid, size_t ssid_len, enum ieee80211_bss_type bss_type, enum ieee80211_privacy privacy, - u32 use_for) + u32 use_for, + struct netlink_ext_ack *extack) { struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); struct cfg80211_internal_bss *bss, *res = NULL; + bool expired = false, unusable = false; unsigned long now = jiffies; int bss_privacy; @@ -1634,22 +1636,39 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, continue; if (!is_valid_ether_addr(bss->pub.bssid)) continue; - if ((bss->pub.use_for & use_for) != use_for) + if (!is_bss(&bss->pub, bssid, ssid, ssid_len)) continue; + /* Don't get expired BSS structs */ if (time_after(now, bss->ts + IEEE80211_SCAN_RESULT_EXPIRE) && - !atomic_read(&bss->hold)) + !atomic_read(&bss->hold)) { + expired = true; + continue; + } + + if ((bss->pub.use_for & use_for) != use_for) { + unusable = true; continue; - if (is_bss(&bss->pub, bssid, ssid, ssid_len)) { - res = bss; - bss_ref_get(rdev, res); - break; } + + res = bss; + bss_ref_get(rdev, res); + break; } spin_unlock_bh(&rdev->bss_lock); - if (!res) + if (!res) { + if (unusable) + NL_SET_ERR_MSG(extack, + "BSS cannot be used for the requested operation"); + else if (expired) + NL_SET_ERR_MSG(extack, + "BSS entry is expired, scan again"); + else + NL_SET_ERR_MSG(extack, + "BSS not found in scan results"); return NULL; + } trace_cfg80211_return_bss(&res->pub); return &res->pub; } diff --git a/net/wireless/tests/scan.c b/net/wireless/tests/scan.c index b1a9c1466d6c..2fc717317ac3 100644 --- a/net/wireless/tests/scan.c +++ b/net/wireless/tests/scan.c @@ -617,7 +617,7 @@ static void test_inform_bss_ml_sta(struct kunit *test) link_bss = __cfg80211_get_bss(wiphy, NULL, sta_prof.bssid, NULL, 0, IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY, - 0); + 0, NULL); KUNIT_ASSERT_NOT_NULL(test, link_bss); KUNIT_EXPECT_EQ(test, link_bss->signal, 0); KUNIT_EXPECT_EQ(test, link_bss->beacon_interval, -- 2.55.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v2 1/2] wifi: cfg80211: say why the auth/assoc BSS lookup failed 2026-07-21 18:11 ` [PATCH v2 1/2] " Louis Kotze @ 2026-07-21 22:10 ` Johannes Berg 2026-07-22 7:07 ` Louis Kotze 2026-07-22 7:07 ` [PATCH v3 0/2] " Louis Kotze 0 siblings, 2 replies; 10+ messages in thread From: Johannes Berg @ 2026-07-21 22:10 UTC (permalink / raw) To: Louis Kotze; +Cc: linux-wireless, linux-kernel On Tue, 2026-07-21 at 20:11 +0200, Louis Kotze wrote: > > - if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_WIPHY_FREQ]) > + if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_WIPHY_FREQ]) { > + NL_SET_ERR_MSG(info->extack, "BSSID or frequency missing"); > > could use the GENL versions and save some characters :) > if (IS_ERR(links[link_id].bss)) { > err = PTR_ERR(links[link_id].bss); > links[link_id].bss = NULL; > - NL_SET_ERR_MSG_ATTR(info->extack, link, > - "Error fetching BSS for link"); > + NL_SET_BAD_ATTR(info->extack, link); why remove the message, it's possible to have both? > > - req.bss = nl80211_assoc_bss(rdev, ssid, ssid_len, info->attrs, > - -1, -1); > + req.bss = nl80211_assoc_bss(rdev, info, ssid, ssid_len, > + info->attrs, -1, -1); entirely unrelated (whitespace) change? > +++ b/net/wireless/scan.c > @@ -1609,10 +1609,12 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, > const u8 *ssid, size_t ssid_len, > enum ieee80211_bss_type bss_type, > enum ieee80211_privacy privacy, > - u32 use_for) > + u32 use_for, > + struct netlink_ext_ack *extack) > { > struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); > struct cfg80211_internal_bss *bss, *res = NULL; > + bool expired = false, unusable = false; > unsigned long now = jiffies; > int bss_privacy; > > @@ -1634,22 +1636,39 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, > continue; > if (!is_valid_ether_addr(bss->pub.bssid)) > continue; > - if ((bss->pub.use_for & use_for) != use_for) > + if (!is_bss(&bss->pub, bssid, ssid, ssid_len)) > continue; > + > /* Don't get expired BSS structs */ > if (time_after(now, bss->ts + IEEE80211_SCAN_RESULT_EXPIRE) && > - !atomic_read(&bss->hold)) > + !atomic_read(&bss->hold)) { > + expired = true; > + continue; > + } > + > + if ((bss->pub.use_for & use_for) != use_for) { > + unusable = true; > continue; > - if (is_bss(&bss->pub, bssid, ssid, ssid_len)) { > - res = bss; > - bss_ref_get(rdev, res); > - break; > } > + > + res = bss; > + bss_ref_get(rdev, res); > + break; That code should probably have a comment that the is_bss() must come first... Also however, it could result in having *both* 'unusable' and 'expired' set, and then > - if (!res) > + if (!res) { > + if (unusable) > + NL_SET_ERR_MSG(extack, > + "BSS cannot be used for the requested operation"); > + else if (expired) > + NL_SET_ERR_MSG(extack, > + "BSS entry is expired, scan again"); > + else > + NL_SET_ERR_MSG(extack, > + "BSS not found in scan results"); > return NULL; you prefer the 'unusable' message... not sure that makes sense? I also don't think the "scan again" instruction makes any sense here - this is meant to provide an error message, not instructions how to fix it? johannes ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 1/2] wifi: cfg80211: say why the auth/assoc BSS lookup failed 2026-07-21 22:10 ` Johannes Berg @ 2026-07-22 7:07 ` Louis Kotze 2026-07-22 7:07 ` [PATCH v3 0/2] " Louis Kotze 1 sibling, 0 replies; 10+ messages in thread From: Louis Kotze @ 2026-07-22 7:07 UTC (permalink / raw) To: Johannes Berg; +Cc: linux-wireless, linux-kernel, loukot On Wed, 2026-07-22 at 00:10 +0200, Johannes Berg wrote: > > + if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_WIPHY_FREQ]) { > > + NL_SET_ERR_MSG(info->extack, "BSSID or frequency missing"); > > could use the GENL versions and save some characters :) Done in v3. > > - NL_SET_ERR_MSG_ATTR(info->extack, link, > > - "Error fetching BSS for link"); > > + NL_SET_BAD_ATTR(info->extack, link); > > why remove the message, it's possible to have both? There still is a message here: __cfg80211_get_bss() has already set the specific one by the time this runs, and NL_SET_ERR_MSG_ATTR() would overwrite it with the generic text again. Added a comment in v3 to make that visible. > > - req.bss = nl80211_assoc_bss(rdev, ssid, ssid_len, info->attrs, > > - -1, -1); > > + req.bss = nl80211_assoc_bss(rdev, info, ssid, ssid_len, > > + info->attrs, -1, -1); > > entirely unrelated (whitespace) change? The call gains the new "info" argument; v3 keeps the original wrapping so only that shows in the diff. > That code should probably have a comment that the is_bss() must come > first... Added. > Also however, it could result in having *both* 'unusable' and > 'expired' set, and then > [...] > you prefer the 'unusable' message... not sure that makes sense? Right. v3 sets a combined message when matching entries were rejected for different reasons, so neither is preferred or hidden. > I also don't think the "scan again" instruction makes any sense here - > this is meant to provide an error message, not instructions how to fix > it? Dropped, the messages are purely descriptive now. Sending v3 shortly. thanks, Louis ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 0/2] wifi: cfg80211: say why the auth/assoc BSS lookup failed 2026-07-21 22:10 ` Johannes Berg 2026-07-22 7:07 ` Louis Kotze @ 2026-07-22 7:07 ` Louis Kotze 2026-07-22 7:07 ` [PATCH v3 1/2] " Louis Kotze 2026-07-22 7:07 ` [PATCH v3 2/2] wifi: cfg80211: tests: check BSS lookup failure reasons Louis Kotze 1 sibling, 2 replies; 10+ messages in thread From: Louis Kotze @ 2026-07-22 7:07 UTC (permalink / raw) To: Johannes Berg; +Cc: linux-wireless, linux-kernel, loukot The BSS lookup for an authentication or association request can fail because no scan entry exists, because the entry is expired, or because the entry's use_for flags forbid the requested use. All three currently produce the same generic (or no) diagnostic, which wpa_supplicant relays verbatim as the only hint a user gets when e.g. an MLO association degrades to fewer links. Patch 1 makes __cfg80211_get_bss() record the reason via an optional extack while it walks the BSS table, and wires the auth and assoc paths up to it. Patch 2 adds a KUnit test for the reported reasons. Still demonstrated end-to-end in a mac80211_hwsim VM: an unpatched wpa_supplicant failing an MLD reassociation on a stale per-link BSS now logs "nl80211: kernel reports: BSS not found in scan results" plus the failing link, instead of the generic message. v3: address Johannes' review of v2: use GENL_SET_ERR_MSG for the -EINVAL messages in nl80211_assoc_bss(); comment why the bare NL_SET_BAD_ATTR is enough there (the lookup already set the specific message); comment that the identity checks in the walk must precede the expired/unusable classification; report a combined message when matching entries were rejected for different reasons instead of preferring one of them; drop the "scan again" instruction so the messages are purely descriptive; keep the original argument wrapping at the nl80211_associate() call site so only the new argument shows in the diff. v2: per Johannes' feedback on v1, capture the reason inside __cfg80211_get_bss() during the single bss_lock walk instead of a separate re-walk helper; cover the previously silent authentication path; add the KUnit test. Louis Kotze (2): wifi: cfg80211: say why the auth/assoc BSS lookup failed wifi: cfg80211: tests: check BSS lookup failure reasons include/net/cfg80211.h | 7 ++- net/wireless/nl80211.c | 27 +++++---- net/wireless/scan.c | 44 +++++++++++--- net/wireless/tests/scan.c | 121 +++++++++++++++++++++++++++++++++++++- 4 files changed, 178 insertions(+), 21 deletions(-) base-commit: 69bd85e933c617a84fa54fcc80e628e98afdc45d -- 2.55.0 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 1/2] wifi: cfg80211: say why the auth/assoc BSS lookup failed 2026-07-22 7:07 ` [PATCH v3 0/2] " Louis Kotze @ 2026-07-22 7:07 ` Louis Kotze 2026-07-22 7:07 ` [PATCH v3 2/2] wifi: cfg80211: tests: check BSS lookup failure reasons Louis Kotze 1 sibling, 0 replies; 10+ messages in thread From: Louis Kotze @ 2026-07-22 7:07 UTC (permalink / raw) To: Johannes Berg; +Cc: linux-wireless, linux-kernel, loukot The BSS lookup for an authentication or association request can fail for three distinct reasons: cfg80211 has no scan entry at all for the BSSID/channel, an entry exists but is older than IEEE80211_SCAN_RESULT_EXPIRE (and not held), or a fresh entry exists but its use_for flags do not allow this use. All three currently surface as the same generic extack message "Error fetching BSS for link" on the MLO association path, and as a bare -ENOENT with no message at all on the authentication and non-MLO association paths. Since wpa_supplicant logs the extack message verbatim ("nl80211: kernel reports: ..."), that message is often the only diagnostic a user sees when an MLO association degrades to fewer links, and it does not say whether a fresh scan could have helped. In practice the expired case is common for MLO partner links: 6 GHz is passive-scan in many regulatory domains, so the partner-link entry is routinely stale by the time userspace requests the association even though the link is perfectly usable. Let __cfg80211_get_bss() take an optional extack and record, during the same bss_lock walk that fails the lookup, whether any matching entry was rejected for being expired or for not being usable for the requested use, and set a distinct message for each case (and a combined one when different entries were rejected for different reasons). Reorder the checks in the walk so that an entry's identity (type, privacy, channel, BSSID/SSID) is established before the usability checks; this doesn't change which entry is returned since an entry is only used when all checks pass. Also give the -EINVAL paths in nl80211_assoc_bss() proper messages while at it, and keep pointing the bad_attr at the failing link on the MLO path there; the message for that case is already set by the lookup itself. Signed-off-by: Louis Kotze <loukot@gmail.com> --- v3: use GENL_SET_ERR_MSG for the -EINVAL messages; comment the bare NL_SET_BAD_ATTR (the lookup already set the specific message); comment the check ordering in the walk; combined message when matching entries were rejected for different reasons; drop the "scan again" instruction; minimal diff at the nl80211_associate() call site. v2: capture the reason inside __cfg80211_get_bss() during the single bss_lock walk (per Johannes' feedback on v1); cover the auth path; KUnit test added as patch 2. include/net/cfg80211.h | 7 +++++-- net/wireless/nl80211.c | 27 +++++++++++++++--------- net/wireless/scan.c | 44 ++++++++++++++++++++++++++++++++------- net/wireless/tests/scan.c | 2 +- 4 files changed, 59 insertions(+), 21 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index b8e9fbb89e69..15c08b24502f 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -8424,6 +8424,8 @@ cfg80211_inform_bss(struct wiphy *wiphy, * @bss_type: type of BSS, see &enum ieee80211_bss_type * @privacy: privacy filter, see &enum ieee80211_privacy * @use_for: indicates which use is intended + * @extack: (optional) extack that is filled with the reason when no + * usable entry was found; may be %NULL * * Return: Reference-counted BSS on success. %NULL on error. */ @@ -8433,7 +8435,8 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, const u8 *ssid, size_t ssid_len, enum ieee80211_bss_type bss_type, enum ieee80211_privacy privacy, - u32 use_for); + u32 use_for, + struct netlink_ext_ack *extack); /** * cfg80211_get_bss - get a BSS reference @@ -8457,7 +8460,7 @@ cfg80211_get_bss(struct wiphy *wiphy, struct ieee80211_channel *channel, { return __cfg80211_get_bss(wiphy, channel, bssid, ssid, ssid_len, bss_type, privacy, - NL80211_BSS_USE_FOR_NORMAL); + NL80211_BSS_USE_FOR_NORMAL, NULL); } static inline struct cfg80211_bss * diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index d962b5944533..ac0c0da45241 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -12890,9 +12890,11 @@ static int nl80211_authenticate(struct sk_buff *skb, struct genl_info *info) return -EINVAL; } - req.bss = cfg80211_get_bss(&rdev->wiphy, chan, bssid, ssid, ssid_len, - IEEE80211_BSS_TYPE_ESS, - IEEE80211_PRIVACY_ANY); + req.bss = __cfg80211_get_bss(&rdev->wiphy, chan, bssid, ssid, ssid_len, + IEEE80211_BSS_TYPE_ESS, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, + info->extack); if (!req.bss) return -ENOENT; @@ -13037,6 +13039,7 @@ static int nl80211_crypto_settings(struct cfg80211_registered_device *rdev, } static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device *rdev, + struct genl_info *info, const u8 *ssid, int ssid_len, struct nlattr **attrs, int assoc_link_id, int link_id) @@ -13046,8 +13049,10 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device const u8 *bssid; u32 freq, use_for = 0; - if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_WIPHY_FREQ]) + if (!attrs[NL80211_ATTR_MAC] || !attrs[NL80211_ATTR_WIPHY_FREQ]) { + GENL_SET_ERR_MSG(info, "BSSID or frequency missing"); return ERR_PTR(-EINVAL); + } bssid = nla_data(attrs[NL80211_ATTR_MAC]); @@ -13056,8 +13061,10 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device freq += nla_get_u32(attrs[NL80211_ATTR_WIPHY_FREQ_OFFSET]); chan = nl80211_get_valid_chan(&rdev->wiphy, freq); - if (!chan) + if (!chan) { + GENL_SET_ERR_MSG(info, "invalid or disabled channel"); return ERR_PTR(-EINVAL); + } if (assoc_link_id >= 0) use_for = NL80211_BSS_USE_FOR_MLD_LINK; @@ -13068,7 +13075,7 @@ static struct cfg80211_bss *nl80211_assoc_bss(struct cfg80211_registered_device ssid, ssid_len, IEEE80211_BSS_TYPE_ESS, IEEE80211_PRIVACY_ANY, - use_for); + use_for, info->extack); if (!bss) return ERR_PTR(-ENOENT); @@ -13107,13 +13114,13 @@ static int nl80211_process_links(struct cfg80211_registered_device *rdev, return -EINVAL; } links[link_id].bss = - nl80211_assoc_bss(rdev, ssid, ssid_len, attrs, + nl80211_assoc_bss(rdev, info, ssid, ssid_len, attrs, assoc_link_id, link_id); if (IS_ERR(links[link_id].bss)) { err = PTR_ERR(links[link_id].bss); links[link_id].bss = NULL; - NL_SET_ERR_MSG_ATTR(info->extack, link, - "Error fetching BSS for link"); + /* the BSS lookup set the specific message already */ + NL_SET_BAD_ATTR(info->extack, link); return err; } @@ -13329,7 +13336,7 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info) if (req.link_id >= 0) return -EINVAL; - req.bss = nl80211_assoc_bss(rdev, ssid, ssid_len, info->attrs, + req.bss = nl80211_assoc_bss(rdev, info, ssid, ssid_len, info->attrs, -1, -1); if (IS_ERR(req.bss)) return PTR_ERR(req.bss); diff --git a/net/wireless/scan.c b/net/wireless/scan.c index e62b7dd2b7c2..90a4f285654f 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -1609,10 +1609,12 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, const u8 *ssid, size_t ssid_len, enum ieee80211_bss_type bss_type, enum ieee80211_privacy privacy, - u32 use_for) + u32 use_for, + struct netlink_ext_ack *extack) { struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); struct cfg80211_internal_bss *bss, *res = NULL; + bool expired = false, unusable = false; unsigned long now = jiffies; int bss_privacy; @@ -1634,22 +1636,48 @@ struct cfg80211_bss *__cfg80211_get_bss(struct wiphy *wiphy, continue; if (!is_valid_ether_addr(bss->pub.bssid)) continue; - if ((bss->pub.use_for & use_for) != use_for) + if (!is_bss(&bss->pub, bssid, ssid, ssid_len)) continue; + + /* + * The identity checks above must all come first so that + * the expired/unusable classification below only ever + * applies to entries that actually match the request. + */ + /* Don't get expired BSS structs */ if (time_after(now, bss->ts + IEEE80211_SCAN_RESULT_EXPIRE) && - !atomic_read(&bss->hold)) + !atomic_read(&bss->hold)) { + expired = true; + continue; + } + + if ((bss->pub.use_for & use_for) != use_for) { + unusable = true; continue; - if (is_bss(&bss->pub, bssid, ssid, ssid_len)) { - res = bss; - bss_ref_get(rdev, res); - break; } + + res = bss; + bss_ref_get(rdev, res); + break; } spin_unlock_bh(&rdev->bss_lock); - if (!res) + if (!res) { + if (expired && unusable) + NL_SET_ERR_MSG(extack, + "BSS entries are expired or cannot be used for the requested operation"); + else if (unusable) + NL_SET_ERR_MSG(extack, + "BSS cannot be used for the requested operation"); + else if (expired) + NL_SET_ERR_MSG(extack, + "BSS entry in scan results is expired"); + else + NL_SET_ERR_MSG(extack, + "BSS not found in scan results"); return NULL; + } trace_cfg80211_return_bss(&res->pub); return &res->pub; } diff --git a/net/wireless/tests/scan.c b/net/wireless/tests/scan.c index b1a9c1466d6c..2fc717317ac3 100644 --- a/net/wireless/tests/scan.c +++ b/net/wireless/tests/scan.c @@ -617,7 +617,7 @@ static void test_inform_bss_ml_sta(struct kunit *test) link_bss = __cfg80211_get_bss(wiphy, NULL, sta_prof.bssid, NULL, 0, IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY, - 0); + 0, NULL); KUNIT_ASSERT_NOT_NULL(test, link_bss); KUNIT_EXPECT_EQ(test, link_bss->signal, 0); KUNIT_EXPECT_EQ(test, link_bss->beacon_interval, -- 2.55.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v3 2/2] wifi: cfg80211: tests: check BSS lookup failure reasons 2026-07-22 7:07 ` [PATCH v3 0/2] " Louis Kotze 2026-07-22 7:07 ` [PATCH v3 1/2] " Louis Kotze @ 2026-07-22 7:07 ` Louis Kotze 1 sibling, 0 replies; 10+ messages in thread From: Louis Kotze @ 2026-07-22 7:07 UTC (permalink / raw) To: Johannes Berg; +Cc: linux-wireless, linux-kernel, loukot Add a KUnit test for the extack failure reasons that __cfg80211_get_bss() now reports: no matching scan entry at all, a matching entry that is expired, and a matching entry whose use_for flags do not allow the requested use. Also cover the cases that must not report a failure (a fresh entry, and an expired-but-held entry), an entry that is both expired and unusable, and the combined message when one matching entry is expired while another is current but unusable. Signed-off-by: Louis Kotze <loukot@gmail.com> --- v3: message strings updated to match patch 1; the cross-entry case now checks the combined message. net/wireless/tests/scan.c | 119 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/net/wireless/tests/scan.c b/net/wireless/tests/scan.c index 2fc717317ac3..8c20278b5d3a 100644 --- a/net/wireless/tests/scan.c +++ b/net/wireless/tests/scan.c @@ -402,6 +402,124 @@ static void test_inform_bss_ssid_only(struct kunit *test) cfg80211_put_bss(wiphy, bss); } +static void test_get_bss_miss_reason(struct kunit *test) +{ + struct inform_bss ctx = { + .test = test, + }; + struct wiphy *wiphy = T_WIPHY(test, ctx); + struct cfg80211_inform_bss inform_bss = { + .signal = 50, + .drv_data = &ctx, + }; + const u8 bssid[ETH_ALEN] = { 0x10, 0x22, 0x33, 0x44, 0x55, 0x66 }; + const u8 other_bssid[ETH_ALEN] = { 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 }; + static const u8 ies[] = { + [0] = WLAN_EID_SSID, + [1] = 4, + [2] = 'T', 'E', 'S', 'T' + }; + struct cfg80211_internal_bss *ibss; + struct netlink_ext_ack extack = {}; + struct cfg80211_bss *bss, *bss2, *found; + + inform_bss.chan = ieee80211_get_channel_khz(wiphy, MHZ_TO_KHZ(2412)); + KUNIT_ASSERT_NOT_NULL(test, inform_bss.chan); + + bss = cfg80211_inform_bss_data(wiphy, &inform_bss, + CFG80211_BSS_FTYPE_PRESP, bssid, 0, + 0x1234, 100, ies, sizeof(ies), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, bss); + ibss = container_of(bss, struct cfg80211_internal_bss, pub); + + /* Fresh usable entry: found, no message is set */ + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_ASSERT_PTR_EQ(test, found, bss); + KUNIT_EXPECT_NULL(test, extack._msg); + cfg80211_put_bss(wiphy, found); + + /* No entry at all for this BSSID */ + found = __cfg80211_get_bss(wiphy, NULL, other_bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, "BSS not found in scan results"); + + /* Fresh entry that is not usable for the requested use */ + extack._msg = NULL; + bss->use_for = 0; + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS cannot be used for the requested operation"); + bss->use_for = NL80211_BSS_USE_FOR_ALL; + + /* Expired entry, > IEEE80211_SCAN_RESULT_EXPIRE (30s) old */ + extack._msg = NULL; + ibss->ts = jiffies - 60 * HZ; + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS entry in scan results is expired"); + + /* An entry both expired and unusable reports expired */ + extack._msg = NULL; + bss->use_for = 0; + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS entry in scan results is expired"); + bss->use_for = NL80211_BSS_USE_FOR_ALL; + + /* Expired but held entries are still usable, no message is set */ + extack._msg = NULL; + atomic_set(&ibss->hold, 1); + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_ASSERT_PTR_EQ(test, found, bss); + KUNIT_EXPECT_NULL(test, extack._msg); + cfg80211_put_bss(wiphy, found); + atomic_set(&ibss->hold, 0); + + /* + * With one matching entry expired and another current but + * unusable, both reasons are reported. + */ + bss2 = cfg80211_inform_bss_data(wiphy, &inform_bss, + CFG80211_BSS_FTYPE_PRESP, other_bssid, + 0, 0x1234, 100, ies, sizeof(ies), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, bss2); + bss2->use_for = 0; + extack._msg = NULL; + found = __cfg80211_get_bss(wiphy, NULL, NULL, "TEST", 4, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS entries are expired or cannot be used for the requested operation"); + + cfg80211_put_bss(wiphy, bss2); + cfg80211_put_bss(wiphy, bss); +} + static struct inform_bss_ml_sta_case { const char *desc; int mld_id; @@ -855,6 +973,7 @@ kunit_test_suite(gen_new_ie); static struct kunit_case inform_bss_test_cases[] = { KUNIT_CASE(test_inform_bss_ssid_only), + KUNIT_CASE(test_get_bss_miss_reason), KUNIT_CASE_PARAM(test_inform_bss_ml_sta, inform_bss_ml_sta_gen_params), {} }; -- 2.55.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 2/2] wifi: cfg80211: tests: check BSS lookup failure reasons 2026-07-21 18:11 ` [PATCH v2 0/2] wifi: cfg80211: say why the auth/assoc " Louis Kotze 2026-07-21 18:11 ` [PATCH v2 1/2] " Louis Kotze @ 2026-07-21 18:11 ` Louis Kotze 1 sibling, 0 replies; 10+ messages in thread From: Louis Kotze @ 2026-07-21 18:11 UTC (permalink / raw) To: johannes; +Cc: linux-wireless, linux-kernel, loukot Add a KUnit test for the extack failure reasons that __cfg80211_get_bss() now reports: no matching scan entry at all, a matching entry that is expired, and a matching entry whose use_for flags do not allow the requested use. Also cover the cases that must not report a failure (a fresh entry, and an expired-but-held entry), the precedence for an entry that is both expired and unusable, and the cross-entry precedence when one matching entry is expired and another is current but unusable. Signed-off-by: Louis Kotze <loukot@gmail.com> --- net/wireless/tests/scan.c | 120 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/net/wireless/tests/scan.c b/net/wireless/tests/scan.c index 2fc717317ac3..e62b01f8cbe5 100644 --- a/net/wireless/tests/scan.c +++ b/net/wireless/tests/scan.c @@ -402,6 +402,125 @@ static void test_inform_bss_ssid_only(struct kunit *test) cfg80211_put_bss(wiphy, bss); } +static void test_get_bss_miss_reason(struct kunit *test) +{ + struct inform_bss ctx = { + .test = test, + }; + struct wiphy *wiphy = T_WIPHY(test, ctx); + struct cfg80211_inform_bss inform_bss = { + .signal = 50, + .drv_data = &ctx, + }; + const u8 bssid[ETH_ALEN] = { 0x10, 0x22, 0x33, 0x44, 0x55, 0x66 }; + const u8 other_bssid[ETH_ALEN] = { 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 }; + static const u8 ies[] = { + [0] = WLAN_EID_SSID, + [1] = 4, + [2] = 'T', 'E', 'S', 'T' + }; + struct cfg80211_internal_bss *ibss; + struct netlink_ext_ack extack = {}; + struct cfg80211_bss *bss, *bss2, *found; + + inform_bss.chan = ieee80211_get_channel_khz(wiphy, MHZ_TO_KHZ(2412)); + KUNIT_ASSERT_NOT_NULL(test, inform_bss.chan); + + bss = cfg80211_inform_bss_data(wiphy, &inform_bss, + CFG80211_BSS_FTYPE_PRESP, bssid, 0, + 0x1234, 100, ies, sizeof(ies), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, bss); + ibss = container_of(bss, struct cfg80211_internal_bss, pub); + + /* Fresh usable entry: found, no message is set */ + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_ASSERT_PTR_EQ(test, found, bss); + KUNIT_EXPECT_NULL(test, extack._msg); + cfg80211_put_bss(wiphy, found); + + /* No entry at all for this BSSID */ + found = __cfg80211_get_bss(wiphy, NULL, other_bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, "BSS not found in scan results"); + + /* Fresh entry that is not usable for the requested use */ + extack._msg = NULL; + bss->use_for = 0; + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS cannot be used for the requested operation"); + bss->use_for = NL80211_BSS_USE_FOR_ALL; + + /* Expired entry, > IEEE80211_SCAN_RESULT_EXPIRE (30s) old */ + extack._msg = NULL; + ibss->ts = jiffies - 60 * HZ; + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS entry is expired, scan again"); + + /* An entry both expired and unusable reports expired */ + extack._msg = NULL; + bss->use_for = 0; + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS entry is expired, scan again"); + bss->use_for = NL80211_BSS_USE_FOR_ALL; + + /* Expired but held entries are still usable, no message is set */ + extack._msg = NULL; + atomic_set(&ibss->hold, 1); + found = __cfg80211_get_bss(wiphy, NULL, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_ASSERT_PTR_EQ(test, found, bss); + KUNIT_EXPECT_NULL(test, extack._msg); + cfg80211_put_bss(wiphy, found); + atomic_set(&ibss->hold, 0); + + /* + * With both an expired entry and a fresh-but-unusable entry + * matching, the unusable one determines the message: a current + * entry exists, so suggesting a rescan would be misleading. + */ + bss2 = cfg80211_inform_bss_data(wiphy, &inform_bss, + CFG80211_BSS_FTYPE_PRESP, other_bssid, + 0, 0x1234, 100, ies, sizeof(ies), + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, bss2); + bss2->use_for = 0; + extack._msg = NULL; + found = __cfg80211_get_bss(wiphy, NULL, NULL, "TEST", 4, + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY, + NL80211_BSS_USE_FOR_NORMAL, &extack); + KUNIT_EXPECT_NULL(test, found); + KUNIT_EXPECT_STREQ(test, extack._msg, + "BSS cannot be used for the requested operation"); + + cfg80211_put_bss(wiphy, bss2); + cfg80211_put_bss(wiphy, bss); +} + static struct inform_bss_ml_sta_case { const char *desc; int mld_id; @@ -855,6 +974,7 @@ kunit_test_suite(gen_new_ie); static struct kunit_case inform_bss_test_cases[] = { KUNIT_CASE(test_inform_bss_ssid_only), + KUNIT_CASE(test_get_bss_miss_reason), KUNIT_CASE_PARAM(test_inform_bss_ml_sta, inform_bss_ml_sta_gen_params), {} }; -- 2.55.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-07-22 7:07 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-11 4:34 [PATCH] wifi: nl80211: say why the association BSS lookup failed Louis Kotze 2026-07-21 15:31 ` Johannes Berg 2026-07-21 18:11 ` [PATCH v2 0/2] wifi: cfg80211: say why the auth/assoc " Louis Kotze 2026-07-21 18:11 ` [PATCH v2 1/2] " Louis Kotze 2026-07-21 22:10 ` Johannes Berg 2026-07-22 7:07 ` Louis Kotze 2026-07-22 7:07 ` [PATCH v3 0/2] " Louis Kotze 2026-07-22 7:07 ` [PATCH v3 1/2] " Louis Kotze 2026-07-22 7:07 ` [PATCH v3 2/2] wifi: cfg80211: tests: check BSS lookup failure reasons Louis Kotze 2026-07-21 18:11 ` [PATCH v2 " Louis Kotze
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.