[PATCH] Pass more capabilities of MLO link to framework
Bruno.Kremp at sony.com
Bruno.Kremp at sony.com
Mon Dec 15 05:43:53 PST 2025
Thank you for the comments and please help checking the new patch:
Pass Channel Bandwidth and Maximum Number of Spatial Stream of
MLO link to framework, the information is necessary to
calculate the theoretical maximum speed for MLO link.
Signed-off-by: Bruno Kremp <bruno.kremp at sony.com>
---
src/common/ieee802_11_common.c | 55 ++++++++++++++++++++++++++++++++++-----
src/common/ieee802_11_common.h | 9 ++++---
wpa_supplicant/events.c | 51 ++++++++++++++++++++++++++++++++++++
wpa_supplicant/wpa_supplicant_i.h | 3 +++
4 files changed, 108 insertions(+), 10 deletions(-)
diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c
index 5d1e02f81..c0ae42591 100644
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -1001,9 +1001,10 @@ void ieee802_11_elems_clear_ext_ids(struct ieee802_11_elems *elems,
}
-ParseRes ieee802_11_parse_link_assoc_req(struct ieee802_11_elems *elems,
- struct wpabuf *mlbuf,
- u8 link_id, bool show_errors)
+static ParseRes ieee802_11_parse_link_profile(struct ieee802_11_elems *elems,
+ struct wpabuf *mlbuf,
+ u8 link_id, bool show_errors,
+ bool is_assoc_resp)
{
const struct ieee80211_eht_ml *ml;
const u8 *pos;
@@ -1042,7 +1043,8 @@ ParseRes ieee802_11_parse_link_assoc_req(struct ieee802_11_elems *elems,
"MLD: Failed to parse MLE subelem");
goto out;
}
-
+ if ((size_t) num_frag_subelems * 2 > len)
+ goto out;
len -= num_frag_subelems * 2;
wpa_printf(MSG_DEBUG,
@@ -1101,6 +1103,26 @@ ParseRes ieee802_11_parse_link_assoc_req(struct ieee802_11_elems *elems,
pos += 2;
sub_elem_len -= 2;
+ /* For association response, check status code */
+ if (is_assoc_resp) {
+ if (sub_elem_len < 2) {
+ if (show_errors)
+ wpa_printf(MSG_DEBUG,
+ "MLD: missing status code");
+ goto out;
+ }
+
+ le16 status_code = WPA_GET_LE16(pos);
+ if (le_to_host16(status_code) != WLAN_STATUS_SUCCESS) {
+ wpa_printf(MSG_DEBUG,
+ "MLD: status code %u", status_code);
+ goto out;
+ }
+
+ pos += 2;
+ sub_elem_len -= 2;
+ }
+
/* Handle non-inheritance */
non_inherit = get_ie_ext(pos, sub_elem_len,
WLAN_EID_EXT_NON_INHERITANCE);
@@ -1160,6 +1182,24 @@ out:
}
+ParseRes ieee802_11_parse_link_assoc_req(struct ieee802_11_elems *elems,
+ struct wpabuf *mlbuf,
+ u8 link_id, bool show_errors)
+{
+ return ieee802_11_parse_link_profile(elems, mlbuf, link_id,
+ show_errors, false);
+}
+
+ParseRes ieee802_11_parse_link_assoc_resp(struct ieee802_11_elems *elems,
+ struct wpabuf *mlbuf,
+ u8 link_id, bool show_errors)
+{
+ /* ieee802_11_defrag_mle_subelem() handles subelement defragmentation
+ * in-place within mlbuf */
+ return ieee802_11_parse_link_profile(elems, mlbuf, link_id,
+ show_errors, true);
+}
+
int ieee802_11_ie_count(const u8 *ies, size_t ies_len)
{
const struct element *elem;
@@ -3486,9 +3526,9 @@ struct wpabuf * ieee802_11_defrag(const u8 *data, size_t len, bool ext_elem)
* of data, see IEEE Std 802.11be-2024, Figure 35-4 - Per-STA Profile subelement
* fragmentation within a fragmented Multi-Link element.
*/
-ssize_t ieee802_11_defrag_mle_subelem(struct wpabuf *mlbuf,
- const u8 *parent_subelem,
- size_t *defrag_len)
+int ieee802_11_defrag_mle_subelem(struct wpabuf *mlbuf,
+ const u8 *parent_subelem,
+ size_t *defrag_len)
{
u8 *buf, *pos, *end;
size_t len, subelem_len;
@@ -3524,6 +3564,7 @@ ssize_t ieee802_11_defrag_mle_subelem(struct wpabuf *mlbuf,
return -1;
os_memmove(pos, pos + 2, end - (pos + 2));
+ end -= 2;
pos += elen - 2;
subelem_len += elen - 2;
diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h
index 09bd0675a..f63379a7f 100644
--- a/src/common/ieee802_11_common.h
+++ b/src/common/ieee802_11_common.h
@@ -217,6 +217,9 @@ void ieee802_11_elems_clear_ext_ids(struct ieee802_11_elems *elems,
ParseRes ieee802_11_parse_link_assoc_req(struct ieee802_11_elems *elems,
struct wpabuf *mlbuf,
u8 link_id, bool show_errors);
+ParseRes ieee802_11_parse_link_assoc_resp(struct ieee802_11_elems *elems,
+ struct wpabuf *mlbuf,
+ u8 link_id, bool show_errors);
int ieee802_11_ie_count(const u8 *ies, size_t ies_len);
struct wpabuf * ieee802_11_vendor_ie_concat(const u8 *ies, size_t ies_len,
u32 oui_type);
@@ -382,9 +385,9 @@ int ieee802_edmg_is_allowed(struct ieee80211_edmg_config allowed,
struct ieee80211_edmg_config requested);
struct wpabuf * ieee802_11_defrag(const u8 *data, size_t len, bool ext_elem);
-ssize_t ieee802_11_defrag_mle_subelem(struct wpabuf *mlbuf,
- const u8 *parent_subelem,
- size_t *defrag_len);
+int ieee802_11_defrag_mle_subelem(struct wpabuf *mlbuf,
+ const u8 *parent_subelem,
+ size_t *defrag_len);
const u8 * get_ml_ie(const u8 *ies, size_t len, u8 type);
const u8 * get_basic_mle_mld_addr(const u8 *buf, size_t len);
const u8 * get_basic_mle_eml_capa(const u8 *buf, size_t len);
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index d831557b3..7a6c0ddf0 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -3501,6 +3501,57 @@ static void wpas_parse_connection_info(struct wpa_supplicant *wpa_s,
} else {
wpa_s->connection_channel_bandwidth = CHAN_WIDTH_20;
}
+
+ struct wpabuf *req_mlbuf = NULL, *resp_mlbuf = NULL;
+
+ req_mlbuf = ieee802_11_defrag(req_elems.basic_mle, req_elems.basic_mle_len, true);
+ resp_mlbuf = ieee802_11_defrag(resp_elems.basic_mle, resp_elems.basic_mle_len, true);
+
+ if (req_mlbuf && resp_mlbuf) {
+ for (int i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+ if (!(wpa_s->valid_links & BIT(i)))
+ continue;
+
+ struct ieee802_11_elems req_persta_elems = req_elems;
+ struct ieee802_11_elems resp_persta_elems = resp_elems;
+
+ if (ieee802_11_parse_link_assoc_req(&req_persta_elems, req_mlbuf, i, true) == ParseFailed ||
+ ieee802_11_parse_link_assoc_resp(&resp_persta_elems, resp_mlbuf, i, true) == ParseFailed) {
+ wpa_s->links[i].max_nss_rx = wpa_s->connection_max_nss_rx;
+ wpa_s->links[i].max_nss_tx = wpa_s->connection_max_nss_tx;
+ wpa_s->links[i].channel_bandwidth = wpa_s->connection_channel_bandwidth;
+ continue;
+ }
+
+ wpa_s->links[i].max_nss_rx =
+ get_max_nss_capability(&req_persta_elems, 1) > get_max_nss_capability(&resp_persta_elems, 1)
+ ? get_max_nss_capability(&resp_persta_elems, 1)
+ : get_max_nss_capability(&req_persta_elems, 1);
+
+ wpa_s->links[i].max_nss_tx =
+ get_max_nss_capability(&req_persta_elems, 0) > get_max_nss_capability(&resp_persta_elems, 0)
+ ? get_max_nss_capability(&resp_persta_elems, 0)
+ : get_max_nss_capability(&req_persta_elems, 0);
+
+ struct supported_chan_width sta_link_supported_chan_width =
+ get_supported_channel_width(&req_persta_elems);
+ enum chan_width ap_link_operation_chan_width =
+ get_operation_channel_width(&resp_persta_elems);
+
+ if (wpa_s->connection_vht || wpa_s->connection_he || wpa_s->connection_eht) {
+ wpa_s->links[i].channel_bandwidth =
+ get_sta_operation_chan_width(ap_link_operation_chan_width,
+ sta_link_supported_chan_width);
+ } else if (wpa_s->connection_ht) {
+ wpa_s->links[i].channel_bandwidth =
+ (ap_link_operation_chan_width == CHAN_WIDTH_40) ? CHAN_WIDTH_40 : CHAN_WIDTH_20;
+ } else {
+ wpa_s->links[i].channel_bandwidth = CHAN_WIDTH_20;
+ }
+ }
+ wpabuf_free(req_mlbuf);
+ wpabuf_free(resp_mlbuf);
+ }
}
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 486ea0b98..c1e991179 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -749,6 +749,9 @@ struct wpa_supplicant {
struct wpa_bss *bss;
bool disabled;
struct wpabuf *ies;
+ unsigned int max_nss_rx:4;
+ unsigned int max_nss_tx:4;
+ enum chan_width channel_bandwidth;
} links[MAX_NUM_MLD_LINKS];
u8 *last_con_fail_realm;
size_t last_con_fail_realm_len;
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 486ea0b98..c1e991179 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -749,6 +749,9 @@ struct wpa_supplicant {
struct wpa_bss *bss;
bool disabled;
struct wpabuf *ies;
+ unsigned int max_nss_rx:4;
+ unsigned int max_nss_tx:4;
+ enum chan_width channel_bandwidth;
} links[MAX_NUM_MLD_LINKS];
u8 *last_con_fail_realm;
size_t last_con_fail_realm_len;
On Tue, Dec 02, 2025 at 03:09:38PM +0200, Jouni Malinen wrote:
> On Fri, Oct 03, 2025 at 07:44:55AM +0000, mailto:Bruno.Kremp at sony.com wrote:
> > Pass Channel Bandwidth and Maximum Number of Spatial Stream
> > of MLO link to framework, the information is necessary to
> > calculate the theoretical maximum speed for MLO link.
>
> This seems to be based on a modified hostap.git implementation since
> compilation with these changes would fail due to various missing
> functions and variables:
The previously missing commit in now in hostap.git, so I took a closer
look at the way this per-link variant is implemented.
It looks like ieee802_11_parse_link_assoc_resp() duplicates a
significant amount (i.e., most..) of functionality from the existing
ieee802_11_parse_link_assoc_req() function. This is not ideal and
instead, these two cases should be using a shared helper function to
avoid such code duplication.
The main difference between the existing functionality and the new one
seems to be in defragmentation of the Per-STA Profile information. The
new ieee802_11_defrag_per_sta_data() function would seem to duplicate
existing functionality in ieee802_11_defrag_mle_subelem(). That is not
really good way of doing this and instead, that existing hostap.git
functionality should be used which would be the case with that shared
helper function mentioned above.
Please also note that I cleaned up wpa_supplicant_event_associnfo() by
splitting the functionality related to determining connection
information into a separate helper function to avoid excessive
indentation and even longer function. In other words, the
wpa_supplicant/events.c changes would be in a bit different location in
the file.
--
Jouni Malinen PGP id EFC895FA
More information about the Hostap
mailing list