diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 175f3ed02..335fa471d 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -83,6 +83,8 @@ static void pasn_fils_auth_resp(struct hostapd_data *hapd, static void handle_auth(struct hostapd_data *hapd, const struct ieee80211_mgmt *mgmt, size_t len, int rssi, int from_queue); +static int add_associated_sta(struct hostapd_data *hapd, + struct sta_info *sta, int reassoc); u8 * hostapd_eid_multi_ap(struct hostapd_data *hapd, u8 *eid) @@ -3788,7 +3790,8 @@ static bool check_sa_query(struct hostapd_data *hapd, struct sta_info *sta, static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta, const u8 *ies, size_t ies_len, - struct ieee802_11_elems *elems, int reassoc) + struct ieee802_11_elems *elems, int reassoc, + bool link) { int resp; const u8 *wpa_ie; @@ -3890,6 +3893,12 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta, elems->eht_capabilities_len); if (resp != WLAN_STATUS_SUCCESS) return resp; + + if (!link) { + resp = hostapd_process_ml_assoc_req(hapd, elems, sta); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + } } #endif /* CONFIG_IEEE80211BE */ @@ -4249,7 +4258,255 @@ static int check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta, return WLAN_STATUS_UNSPECIFIED_FAILURE; } - return __check_assoc_ies(hapd, sta, ies, ies_len, &elems, reassoc); + return __check_assoc_ies(hapd, sta, ies, ies_len, &elems, reassoc, + false); +} + + +#ifdef CONFIG_IEEE80211BE + +static size_t ieee80211_ml_build_assoc_resp(struct hostapd_data *hapd, + u16 status_code, + u8 *buf, size_t buflen) +{ + u8 *p = buf; + + /* Capability Info */ + WPA_PUT_LE16(p, hostapd_own_capab_info(hapd)); + p += 2; + + /* Status Code */ + WPA_PUT_LE16(p, status_code); + p += 2; + + if (status_code != WLAN_STATUS_SUCCESS) + return p - buf; + + /* AID is not included */ + p = hostapd_eid_supp_rates(hapd, p); + p = hostapd_eid_ext_supp_rates(hapd, p); + p = hostapd_eid_rm_enabled_capab(hapd, p, buf + buflen - p); + p = hostapd_eid_ht_capabilities(hapd, p); + p = hostapd_eid_ht_operation(hapd, p); + + if (hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac) { + p = hostapd_eid_vht_capabilities(hapd, p, 0); + p = hostapd_eid_vht_operation(hapd, p); + } + + if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) { + p = hostapd_eid_he_capab(hapd, p, IEEE80211_MODE_AP); + p = hostapd_eid_he_operation(hapd, p); + p = hostapd_eid_spatial_reuse(hapd, p); + p = hostapd_eid_he_mu_edca_parameter_set(hapd, p); + p = hostapd_eid_he_6ghz_band_cap(hapd, p); + if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) { + p = hostapd_eid_eht_capab(hapd, p, IEEE80211_MODE_AP); + p = hostapd_eid_eht_operation(hapd, p); + } + } + + p = hostapd_eid_ext_capab(hapd, p, false); + p = hostapd_eid_mbo(hapd, p, buf + buflen - p); + p = hostapd_eid_wmm(hapd, p); + + if (hapd->conf->assocresp_elements && + (size_t) (buf + buflen - p) >= + wpabuf_len(hapd->conf->assocresp_elements)) { + os_memcpy(p, wpabuf_head(hapd->conf->assocresp_elements), + wpabuf_len(hapd->conf->assocresp_elements)); + p += wpabuf_len(hapd->conf->assocresp_elements); + } + + return p - buf; +} + + +static void ieee80211_ml_process_link(struct hostapd_data *hapd, + struct sta_info *origin_sta, + struct mld_link_info *link, + const u8 *ies, size_t ies_len, + bool reassoc) +{ + struct ieee802_11_elems elems; + struct wpabuf *mlbuf = NULL; + struct sta_info *sta = NULL; + u16 status = WLAN_STATUS_SUCCESS; + + wpa_printf(MSG_DEBUG, "MLD: link: link_id=%u, peer=" MACSTR, + hapd->mld_link_id, MAC2STR(link->peer_addr)); + + if (ieee802_11_parse_elems(ies, ies_len, &elems, 1) == ParseFailed) { + wpa_printf(MSG_DEBUG, "MLD: link: Element parsing failed"); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto out; + } + + sta = ap_get_sta(hapd, origin_sta->addr); + if (sta) { + wpa_printf(MSG_INFO, "MLD: link: Station already exists"); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + sta = NULL; + goto out; + } + + sta = ap_sta_add(hapd, origin_sta->addr); + if (!sta) { + wpa_printf(MSG_DEBUG, "MLD: link: ap_sta_add() failed"); + status = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; + goto out; + } + + mlbuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_BASIC); + if (!mlbuf) + goto out; + + if (ieee802_11_parse_link_assoc_req(ies, ies_len, &elems, mlbuf, + hapd->mld_link_id, true) == + ParseFailed) { + wpa_printf(MSG_DEBUG, + "MLD: link: Failed to parse association request Multi-Link element"); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto out; + } + + sta->flags |= origin_sta->flags | WLAN_STA_ASSOC_REQ_OK; + status = __check_assoc_ies(hapd, sta, NULL, 0, &elems, reassoc, true); + if (status != WLAN_STATUS_SUCCESS) { + wpa_printf(MSG_DEBUG, "MLD: link: Element check failed"); + goto out; + } + + sta->mld_info.mld_sta = true; + sta->mld_assoc_link_id = origin_sta->mld_assoc_link_id; + + os_memcpy(&sta->mld_info, &origin_sta->mld_info, sizeof(sta->mld_info)); + + /* + * Get the AID from the station on which the association was performed, + * and mark it as used. + */ + sta->aid = origin_sta->aid; + if (sta->aid == 0) { + wpa_printf(MSG_DEBUG, "MLD: link: No AID assigned"); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto out; + } + hapd->sta_aid[(sta->aid - 1) / 32] |= BIT((sta->aid - 1) % 32); + sta->listen_interval = origin_sta->listen_interval; + update_ht_state(hapd, sta); + + /* RSN Authenticator should always be the one on the original station */ + wpa_auth_sta_deinit(sta->wpa_sm); + sta->wpa_sm = NULL; + + /* + * Do not initialize the EAPOL state machine. + * TODO: Maybe it is needed? + */ + sta->eapol_sm = NULL; + + wpa_printf(MSG_DEBUG, "MLD: link=%u, association OK (aid=%u)", + hapd->mld_link_id, sta->aid); + + /* + * Get RSNE and RSNXE for the current BSS as they are required by the + * Authenticator. + */ + link->rsne = hostapd_wpa_ie(hapd, WLAN_EID_RSN); + link->rsnxe = hostapd_wpa_ie(hapd, WLAN_EID_RSNX); + + sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC_REQ_OK; + + /* TODO: What other processing is required? */ + + if (add_associated_sta(hapd, sta, reassoc)) + status = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; +out: + wpabuf_free(mlbuf); + link->status = status; + + wpa_printf(MSG_DEBUG, "MLD: link: status=%u", status); + if (sta && status != WLAN_STATUS_SUCCESS) + ap_free_sta(hapd, sta); + + link->resp_sta_profile_len = + ieee80211_ml_build_assoc_resp(hapd, link->status, + link->resp_sta_profile, + sizeof(link->resp_sta_profile)); +} + + +static bool hostapd_is_mld_ap(struct hostapd_data *hapd) +{ + if (!hapd->conf->mld_ap) + return false; + + if (!hapd->iface || !hapd->iface->interfaces || + hapd->iface->interfaces->count <= 1) + return false; + + return true; +} + +#endif /* CONFIG_IEEE80211BE */ + + +static void hostapd_process_assoc_ml_info(struct hostapd_data *hapd, + struct sta_info *sta, + const u8 *ies, size_t ies_len, + bool reassoc) +{ +#ifdef CONFIG_IEEE80211BE + unsigned int i, j; + + if (!hostapd_is_mld_ap(hapd)) + return; + + /* + * This is not really needed, but make the interaction with the RSN + * Authenticator more consistent + */ + sta->mld_info.links[hapd->mld_link_id].rsne = + hostapd_wpa_ie(hapd, WLAN_EID_RSN); + sta->mld_info.links[hapd->mld_link_id].rsnxe = + hostapd_wpa_ie(hapd, WLAN_EID_RSNX); + + for (i = 0; i < MAX_NUM_MLD_LINKS; i++) { + struct hostapd_iface *iface = NULL; + struct mld_link_info *link = &sta->mld_info.links[i]; + + if (!link->valid) + continue; + + for (j = 0; j < hapd->iface->interfaces->count; j++) { + iface = hapd->iface->interfaces->iface[j]; + + if (hapd->iface == iface) + continue; + + if (iface->bss[0]->conf->mld_ap && + hapd->conf->mld_id == iface->bss[0]->conf->mld_id && + i == iface->bss[0]->mld_link_id) + break; + } + + if (!iface || j == hapd->iface->interfaces->count) { + wpa_printf(MSG_DEBUG, + "MLD: No link match for link_id=%u", i); + + link->status = WLAN_STATUS_UNSPECIFIED_FAILURE; + link->resp_sta_profile_len = + ieee80211_ml_build_assoc_resp( + hapd, link->status, + link->resp_sta_profile, + sizeof(link->resp_sta_profile)); + } else { + ieee80211_ml_process_link(iface->bss[0], sta, link, + ies, ies_len, reassoc); + } + } +#endif /* CONFIG_IEEE80211BE */ } @@ -5175,6 +5432,9 @@ static void handle_assoc(struct hostapd_data *hapd, * issues with processing other non-Data Class 3 frames during this * window. */ + if (resp == WLAN_STATUS_SUCCESS) + hostapd_process_assoc_ml_info(hapd, sta, pos, left, reassoc); + if (resp == WLAN_STATUS_SUCCESS && sta && add_associated_sta(hapd, sta, reassoc)) resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h index 842881358..4d207699b 100644 --- a/src/ap/ieee802_11.h +++ b/src/ap/ieee802_11.h @@ -19,6 +19,7 @@ struct ieee80211_mgmt; struct radius_sta; enum ieee80211_op_mode; enum oper_chan_width; +struct ieee802_11_elems; int ieee802_11_mgmt(struct hostapd_data *hapd, const u8 *buf, size_t len, struct hostapd_frame_info *fi); @@ -90,6 +91,9 @@ struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd); const u8 * hostapd_process_ml_auth(struct hostapd_data *hapd, const struct ieee80211_mgmt *mgmt, size_t len); +u16 hostapd_process_ml_assoc_req(struct hostapd_data *hapd, + struct ieee802_11_elems *elems, + struct sta_info *sta); int hostapd_get_aid(struct hostapd_data *hapd, struct sta_info *sta); u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta, const u8 *ht_capab); diff --git a/src/ap/ieee802_11_eht.c b/src/ap/ieee802_11_eht.c index 2c003e3bb..9a07f7501 100644 --- a/src/ap/ieee802_11_eht.c +++ b/src/ap/ieee802_11_eht.c @@ -827,3 +827,313 @@ const u8 * hostapd_process_ml_auth(struct hostapd_data *hapd, return get_basic_mle_mld_addr(elems.basic_mle, elems.basic_mle_len); } + + +static int hostapd_mld_validate_assoc_info(struct hostapd_data *hapd, + struct mld_info *info) +{ + u8 i, link_id; + + if (!info->mld_sta) { + wpa_printf(MSG_DEBUG, "MLD: Not a non-AP MLD"); + return 0; + } + + /* + * Iterate over the links negotiated in the (Re)Association Request + * frame and validate that they are indeed valid links in the local AP + * MLD. + * + * While at it, also update the local address for the links in the + * mld_info, so it could be easily available for later flows, e.g., for + * the RSN Authenticator, etc. + */ + for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + struct hostapd_data *other_hapd; + + if (!info->links[link_id].valid) + continue; + + for (i = 0; i < hapd->iface->interfaces->count; i++) { + other_hapd = hapd->iface->interfaces->iface[i]->bss[0]; + + if (hapd == other_hapd) + continue; + + if (other_hapd->conf->mld_ap && + other_hapd->conf->mld_id == hapd->conf->mld_id && + link_id == other_hapd->mld_link_id) + break; + } + + if (i == hapd->iface->interfaces->count && + link_id != hapd->mld_link_id) { + wpa_printf(MSG_DEBUG, "MLD: Invalid link ID=%u", + link_id); + return -1; + } + + if (i < hapd->iface->interfaces->count) + os_memcpy(info->links[link_id].local_addr, + other_hapd->own_addr, + ETH_ALEN); + } + + return 0; +} + + +u16 hostapd_process_ml_assoc_req(struct hostapd_data *hapd, + struct ieee802_11_elems *elems, + struct sta_info *sta) +{ + struct wpabuf *mlbuf; + const struct ieee80211_eht_ml *ml; + const struct eht_ml_basic_common_info *common_info; + size_t ml_len, common_info_len; + struct mld_link_info *link_info; + struct mld_info *info = &sta->mld_info; + const u8 *pos; + int ret = -1; + u16 ml_control; + + mlbuf = ieee802_11_defrag_mle(elems, MULTI_LINK_CONTROL_TYPE_BASIC); + if (!mlbuf) + return WLAN_STATUS_SUCCESS; + + ml = wpabuf_head(mlbuf); + ml_len = wpabuf_len(mlbuf); + + ml_control = le_to_host16(ml->ml_control); + if ((ml_control & MULTI_LINK_CONTROL_TYPE_MASK) != + MULTI_LINK_CONTROL_TYPE_BASIC) { + wpa_printf(MSG_DEBUG, "MLD: Invalid ML type=%u", + ml_control & MULTI_LINK_CONTROL_TYPE_MASK); + goto out; + } + + /* Common Info length and MLD MAC address must always be present */ + common_info_len = 1 + ETH_ALEN; + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_LINK_ID) { + wpa_printf(MSG_DEBUG, "MLD: Link ID info not expected"); + goto out; + } + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT) { + wpa_printf(MSG_DEBUG, "MLD: BSS params change not expected"); + goto out; + } + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO) { + wpa_printf(MSG_DEBUG, "MLD: Sync delay not expected"); + goto out; + } + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) { + common_info_len += 2; + } else { + wpa_printf(MSG_DEBUG, "MLD: EML capabilities not present"); + } + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA) { + common_info_len += 2; + + } else { + wpa_printf(MSG_DEBUG, "MLD: MLD capabilities not present"); + goto out; + } + + wpa_printf(MSG_DEBUG, "MLD: expected_common_info_len=%lu", + common_info_len); + + if (sizeof(*ml) + common_info_len > ml_len) { + wpa_printf(MSG_DEBUG, "MLD: Not enough bytes for common info"); + goto out; + } + + common_info = (const struct eht_ml_basic_common_info *) ml->variable; + + /* Common information length includes the length octet */ + if (common_info->len != common_info_len) { + wpa_printf(MSG_DEBUG, + "MLD: Invalid common info len=%u (expected %zu)", + common_info->len, common_info_len); + goto out; + } + + pos = common_info->variable; + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) { + info->common_info.eml_capa = WPA_GET_LE16(pos); + pos += 2; + } else { + info->common_info.eml_capa = 0; + } + + info->common_info.mld_capa = WPA_GET_LE16(pos); + pos += 2; + + wpa_printf(MSG_DEBUG, "MLD: addr=" MACSTR ", eml=0x%x, mld=0x%x", + MAC2STR(info->common_info.mld_addr), + info->common_info.eml_capa, info->common_info.mld_capa); + + /* Check the MLD MAC Address */ + if (os_memcmp(info->common_info.mld_addr, common_info->mld_addr, + ETH_ALEN) != 0) { + wpa_printf(MSG_DEBUG, + "MLD: MLD address mismatch between authentication (" + MACSTR ") and association (" MACSTR ")", + MAC2STR(info->common_info.mld_addr), + MAC2STR(common_info->mld_addr)); + goto out; + } + + info->links[hapd->mld_link_id].valid = true; + + /* Parse the link info field */ + ml_len -= sizeof(*ml) + common_info_len; + + while (ml_len > 2) { + size_t sub_elem_len = *(pos + 1); + size_t sta_info_len; + u16 control; + + wpa_printf(MSG_DEBUG, "MLD: sub element len=%zu", + sub_elem_len); + + if (2 + sub_elem_len > ml_len) { + wpa_printf(MSG_DEBUG, + "MLD: Invalid link info len: %zu %zu", + 2 + sub_elem_len, ml_len); + goto out; + } + + if (*pos == MULTI_LINK_SUB_ELEM_ID_VENDOR) { + wpa_printf(MSG_DEBUG, + "MLD: Skip vendor specific subelement"); + + pos += 2 + sub_elem_len; + ml_len -= 2 + sub_elem_len; + continue; + } + + if (*pos != MULTI_LINK_SUB_ELEM_ID_PER_STA_PROFILE) { + wpa_printf(MSG_DEBUG, + "MLD: Unexpected Multi-Link element subelement ID=%u", + *pos); + goto out; + } + + /* Skip the subelement ID and the length */ + pos += 2; + ml_len -= 2; + + /* Get the station control field */ + if (sub_elem_len < 2) { + wpa_printf(MSG_DEBUG, + "MLD: Too short Per-STA Profile subelement"); + goto out; + } + control = WPA_GET_LE16(pos); + link_info = &info->links[control & + EHT_PER_STA_CTRL_LINK_ID_MSK]; + pos += 2; + ml_len -= 2; + sub_elem_len -= 2; + + if (!(control & EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK)) { + wpa_printf(MSG_DEBUG, + "MLD: Per-STA complete profile expected"); + goto out; + } + + if (!(control & EHT_PER_STA_CTRL_MAC_ADDR_PRESENT_MSK)) { + wpa_printf(MSG_DEBUG, + "MLD: Per-STA MAC address not present"); + goto out; + } + + if ((control & (EHT_PER_STA_CTRL_BEACON_INTERVAL_PRESENT_MSK | + EHT_PER_STA_CTRL_DTIM_INFO_PRESENT_MSK))) { + wpa_printf(MSG_DEBUG, + "MLD: Beacon/DTIM interval not expected"); + goto out; + } + + /* The length octet and the MAC address must be present */ + sta_info_len = 1 + ETH_ALEN; + + if (control & EHT_PER_STA_CTRL_NSTR_LINK_PAIR_PRESENT_MSK) { + if (control & EHT_PER_STA_CTRL_NSTR_BM_SIZE_MSK) + link_info->nstr_bitmap_len = 2; + else + link_info->nstr_bitmap_len = 1; + } + + sta_info_len += link_info->nstr_bitmap_len; + + if (sta_info_len > ml_len || sta_info_len != *pos || + sta_info_len > sub_elem_len) { + wpa_printf(MSG_DEBUG, "MLD: Invalid STA Info length"); + goto out; + } + + /* skip the length */ + pos++; + ml_len--; + + /* get the link address */ + os_memcpy(link_info->peer_addr, pos, ETH_ALEN); + wpa_printf(MSG_DEBUG, + "MLD: assoc: link id=%u, addr=" MACSTR, + control & EHT_PER_STA_CTRL_LINK_ID_MSK, + MAC2STR(link_info->peer_addr)); + + pos += ETH_ALEN; + ml_len -= ETH_ALEN; + + /* Get the NSTR bitmap */ + if (link_info->nstr_bitmap_len) { + os_memcpy(link_info->nstr_bitmap, pos, + link_info->nstr_bitmap_len); + pos += link_info->nstr_bitmap_len; + ml_len -= link_info->nstr_bitmap_len; + } + + sub_elem_len -= sta_info_len; + + wpa_printf(MSG_DEBUG, "MLD: STA Profile len=%zu", sub_elem_len); + if (sub_elem_len > ml_len) + goto out; + + if (sub_elem_len > 2) + link_info->capability = WPA_GET_LE16(pos); + + pos += sub_elem_len; + ml_len -= sub_elem_len; + + wpa_printf(MSG_DEBUG, "MLD: link ctrl=0x%x, " MACSTR + ", nstr bitmap len=%lu", + control, MAC2STR(link_info->peer_addr), + link_info->nstr_bitmap_len); + + link_info->valid = true; + } + + if (ml_len) { + wpa_printf(MSG_DEBUG, "MLD: %zu bytes left after parsing. fail", + ml_len); + goto out; + } + + ret = hostapd_mld_validate_assoc_info(hapd, info); +out: + wpabuf_free(mlbuf); + if (ret) { + os_memset(info, 0, sizeof(*info)); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + return WLAN_STATUS_SUCCESS; +} diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index 041c95961..01d73cf14 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -301,7 +301,15 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) sae_clear_retransmit_timer(hapd, sta); ieee802_1x_free_station(hapd, sta); + +#ifdef CONFIG_IEEE80211BE + if (!hapd->conf->mld_ap || !sta->mld_info.mld_sta || + hapd->mld_link_id == sta->mld_assoc_link_id) + wpa_auth_sta_deinit(sta->wpa_sm); +#else wpa_auth_sta_deinit(sta->wpa_sm); +#endif /* CONFIG_IEEE80211BE */ + rsn_preauth_free_station(hapd, sta); #ifndef CONFIG_NO_RADIUS if (hapd->radius) @@ -866,7 +874,14 @@ void ap_sta_disassociate(struct hostapd_data *hapd, struct sta_info *sta, ap_handle_timer, hapd, sta); accounting_sta_stop(hapd, sta); ieee802_1x_free_station(hapd, sta); +#ifdef CONFIG_IEEE80211BE + if (!hapd->conf->mld_ap || + hapd->mld_link_id == sta->mld_assoc_link_id) + wpa_auth_sta_deinit(sta->wpa_sm); +#else wpa_auth_sta_deinit(sta->wpa_sm); +#endif /* CONFIG_IEEE80211BE */ + sta->wpa_sm = NULL; sta->disassoc_reason = reason;