Linux wireless drivers development
 help / color / mirror / Atom feed
* [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