[PATCH] Pass more capabilities of MLO link to framework

Bruno.Kremp at sony.com Bruno.Kremp at sony.com
Fri Oct 3 00:44:55 PDT 2025


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    | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/common/ieee802_11_common.h    |   4 ++++
 wpa_supplicant/events.c           |  64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 wpa_supplicant/wpa_supplicant_i.h |   3 +++
 4 files changed, 278 insertions(+)

diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c
index ae7a7647a..206139916 100644
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -1146,6 +1146,171 @@ out:
 	return res;
 }
 
+ParseRes ieee802_11_parse_link_assoc_resp(struct ieee802_11_elems *elems,
+					  struct wpabuf *mlbuf, struct wpabuf **sub_elem_buf,
+					  u8 link_id, bool show_errors)
+{
+	const struct ieee80211_eht_ml *ml;
+	const u8 *pos;
+	ParseRes res = ParseFailed;
+
+	pos = wpabuf_head(mlbuf);
+	size_t len = wpabuf_len(mlbuf);
+
+	/* Must have control and common info length */
+	if (len < sizeof(*ml) + 1 || len < sizeof(*ml) + pos[sizeof(*ml)])
+		goto out;
+
+	ml = (const struct ieee80211_eht_ml *) pos;
+
+	/* As we are interested with the Per-STA profile, ignore other types */
+	if ((le_to_host16(ml->ml_control) & MULTI_LINK_CONTROL_TYPE_MASK) !=
+		MULTI_LINK_CONTROL_TYPE_BASIC)
+		goto out;
+
+	/* Skip the common info */
+	len -= sizeof(*ml) + pos[sizeof(*ml)];
+	pos += sizeof(*ml) + pos[sizeof(*ml)];
+
+	while (len > 2) {
+		size_t sub_elem_len = *(pos + 1), frag_len = sub_elem_len;
+		const u8 *start = pos;
+		size_t sta_info_len;
+		u16 link_info_control;
+		const u8 *non_inherit;
+
+		wpa_printf(MSG_DEBUG,
+			   "MLD: sub element: len=%zu, sub_elem_len=%zu",
+			   len, sub_elem_len);
+
+		if (2 + sub_elem_len > len) {
+			if (show_errors)
+				wpa_printf(MSG_DEBUG,
+					   "MLD: error: len=%zu, sub_elem_len=%zu",
+					   len, sub_elem_len);
+			goto out;
+		}
+
+		if (*pos != 0) {
+			pos += 2 + sub_elem_len;
+			len -= 2 + sub_elem_len;
+			continue;
+		}
+
+		if (sub_elem_len < 3) {
+			if (show_errors)
+				wpa_printf(MSG_DEBUG,
+					   "MLD: error: sub_elem_len=%zu < 5",
+					   sub_elem_len);
+			goto out;
+		}
+
+		link_info_control = WPA_GET_LE16(pos + 2);
+
+		if ((link_info_control & BASIC_MLE_STA_CTRL_LINK_ID_MASK) != link_id) {
+			pos += 2 + sub_elem_len;
+			len -= 2 + sub_elem_len;
+			continue;
+		}
+
+		sta_info_len = *(pos + 4);
+		if (sub_elem_len < sta_info_len + 3 || sta_info_len < 1) {
+			if (show_errors)
+				wpa_printf(MSG_DEBUG,
+					   "MLD: error: sub_elem_len=%zu, sta_info_len=%zu",
+					   sub_elem_len, sta_info_len);
+			goto out;
+		}
+
+		pos += sta_info_len + 4;
+		sub_elem_len -= sta_info_len + 2;
+
+		if (sub_elem_len < 2) {
+			if (show_errors)
+				wpa_printf(MSG_DEBUG,
+					   "MLD: missing capability info");
+			goto out;
+		}
+
+		pos += 2;
+		sub_elem_len -= 2;
+
+		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;
+
+		if (frag_len == 255) {
+			*sub_elem_buf = ieee802_11_defrag_per_sta_data(start, len);
+			if (!*sub_elem_buf)
+			{
+				goto out;
+			}
+			sub_elem_len = wpabuf_size(*sub_elem_buf) - 2 - (frag_len - sub_elem_len);
+			pos = (u8 *)wpabuf_head(*sub_elem_buf) + (pos - start);
+		}
+
+		/* Handle non-inheritance */
+		non_inherit = get_ie_ext(pos, sub_elem_len,
+					 WLAN_EID_EXT_NON_INHERITANCE);
+		if (non_inherit && non_inherit[1] > 1) {
+			u8 non_inherit_len = non_inherit[1] - 1;
+
+			/*
+			 * Do not include the Non-Inheritance element when
+			 * parsing below. It should be the last element in the
+			 * subelement.
+			 */
+			if (3U + non_inherit_len > sub_elem_len)
+				goto out;
+			sub_elem_len -= 3 + non_inherit_len;
+
+			/* Skip the ID, length and extension ID */
+			non_inherit += 3;
+
+			if (non_inherit_len < 1UL + non_inherit[0]) {
+				if (show_errors)
+					wpa_printf(MSG_DEBUG,
+						   "MLD: Invalid inheritance");
+				goto out;
+			}
+
+			ieee802_11_elems_clear_ids(elems, &non_inherit[1],
+						   non_inherit[0]);
+
+			non_inherit_len -= 1 + non_inherit[0];
+			non_inherit += 1 + non_inherit[0];
+
+			if (non_inherit_len < 1UL + non_inherit[0]) {
+				if (show_errors)
+					wpa_printf(MSG_DEBUG,
+						   "MLD: Invalid inheritance");
+				goto out;
+			}
+
+			ieee802_11_elems_clear_ext_ids(elems, &non_inherit[1],
+						       non_inherit[0]);
+		}
+
+		wpa_printf(MSG_DEBUG, "MLD: link: sub_elem_len=%zu",
+			   sub_elem_len);
+
+		if (sub_elem_len)
+			res = __ieee802_11_parse_elems(pos, sub_elem_len,
+						       elems, show_errors);
+		else
+			res = ParseOK;
+		break;
+	}
+
+out:
+	return res;
+}
 
 int ieee802_11_ie_count(const u8 *ies, size_t ies_len)
 {
@@ -3458,6 +3623,48 @@ struct wpabuf * ieee802_11_defrag(const u8 *data, size_t len, bool ext_elem)
 	return buf;
 }
 
+struct wpabuf * ieee802_11_defrag_per_sta_data(const u8 *data, size_t len)
+{
+	struct wpabuf *buf;
+	const u8 *pos, *end = data + len;
+
+	// subEID(1 oct) + length(1 oct) + max length of subelement(255 oct)
+	const size_t min_defrag_len = 258;
+
+	if (!data || !len)
+		return NULL;
+
+	if (len < min_defrag_len) {
+		return wpabuf_alloc_copy(data, len);
+	}
+
+	buf = wpabuf_alloc_copy(data, min_defrag_len - 1);
+	if (!buf)
+		return NULL;
+
+	pos = &data[min_defrag_len - 1];
+	len -= min_defrag_len - 1;
+	while (len > 2 && pos[0] == (int)EHT_ML_SUB_ELEM_FRAGMENT  && pos[1]) {
+		int ret;
+		size_t elen = 2 + pos[1];
+
+		if (elen > (size_t) (end - pos) || elen > len)
+			break;
+		ret = wpabuf_resize(&buf, pos[1]);
+		if (ret < 0) {
+			wpabuf_free(buf);
+			return NULL;
+		}
+
+		/* Copy only the fragment data (without the SubEID and length) */
+		wpabuf_put_data(buf, &pos[2], pos[1]);
+		pos += elen;
+		len -= elen;
+	}
+
+	return buf;
+}
+
 
 /**
  * ieee802_11_defrag_mle_subelem - Defragment Multi-Link element subelements
diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h
index 5f52b573c..bcca51f6a 100644
--- a/src/common/ieee802_11_common.h
+++ b/src/common/ieee802_11_common.h
@@ -215,6 +215,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, struct wpabuf **sub_elem_buf,
+					  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);
@@ -383,6 +386,7 @@ struct wpabuf * ieee802_11_defrag(const u8 *data, size_t len, bool ext_elem);
 size_t ieee802_11_defrag_mle_subelem(struct wpabuf *mlbuf,
 				     const u8 *parent_subelem,
 				     size_t *defrag_len);
+struct wpabuf * ieee802_11_defrag_per_sta_data(const u8 *data, size_t 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);
 
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index 42a52899d..319e46bfd 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -3525,6 +3525,70 @@ static int wpa_supplicant_event_associnfo(struct wpa_supplicant *wpa_s,
 				resp_elems.eht_capabilities;
 			if (req_elems.rrm_enabled)
 				wpa_s->rrm.rrm_used = 1;
+
+			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,
+						resp_persta_elems = resp_elems;
+					struct wpabuf *sub_elem_buf = NULL;
+
+					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,
+                                                &sub_elem_buf, 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;
+						if (sub_elem_buf)
+							wpabuf_free(sub_elem_buf);
+						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;
+					}
+					if (sub_elem_buf)
+						wpabuf_free(sub_elem_buf);
+				}
+				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 e7675a4ab..16c797c83 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -742,6 +742,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;



More information about the Hostap mailing list