]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
AP MLD: Send Link Reconfiguration Response after validation
authorPooventhiran G <quic_pooventh@quicinc.com>
Mon, 16 Jun 2025 11:18:58 +0000 (16:48 +0530)
committerJouni Malinen <j@w1.fi>
Wed, 18 Jun 2025 09:08:07 +0000 (12:08 +0300)
If a Link Reconfiguration Request is parsed and validated, send the
response frame to indicate success or failure for each operation
requested.

As long as one "add" request is accepted, Group KDEs and Basic MLE with
Per-STA profile for accepted links, and OCE element are added.

Reviewed-by: Rohan Dutta <quic_drohan@quicinc.com>
Signed-off-by: Pooventhiran G <quic_pooventh@quicinc.com>
src/ap/ieee802_11.c
src/ap/ieee802_11.h
src/ap/ieee802_11_eht.c
src/ap/wpa_auth.c
src/ap/wpa_auth.h

index d29fb29f5d23650276f43fab7b610d3f424bda0f..4df866d30d9c6d48a6cc51baa993689be9f4bfd4 100644 (file)
@@ -4743,8 +4743,8 @@ static int check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 
 #ifdef CONFIG_IEEE80211BE
 
-static void ieee80211_ml_build_assoc_resp(struct hostapd_data *hapd,
-                                         struct mld_link_info *link)
+void ieee80211_ml_build_assoc_resp(struct hostapd_data *hapd,
+                                  struct mld_link_info *link)
 {
        u8 buf[EHT_ML_MAX_STA_PROF_LEN];
        u8 *p = buf;
index befea10149dc1afffe5d1ab20080d0c2ff0ade95..509416e448c72addf0b9bb294fc1bb0e560780e3 100644 (file)
@@ -309,6 +309,8 @@ int ieee80211_ml_process_link(struct hostapd_data *hapd,
                              struct mld_link_info *link,
                              const u8 *ies, size_t ies_len,
                              enum link_parse_type type, bool offload);
+void ieee80211_ml_build_assoc_resp(struct hostapd_data *hapd,
+                                  struct mld_link_info *link);
 
 void ieee802_11_rx_protected_eht_action(struct hostapd_data *hapd,
                                        const struct ieee80211_mgmt *mgmt,
index c109c3242038fae47e0d53d2c4e52c324950ab10..d85478c5888bf055b4773ee9e900cd04dad6a862 100644 (file)
@@ -1611,6 +1611,252 @@ hostapd_ml_process_reconf_link(struct hostapd_data *hapd,
 }
 
 
+static int
+hostapd_reject_all_reconf_req(struct hostapd_data *hapd, u8 *pos,
+                             struct link_reconf_req_list *req_list)
+{
+       struct link_reconf_req_info *info;
+       struct hostapd_data *lhapd;
+       struct sta_info *lsta;
+       u16 status;
+       u8 *buf = pos;
+
+       dl_list_for_each(info, &req_list->add_req, struct link_reconf_req_info,
+                        list) {
+               lhapd = NULL;
+               lsta = NULL;
+               *pos++ = info->link_id;
+               status = info->status != WLAN_STATUS_SUCCESS ? info->status :
+                       WLAN_STATUS_UNSPECIFIED_FAILURE;
+               WPA_PUT_LE16(pos, status);
+
+               if (info->status == WLAN_STATUS_SUCCESS) {
+                       lhapd = hostapd_mld_get_link_bss(hapd, info->link_id);
+                       if (lhapd)
+                               lsta = ap_get_sta(lhapd,
+                                                 req_list->sta_mld_addr);
+
+                       if (lsta)
+                               ap_free_sta(lhapd, lsta);
+
+                       info->status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+               }
+               wpa_printf(MSG_DEBUG, "MLD: Reject add-link=%u with status=%u",
+                          info->link_id, status);
+               pos += 2;
+       }
+
+       dl_list_for_each(info, &req_list->del_req, struct link_reconf_req_info,
+                        list) {
+               *pos++ = info->link_id;
+               status = info->status != WLAN_STATUS_SUCCESS ? info->status :
+                       WLAN_STATUS_UNSPECIFIED_FAILURE;
+               WPA_PUT_LE16(pos, status);
+
+               if (info->status == WLAN_STATUS_SUCCESS)
+                       info->status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+               wpa_printf(MSG_DEBUG, "MLD: Reject del-link=%u with status=%u",
+                          info->link_id, status);
+               pos += 2;
+       }
+
+       return pos - buf;
+}
+
+
+static int
+hostapd_send_link_reconf_resp(struct hostapd_data *hapd,
+                             struct sta_info *assoc_sta,
+                             struct link_reconf_req_list *req_list)
+{
+       u8 *buf, *orig_pos, *pos;
+       struct ieee80211_mgmt *mgmt;
+       struct link_reconf_req_info *info;
+       struct mld_info mld;
+       int ret;
+       unsigned int count;
+       u8 dialog_token;
+       bool reject_all = false;
+       size_t len, pos_len, kde_len, mle_len;
+
+       count = dl_list_len(&req_list->add_req) +
+               dl_list_len(&req_list->del_req);
+       if (!count)
+               return 0;
+
+       os_memset(&mld, 0, sizeof(mld));
+
+       dialog_token = req_list->dialog_token;
+
+       /*
+        * Link Reconfiguration Response:
+        *
+        * IEEE80211 Header (24B) +
+        * Category (1B) + Action code (1B) + Dialog Token (1B) +
+        * Count (1B) + Status list (count * 3B) +
+        * Optional: Group Key Data field (variable) +
+        * Optional: OCI element (6B) +
+        * Optional: Basic Multi-Link element (variable)
+        */
+       len = IEEE80211_HDRLEN + 3 + 1 + count * 3;
+       kde_len = mle_len = 0;
+
+       if (req_list->links_add_ok) {
+               kde_len = wpa_auth_ml_group_kdes_len(
+                       assoc_sta->wpa_sm, req_list->links_add_ok) + 1;
+               len += kde_len;
+
+               if (wpa_auth_uses_ocv(assoc_sta->wpa_sm))
+                       len += OCV_OCI_EXTENDED_LEN;
+
+               mld.mld_sta = true;
+               dl_list_for_each(info, &req_list->add_req,
+                                struct link_reconf_req_info, list) {
+                       struct mld_link_info *link = &mld.links[info->link_id];
+                       struct hostapd_data *lhapd = NULL;
+
+                       if (info->status != WLAN_STATUS_SUCCESS)
+                               continue;
+
+                       link->status = info->status;
+
+                       lhapd = hostapd_mld_get_link_bss(hapd, info->link_id);
+                       if (!lhapd)
+                               continue;
+
+                       link->valid = true;
+                       ieee80211_ml_build_assoc_resp(lhapd, link);
+               }
+               mle_len = hostapd_eid_eht_ml_len(&mld, false);
+               len += mle_len;
+       }
+
+       buf = os_zalloc(len);
+       if (!buf) {
+               wpa_printf(MSG_INFO,
+                          "MLD: Failed to allocate Link Reconf Response buffer (%zu bytes)",
+                          len);
+               return -1;
+       }
+
+       mgmt = (struct ieee80211_mgmt *) buf;
+       mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
+                                          WLAN_FC_STYPE_ACTION);
+       os_memcpy(mgmt->da, assoc_sta->addr, ETH_ALEN);
+       os_memcpy(mgmt->sa, hapd->mld->mld_addr, ETH_ALEN);
+       os_memcpy(mgmt->bssid, hapd->mld->mld_addr, ETH_ALEN);
+
+       mgmt->u.action.category = WLAN_ACTION_PROTECTED_EHT;
+       mgmt->u.action.u.link_reconf_resp.action =
+               WLAN_PROT_EHT_LINK_RECONFIG_RESPONSE;
+       mgmt->u.action.u.link_reconf_resp.dialog_token = dialog_token;
+       mgmt->u.action.u.link_reconf_resp.count = count;
+
+       orig_pos = pos = mgmt->u.action.u.link_reconf_resp.variable;
+       pos_len = 28; /* IEEE80211 Header, category, code, token, count */
+
+       dl_list_for_each(info, &req_list->add_req, struct link_reconf_req_info,
+                        list) {
+               *pos++ = info->link_id;
+               WPA_PUT_LE16(pos, info->status);
+               pos += 2;
+               pos_len += 3;
+       }
+
+       dl_list_for_each(info, &req_list->del_req, struct link_reconf_req_info,
+                        list) {
+               /* Mark the status as INVALID for rejected link to recover */
+               if (!(req_list->links_del_ok & BIT(info->link_id)) &&
+                   info->status == WLAN_STATUS_SUCCESS)
+                       info->status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+               *pos++ = info->link_id;
+               WPA_PUT_LE16(pos, info->status);
+               pos += 2;
+               pos_len += 3;
+       }
+
+       if (!req_list->links_add_ok)
+               goto send_resp;
+
+       /* Key Data for add links */
+       if (kde_len) {
+               u8 *kde_pos = pos;
+
+               kde_pos = wpa_auth_ml_group_kdes(assoc_sta->wpa_sm, ++kde_pos,
+                                                req_list->links_add_ok);
+               *pos = kde_pos - pos - 1;
+               if (kde_len - 1 != *pos) {
+                       reject_all = true;
+                       goto reject_all_req;
+               }
+
+               wpa_hexdump_key(MSG_DEBUG, "MLD: Group KDE", pos + 1, *pos);
+
+               pos += kde_len;
+               pos_len += kde_len;
+       }
+
+       /* OCI element for add links */
+       if (wpa_auth_uses_ocv(assoc_sta->wpa_sm)) {
+               struct wpa_channel_info ci;
+
+               if (hostapd_drv_channel_info(hapd, &ci)) {
+                       wpa_printf(MSG_DEBUG,
+                                  "MLD: Failed to fetch OCI; reject all requests");
+                       reject_all = true;
+                       goto reject_all_req;
+               }
+
+               if (ocv_insert_extended_oci(&ci, pos)) {
+                       wpa_printf(MSG_DEBUG,
+                                  "MLD: Failed to add OCI element; reject all requests");
+                       reject_all = true;
+                       goto reject_all_req;
+               }
+
+               pos += OCV_OCI_EXTENDED_LEN;
+               pos_len += OCV_OCI_EXTENDED_LEN;
+       }
+
+       /* Basic Multi-Link element for add links */
+       if (mle_len) {
+               u8 *mle_pos = pos;
+
+               mle_pos = hostapd_eid_eht_basic_ml_common(hapd, mle_pos, &mld,
+                                                         false);
+               if ((size_t) (mle_pos - pos) != mle_len) {
+                       reject_all = true;
+                       goto reject_all_req;
+               }
+
+               pos += mle_len;
+               pos_len += mle_len;
+       }
+
+reject_all_req:
+       if (reject_all) {
+               pos = orig_pos;
+               pos_len = 28; /* reset pos_len */
+               pos += hostapd_reject_all_reconf_req(hapd, orig_pos, req_list);
+               pos_len += pos - orig_pos;
+
+               req_list->links_add_ok = req_list->links_del_ok = 0;
+               req_list->new_valid_links = 0;
+       }
+
+send_resp:
+       ret = hostapd_drv_send_mlme(hapd, mgmt, pos_len, 0, NULL, 0, 0);
+       os_free(buf);
+
+       if (mld.mld_sta)
+               ap_sta_free_sta_profile(&mld);
+
+       return ret;
+}
+
+
 /* Returns:
  * 0 = successful parsing
  * 1 = per-STA profile (subelement) skipped or rejected
@@ -2206,7 +2452,11 @@ skip_oci_validation:
                goto out;
 
        req_list->dialog_token = dialog_token;
-       ret = 0;
+       ret = hostapd_send_link_reconf_resp(hapd, assoc_sta, req_list);
+       if (ret)
+               wpa_printf(MSG_INFO,
+                          "MLD: Failed to send Link Reconfiguration Response (%d)",
+                          ret);
 
 out:
        if (ret) {
index ee6f46d21cde29e40ba2a4ef9a72938351ffb532..62c8c3c53d14f9e149d7672c8e2aaf4bb1586132 100644 (file)
@@ -4405,8 +4405,8 @@ size_t wpa_auth_ml_group_kdes_len(struct wpa_state_machine *sm, u16 req_links)
 }
 
 
-static u8 * wpa_auth_ml_group_kdes(struct wpa_state_machine *sm, u8 *pos,
-                                  u16 req_links)
+u8 * wpa_auth_ml_group_kdes(struct wpa_state_machine *sm, u8 *pos,
+                           u16 req_links)
 {
        struct wpa_auth_ml_key_info ml_key_info;
        unsigned int i, link_id;
index d3ea6eeb4f7fd3a210a6f6d4b071cbff7718b481..84c83c3f15fa1ef6cb00b907bcd4b3ebfac7e4b7 100644 (file)
@@ -688,6 +688,8 @@ void wpa_release_link_auth_ref(struct wpa_state_machine *sm, u8 link_id,
                               bool rejected);
 
 size_t wpa_auth_ml_group_kdes_len(struct wpa_state_machine *sm, u16 req_links);
+u8 * wpa_auth_ml_group_kdes(struct wpa_state_machine *sm, u8 *pos,
+                           u16 req_links);
 
 #define for_each_sm_auth(sm, link_id) \
        for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++)       \