AP: MLO: Process Multi-Link element from (Re)Association Request frame

Implement processing of the Multi-Link element in the (Re)Association
Request frame, including processing of the Per-STA Profile subelement.

After handling the basic parsing of the element and extracting the
information about the requested links, handle the link specific
processing for each link:

- Find the interface with the corresponding link ID.
- Process the station profile in the interface.
- Prepare the Per-STA Profile subelement to be included in the
  Multi-Link element in the (Re)Association Response frame.

Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Andrei Otcheretianski <andrei.otcheretianski@intel.com>
This commit is contained in:
Andrei Otcheretianski 2023-05-22 22:33:45 +03:00 committed by Jouni Malinen
parent d924be3bd0
commit 5f5db9366c
4 changed files with 591 additions and 2 deletions

View file

@ -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;

View file

@ -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);

View file

@ -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;
}

View file

@ -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;