* [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; 6+ 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] 6+ 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; 6+ 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] 6+ 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 2/2] wifi: cfg80211: tests: check BSS lookup failure reasons Louis Kotze
0 siblings, 2 replies; 6+ 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] 6+ 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 2/2] wifi: cfg80211: tests: check BSS lookup failure reasons Louis Kotze
1 sibling, 1 reply; 6+ 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] 6+ 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; 6+ 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] 6+ 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
0 siblings, 0 replies; 6+ 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] 6+ messages in thread
end of thread, other threads:[~2026-07-21 22:11 UTC | newest]
Thread overview: 6+ 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-21 18:11 ` [PATCH v2 2/2] wifi: cfg80211: tests: check BSS lookup failure reasons Louis Kotze
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox