From c39be6df437aa02d2803b291a635dd6ff4ee5b93 Mon Sep 17 00:00:00 2001 From: Sriram R Date: Fri, 10 Nov 2023 11:48:23 +0530 Subject: [PATCH] hostapd: support ML probe response Add support to respond to ML probe requests with ML probe response contaitning the sta profiles of the requested links. Signed-off-by: Sriram R --- src/ap/beacon.c | 342 ++++++++++++++++++++++++++++++++- src/ap/hostapd.h | 4 + src/ap/ieee802_11.c | 8 +- src/ap/ieee802_11.h | 2 +- src/ap/ieee802_11_eht.c | 4 +- src/ap/sta_info.h | 33 ++-- src/common/ieee802_11_common.c | 29 +++ src/common/ieee802_11_common.h | 1 + 8 files changed, 390 insertions(+), 33 deletions(-) --- a/src/ap/beacon.c +++ b/src/ap/beacon.c @@ -36,6 +36,12 @@ #ifdef NEED_AP_MLME +static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd, + const struct ieee80211_mgmt *req, + int is_p2p, size_t *resp_len, + const u8 *known_bss, u8 known_bss_len, + bool ml_probe); + static u8 * hostapd_eid_bss_load(struct hostapd_data *hapd, u8 *eid, size_t len) { if (len < 2 + 5) @@ -593,10 +599,308 @@ static u8 * hostapd_eid_mbssid_config(st } +static bool hostapd_is_restricted_eid_in_sta_profile(u8 eid) +{ + switch(eid) { + case WLAN_EID_SSID: + case WLAN_EID_TIM: + case WLAN_EID_BSS_MAX_IDLE_PERIOD: + case WLAN_EID_MULTIPLE_BSSID: + case WLAN_EID_REDUCED_NEIGHBOR_REPORT: + case WLAN_EID_NEIGHBOR_REPORT: + return true; + } + + return false; +} + +static bool hostapd_is_restricted_ext_eid_in_sta_profile(u8 eid) +{ + switch(eid) { + case WLAN_EID_EXT_MULTI_LINK: + return true; + } + return false; +} + +/* Create the link sta profile based on inheritance from the reporting + * profile. If @sta_profile is NULL, only the length of the sta_profile + * is returned + */ +static size_t hostapd_add_sta_profile(struct ieee80211_mgmt *own_fdata, + struct ieee80211_mgmt *link_fdata, + size_t own_data_len, + size_t link_data_len, + u8 *sta_profile, u32 type) +{ + /* the bitmap of parsed eids, max eid is 256, hence 32bytes to store the bitmap */ + u8 parsed_eid_bmap[32] = {0}, parsed_ext_eid_bmap[32] = {0}; + u8 oeid, leid, oelen, lelen; + const u8 *oelem_data, *lelem_data; + u8 *own_data, *link_data; + const struct element *oelem, *lelem; + bool is_ext, ie_found; + /* extra len used in the logic incudes the element id and len */ + u8 extra_len = 2; + size_t sta_profile_len = 0; + u8 ninherit_elemlist[254] = {0}, ninherit_elemlist_len = 0; + u8 ninherit_elemextlist[254] = {0}, ninherit_elemextlist_len = 0; + + /* include len for capab info */ + sta_profile_len += sizeof(le16); + + if (type == WLAN_FC_STYPE_PROBE_RESP) { + if (sta_profile) { + memcpy(sta_profile, &link_fdata->u.probe_resp.capab_info, sizeof(le16)); + sta_profile += sizeof(le16); + } + + own_data = own_fdata->u.probe_resp.variable; + link_data = link_fdata->u.probe_resp.variable; + } else { + if (sta_profile) { + memcpy(sta_profile, &link_fdata->u.assoc_resp.capab_info, sizeof(le16)); + sta_profile += sizeof(le16); + + memcpy(sta_profile, &link_fdata->u.assoc_resp.status_code, sizeof(le16)); + sta_profile += sizeof(le16); + } + + /* include len for status code */ + sta_profile_len += sizeof(le16); + + own_data = own_fdata->u.assoc_resp.variable; + link_data = link_fdata->u.assoc_resp.variable; + } + + /* The below logic takes the reporting bss data and reported bss data and performs + * intersection to build the sta profile of the reported bss. Also certain IEs are + * not added to the sta profile as recommended in spec. Matching IE information + * in the reporting bss profile are ignored in the sta profile. Remaining IEs + * pertaining to the sta profile are appended at the end. + * Same logic is used by hostapd_sta_profile_len() to determine the length of + * the sta profile. + */ + for_each_element(oelem, own_data, own_data_len) { + ie_found = false; + is_ext = false; + oelem_data = oelem->data; + oelen = oelem->datalen; + if (oelem->id == WLAN_EID_EXTENSION) { + is_ext = true; + oeid = *(oelem_data); + if (hostapd_is_restricted_ext_eid_in_sta_profile(oeid)) + continue; + } else { + oeid = oelem->id; + if (hostapd_is_restricted_eid_in_sta_profile(oeid)) + continue; + } + + for_each_element(lelem, link_data, link_data_len) { + if ((lelem->id == WLAN_EID_EXTENSION && !is_ext) || + (is_ext && lelem->id != WLAN_EID_EXTENSION)) + continue; + + lelem_data = lelem->data; + lelen = lelem->datalen; + if (lelem->id == WLAN_EID_EXTENSION) { + leid = *(lelem_data); + } else { + leid = lelem->id; + } + + if (oeid == leid) { + ie_found = true; + if (oelen != lelen || + memcmp(oelem->data, lelem->data, oelen)) { + if (sta_profile) { + memcpy(sta_profile, lelem->data - extra_len, lelen + extra_len); + sta_profile += lelen + extra_len; + } + sta_profile_len += lelen + extra_len; + } + + if (is_ext) + parsed_ext_eid_bmap[oeid/8] |= BIT(oeid%8); + else + parsed_eid_bmap[oeid/8] |= BIT(oeid%8); + + break; + } + } + if (!ie_found) { + if (is_ext) + ninherit_elemextlist[ninherit_elemextlist_len++] = oeid; + else + ninherit_elemlist[ninherit_elemlist_len++] = oeid; + } + } + + /* parse the remaining elements in the link profile */ + for_each_element(lelem, link_data, link_data_len) { + lelem_data = lelem->data; + lelen = lelem->datalen; + if (lelem->id == WLAN_EID_EXTENSION) { + leid = *(lelem_data); + if (parsed_ext_eid_bmap[leid/8] & BIT(leid%8)) + continue; + if (hostapd_is_restricted_ext_eid_in_sta_profile(leid)) + continue; + } else { + leid = lelem->id; + if (parsed_eid_bmap[leid/8] & BIT(leid%8)) + continue; + if (hostapd_is_restricted_eid_in_sta_profile(leid)) + continue; + } + + if (sta_profile) { + memcpy(sta_profile, lelem->data - extra_len, lelen + extra_len); + sta_profile += lelen + extra_len; + } + sta_profile_len += lelen + extra_len; + } + + if (sta_profile && (ninherit_elemlist_len || ninherit_elemextlist_len)) { + *sta_profile = WLAN_EID_EXTENSION; + sta_profile++; + + *sta_profile = ninherit_elemlist_len + ninherit_elemextlist_len + 3; + sta_profile++; + + *sta_profile = WLAN_EID_EXT_NON_INHERITANCE; + sta_profile++; + + *sta_profile = ninherit_elemlist_len; + sta_profile++; + if (ninherit_elemlist_len) + memcpy(sta_profile, ninherit_elemlist, ninherit_elemlist_len); + sta_profile += ninherit_elemlist_len; + + *sta_profile = ninherit_elemextlist_len; + sta_profile++; + if (ninherit_elemextlist_len) + memcpy(sta_profile, ninherit_elemextlist, ninherit_elemextlist_len); + sta_profile += ninherit_elemextlist_len; + + } + + /* Non-Inheritance IE = EID_EXT(1byte) + Total len field(1byte) + + * Ext tag number (1byte) + ninherit_elemlist_len(1byte) + ninherit_elemextlist_len(1byte) + + * data + */ + if (ninherit_elemlist_len || ninherit_elemextlist_len) + sta_profile_len += 3 + 2 + ninherit_elemlist_len + ninherit_elemextlist_len; + + return sta_profile_len; +} + +static u8 * hostapd_gen_sta_profile(struct ieee80211_mgmt *own_data, + struct ieee80211_mgmt *link_data, + size_t own_data_len, + size_t link_data_len, + size_t *sta_profile_len, u32 type) +{ + u8 *sta_profile; + + if (type != WLAN_FC_STYPE_PROBE_RESP && + type != WLAN_FC_STYPE_ASSOC_RESP) + return NULL; + + *sta_profile_len = hostapd_add_sta_profile(own_data, link_data, + own_data_len, + link_data_len, + NULL, type); + + if (!(*sta_profile_len)) + return NULL; + + sta_profile = os_zalloc(*sta_profile_len); + + if (sta_profile == NULL) + return NULL; + + hostapd_add_sta_profile(own_data, link_data, own_data_len, + link_data_len, sta_profile, type); + + return sta_profile; +} + +static void hostapd_gen_per_sta_profiles(struct hostapd_data *hapd) +{ + struct ieee80211_mgmt *own_data, *link_data; + struct hostapd_data *link_bss; + size_t own_data_len, link_data_len, sta_profile_len; + u8 *sta_profile, link_id; + + if (!hapd->conf->mld_ap) + return; + + if (dl_list_empty(&hapd->mld->links)) + return; + + own_data = (struct ieee80211_mgmt *)hostapd_gen_probe_resp(hapd, NULL, false, + &own_data_len, + NULL, 0, false); + + if (own_data == NULL) { + wpa_printf(MSG_ERROR, "Error building per sta profile"); + return; + } + + own_data_len -= offsetof(struct ieee80211_mgmt, u.probe_resp.variable); + + for_each_partner_bss(link_bss, hapd) { + if (link_bss == hapd) + continue; + + link_id = link_bss->mld_link_id; + + if (link_id > MAX_NUM_MLD_LINKS) + continue; + + sta_profile = NULL; + sta_profile_len = 0; + + link_data = (struct ieee80211_mgmt *)hostapd_gen_probe_resp(link_bss, NULL, false, + &link_data_len, + NULL, 0, false); + + if (link_data == NULL) { + wpa_printf(MSG_ERROR, "Couldnt generate Link STA profile"); + continue; + } + + link_data_len -= offsetof(struct ieee80211_mgmt, u.probe_resp.variable); + + sta_profile = hostapd_gen_sta_profile(own_data, link_data, + own_data_len, link_data_len, + &sta_profile_len, + WLAN_FC_STYPE_PROBE_RESP); + + if (sta_profile == NULL || sta_profile_len > EHT_ML_MAX_STA_PROF_LEN) { + wpa_printf(MSG_ERROR, "Couldnt generate Link STA profile"); + continue; + } + + memcpy(hapd->partner_links[link_id].resp_sta_profile, sta_profile, sta_profile_len); + hapd->partner_links[link_id].resp_sta_profile_len = sta_profile_len; + memcpy(hapd->partner_links[link_id].local_addr, link_bss->own_addr, ETH_ALEN); + hapd->partner_links[link_id].valid = true; + + os_free(link_data); + os_free(sta_profile); + } + + os_free(own_data); +} + static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd, const struct ieee80211_mgmt *req, int is_p2p, size_t *resp_len, - const u8 *known_bss, u8 known_bss_len) + const u8 *known_bss, u8 known_bss_len, + bool ml_probe) { struct ieee80211_mgmt *resp; u8 *pos, *epos, *csa_pos; @@ -656,12 +960,17 @@ static u8 * hostapd_gen_probe_resp(struc /* * TODO: Multi-Link element has variable length and can be * long based on the common info and number of per - * station profiles. For now use 256. + * station profiles. For now use 256 for non ML probe resp + * and 1024 for ML probe resp */ if (hapd->conf->mld_ap) { - buflen += 256; - /* inclusion on MCST IE is mandatory */ - buflen += 6; + if (ml_probe) { + buflen += 1024; + } else { + buflen += 256; + /* inclusion on MCST IE is mandatory */ + buflen += 6; + } } /* QCN Vendor IE for 240MHz */ if (is_5ghz_freq(hapd->iface->freq)) @@ -836,8 +1145,13 @@ static u8 * hostapd_gen_probe_resp(struc #ifdef CONFIG_IEEE80211BE if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) { - if (hapd->conf->mld_ap) - pos = hostapd_eid_eht_basic_ml(hapd, pos, NULL, true); + if (hapd->conf->mld_ap) { + if (ml_probe) + pos = hostapd_eid_eht_basic_ml(hapd, pos, + hapd->partner_links, true); + else + pos = hostapd_eid_eht_basic_ml(hapd, pos, NULL, true); + } pos = hostapd_eid_eht_capab(hapd, pos, IEEE80211_MODE_AP); pos = hostapd_eid_eht_operation(hapd, pos, IEEE80211_MODE_AP); pos = hostapd_eid_vendor_240mhz(hapd, pos, IEEE80211_MODE_AP); @@ -1118,6 +1432,8 @@ void handle_probe_req(struct hostapd_dat .ssi_signal = ssi_signal, .elems = &elems, }; + bool ml_probe = false; + struct wpabuf *mlbuf = NULL; if (hapd->iconf->rssi_ignore_probe_request && ssi_signal && ssi_signal < hapd->iconf->rssi_ignore_probe_request) @@ -1355,9 +1671,16 @@ void handle_probe_req(struct hostapd_dat wpa_msg_ctrl(hapd->msg_ctx, MSG_INFO, RX_PROBE_REQUEST "sa=" MACSTR " signal=%d", MAC2STR(mgmt->sa), ssi_signal); +#ifdef CONFIG_IEEE80211BE + mlbuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_PROBE_REQ); + if (mlbuf && ieee802_11_parse_ml_probe_req(mlbuf)) { + ml_probe = true; + } + wpabuf_free(mlbuf); +#endif resp = hostapd_gen_probe_resp(hapd, mgmt, elems.p2p != NULL, &resp_len, elems.mbssid_known_bss, - elems.mbssid_known_bss_len); + elems.mbssid_known_bss_len, ml_probe); if (resp == NULL) return; @@ -1427,7 +1750,7 @@ static u8 * hostapd_probe_resp_offloads( "this"); /* Generate a Probe Response template for the non-P2P case */ - return hostapd_gen_probe_resp(hapd, NULL, 0, resp_len, NULL, 0); + return hostapd_gen_probe_resp(hapd, NULL, 0, resp_len, NULL, 0, false); } #endif /* NEED_AP_MLME */ @@ -1458,7 +1781,7 @@ static u8 * hostapd_unsol_bcast_probe_re return hostapd_gen_probe_resp(hapd, NULL, 0, ¶ms->unsol_bcast_probe_resp_tmpl_len, - NULL, 0); + NULL, 0, false); } #endif /* CONFIG_IEEE80211AX */ @@ -2398,6 +2721,9 @@ int ieee802_11_set_beacon(struct hostapd } } + /* Generate per sta profiles for affiliated APs */ + hostapd_gen_per_sta_profiles(hapd); + return 0; } --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -36,6 +36,7 @@ struct ieee80211_ht_capabilities; struct full_dynamic_vlan; enum wps_event; union wps_event_data; +struct mld_link_info; #ifdef CONFIG_MESH struct mesh_conf; #endif /* CONFIG_MESH */ @@ -501,6 +502,9 @@ struct hostapd_data { struct dl_list link; u8 mld_link_id; char ctrl_sock_iface[IFNAMSIZ + 1]; + + /* Store the partner info for ML probe response */ + struct mld_link_info partner_links[MAX_NUM_MLD_LINKS]; #endif /* CONFIG_IEEE80211BE */ }; --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -4446,10 +4446,10 @@ static void ieee80211_ml_process_link(st goto out; if (ieee802_11_parse_link_assoc_req(ies, ies_len, &elems, mlbuf, - hapd->mld_link_id, true) == - ParseFailed) { + hapd->mld_link_id, true) == + ParseFailed) { wpa_printf(MSG_DEBUG, - "MLD: link: Failed to parse association request Multi-Link element"); + "MLD: link: Failed to parse association request Multi-Link element"); status = WLAN_STATUS_UNSPECIFIED_FAILURE; goto out; } @@ -4946,7 +4946,7 @@ rsnxe_done: #ifdef CONFIG_IEEE80211BE if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) { if (hapd->conf->mld_ap) - p = hostapd_eid_eht_basic_ml(hapd, p, sta, false); + p = hostapd_eid_eht_basic_ml(hapd, p, sta->mld_info.links, false); p = hostapd_eid_eht_capab(hapd, p, IEEE80211_MODE_AP); p = hostapd_eid_eht_operation(hapd, p, IEEE80211_MODE_AP); p = hostapd_eid_vendor_240mhz(hapd, p, IEEE80211_MODE_AP); --- a/src/ap/ieee802_11.h +++ b/src/ap/ieee802_11.h @@ -97,7 +97,7 @@ void hostapd_get_eht_capab(struct hostap struct ieee80211_eht_capabilities *dest, size_t len); u8 * hostapd_eid_eht_basic_ml(struct hostapd_data *hapd, u8 *eid, - struct sta_info *info, bool include_mld_id); + struct mld_link_info *link_info, bool include_mld_id); size_t hostapd_eid_eht_basic_ml_len(struct hostapd_data *hapd, struct sta_info *info, bool include_mld_id); struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd); --- a/src/ap/ieee802_11_eht.c +++ b/src/ap/ieee802_11_eht.c @@ -552,7 +552,7 @@ void hostapd_get_eht_capab(struct hostap */ #define MLE_COMMON_INFO_LEN 13 u8 * hostapd_eid_eht_basic_ml(struct hostapd_data *hapd, u8 *eid, - struct sta_info *info, bool include_mld_id) + struct mld_link_info *info, bool include_mld_id) { struct wpabuf *buf; u16 control; @@ -617,7 +617,7 @@ u8 * hostapd_eid_eht_basic_ml(struct hos /* Add link info for the other links */ for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { - struct mld_link_info *link = &info->mld_info.links[link_id]; + struct mld_link_info *link = &info[link_id]; struct hostapd_data *link_bss; /* --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -54,6 +54,7 @@ #define WLAN_SUPP_HT_RATES_MAX 77 struct hostapd_data; +struct mld_link_info; struct mbo_non_pref_chan_info { struct mbo_non_pref_chan_info *next; @@ -71,6 +72,21 @@ struct pending_eapol_rx { }; #define EHT_ML_MAX_STA_PROF_LEN 1024 + +struct mld_link_info { + u8 valid; + u8 local_addr[ETH_ALEN]; + u8 peer_addr[ETH_ALEN]; + size_t nstr_bitmap_len; + u8 nstr_bitmap[2]; + u16 capability; + u16 status; + size_t resp_sta_profile_len; + /* TODO Make allocation dynamic */ + u8 resp_sta_profile[EHT_ML_MAX_STA_PROF_LEN]; + const u8 *rsne, *rsnxe; +}; + struct mld_info { bool mld_sta; @@ -81,22 +97,7 @@ struct mld_info { u16 mld_capa; } common_info; - struct mld_link_info { - u8 valid; - u8 local_addr[ETH_ALEN]; - u8 peer_addr[ETH_ALEN]; - - size_t nstr_bitmap_len; - u8 nstr_bitmap[2]; - - u16 capability; - - u16 status; - size_t resp_sta_profile_len; - u8 resp_sta_profile[EHT_ML_MAX_STA_PROF_LEN]; - - const u8 *rsne, *rsnxe; - } links[MAX_NUM_MLD_LINKS]; + struct mld_link_info links[MAX_NUM_MLD_LINKS]; }; struct sta_info { --- a/src/common/ieee802_11_common.c +++ b/src/common/ieee802_11_common.c @@ -976,6 +976,35 @@ void ieee802_11_elems_clear_ext_ids(stru } +bool ieee802_11_parse_ml_probe_req(struct wpabuf *mlbuf) +{ + const struct ieee80211_eht_ml *ml; + const u8 *pos; + size_t len; + u16 ml_control; + + pos = wpabuf_head(mlbuf); + len = wpabuf_len(mlbuf); + + /* Must have control and common info length */ + if (len < sizeof(*ml) + 1 || len < sizeof(*ml) + pos[sizeof(*ml)]) + return false; + + ml = (const struct ieee80211_eht_ml *) pos; + + ml_control = le_to_host16(ml->ml_control); + if ((ml_control & MULTI_LINK_CONTROL_TYPE_MASK) != + MULTI_LINK_CONTROL_TYPE_PROBE_REQ) + return false; + + /* TODO parse the ML probe request and send response accordingly + * Currently if ML probe request is sent, we support sending all + * link's profile in the ML probe response + */ + return true; +} + + ParseRes ieee802_11_parse_link_assoc_req(const u8 *start, size_t len, struct ieee802_11_elems *elems, struct wpabuf *mlbuf, --- a/src/common/ieee802_11_common.h +++ b/src/common/ieee802_11_common.h @@ -193,6 +193,7 @@ void ieee802_11_elems_clear_ids(struct i const u8 *ids, size_t num); void ieee802_11_elems_clear_ext_ids(struct ieee802_11_elems *elems, const u8 *ids, size_t num); +bool ieee802_11_parse_ml_probe_req(struct wpabuf *mlbuf); ParseRes ieee802_11_parse_link_assoc_req(const u8 *start, size_t len, struct ieee802_11_elems *elems, struct wpabuf *mlbuf,