/* * hostapd / IEEE 802.11be EHT * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. * * This software may be distributed under the terms of the BSD license. * See README for more details. */ #include "utils/includes.h" #include "utils/common.h" #include "crypto/dh_groups.h" #include "hostapd.h" #include "sta_info.h" #include "ieee802_11.h" static u16 ieee80211_eht_ppet_size(u16 ppe_thres_hdr, const u8 *phy_cap_info) { u8 ru; u16 sz = 0; if ((phy_cap_info[EHT_PHYCAP_PPE_THRESHOLD_PRESENT_IDX] & EHT_PHYCAP_PPE_THRESHOLD_PRESENT) == 0) return 0; ru = (ppe_thres_hdr & EHT_PPE_THRES_RU_INDEX_MASK) >> EHT_PPE_THRES_RU_INDEX_SHIFT; while (ru) { if (ru & 0x1) sz++; ru >>= 1; } sz = sz * (1 + ((ppe_thres_hdr & EHT_PPE_THRES_NSS_MASK) >> EHT_PPE_THRES_NSS_SHIFT)); sz = (sz * 6) + 9; if (sz % 8) sz += 8; sz /= 8; return sz; } static u8 ieee80211_eht_mcs_set_size(enum hostapd_hw_mode mode, u8 opclass, u8 he_oper_chwidth, const u8 *he_phy_cap, const u8 *eht_phy_cap) { u8 sz = EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS; bool band24, band5, band6; u8 he_phy_cap_chwidth = ~HE_PHYCAP_CHANNEL_WIDTH_MASK; switch (he_oper_chwidth) { case CONF_OPER_CHWIDTH_80P80MHZ: he_phy_cap_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G; /* fall through */ case CONF_OPER_CHWIDTH_160MHZ: he_phy_cap_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G; /* fall through */ case CONF_OPER_CHWIDTH_80MHZ: case CONF_OPER_CHWIDTH_USE_HT: he_phy_cap_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G | HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G; break; } he_phy_cap_chwidth &= he_phy_cap[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX]; band24 = mode == HOSTAPD_MODE_IEEE80211B || mode == HOSTAPD_MODE_IEEE80211G || mode == NUM_HOSTAPD_MODES; band5 = mode == HOSTAPD_MODE_IEEE80211A || mode == NUM_HOSTAPD_MODES; band6 = is_6ghz_op_class(opclass); if (band24 && (he_phy_cap_chwidth & HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G) == 0) return EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY; if (band5 && (he_phy_cap_chwidth & (HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G | HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G | HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G)) == 0) return EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY; if (band5 && (he_phy_cap_chwidth & (HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G | HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G))) sz += EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS; if (band6 && (eht_phy_cap[EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_IDX] & EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_MASK)) sz += EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS; return sz; } size_t hostapd_eid_eht_capab_len(struct hostapd_data *hapd, enum ieee80211_op_mode opmode) { struct hostapd_hw_modes *mode; struct eht_capabilities *eht_cap; size_t len = 3 + 2 + EHT_PHY_CAPAB_LEN; mode = hapd->iface->current_mode; if (!mode) return 0; eht_cap = &mode->eht_capab[opmode]; if (!eht_cap->eht_supported) return 0; len += ieee80211_eht_mcs_set_size(mode->mode, hapd->iconf->op_class, hapd->iconf->he_oper_chwidth, mode->he_capab[opmode].phy_cap, eht_cap->phy_cap); len += ieee80211_eht_ppet_size(WPA_GET_LE16(&eht_cap->ppet[0]), eht_cap->phy_cap); return len; } u8 * hostapd_eid_eht_capab(struct hostapd_data *hapd, u8 *eid, enum ieee80211_op_mode opmode) { struct hostapd_hw_modes *mode; struct eht_capabilities *eht_cap; struct ieee80211_eht_capabilities *cap; size_t mcs_nss_len, ppe_thresh_len; u8 *pos = eid, *length_pos; mode = hapd->iface->current_mode; if (!mode) return eid; eht_cap = &mode->eht_capab[opmode]; if (!eht_cap->eht_supported) return eid; *pos++ = WLAN_EID_EXTENSION; length_pos = pos++; *pos++ = WLAN_EID_EXT_EHT_CAPABILITIES; cap = (struct ieee80211_eht_capabilities *) pos; os_memset(cap, 0, sizeof(*cap)); cap->mac_cap = host_to_le16(eht_cap->mac_cap); os_memcpy(cap->phy_cap, eht_cap->phy_cap, EHT_PHY_CAPAB_LEN); if (!is_6ghz_op_class(hapd->iconf->op_class)) cap->phy_cap[EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_IDX] &= ~EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_MASK; if (!hapd->iface->conf->eht_phy_capab.su_beamformer) cap->phy_cap[EHT_PHYCAP_SU_BEAMFORMER_IDX] &= ~EHT_PHYCAP_SU_BEAMFORMER; if (!hapd->iface->conf->eht_phy_capab.su_beamformee) cap->phy_cap[EHT_PHYCAP_SU_BEAMFORMEE_IDX] &= ~EHT_PHYCAP_SU_BEAMFORMEE; if (!hapd->iface->conf->eht_phy_capab.mu_beamformer) cap->phy_cap[EHT_PHYCAP_MU_BEAMFORMER_IDX] &= ~EHT_PHYCAP_MU_BEAMFORMER_MASK; pos = cap->optional; mcs_nss_len = ieee80211_eht_mcs_set_size(mode->mode, hapd->iconf->op_class, hapd->iconf->he_oper_chwidth, mode->he_capab[opmode].phy_cap, eht_cap->phy_cap); if (mcs_nss_len) { os_memcpy(pos, eht_cap->mcs, mcs_nss_len); pos += mcs_nss_len; } ppe_thresh_len = ieee80211_eht_ppet_size( WPA_GET_LE16(&eht_cap->ppet[0]), eht_cap->phy_cap); if (ppe_thresh_len) { os_memcpy(pos, eht_cap->ppet, ppe_thresh_len); pos += ppe_thresh_len; } *length_pos = pos - (eid + 2); return pos; } u8 * hostapd_eid_eht_operation(struct hostapd_data *hapd, u8 *eid) { struct hostapd_config *conf = hapd->iconf; struct ieee80211_eht_operation *oper; u8 *pos = eid, seg0 = 0, seg1 = 0; enum oper_chan_width chwidth; size_t elen = 1 + 4 + 3; if (!hapd->iface->current_mode) return eid; if (hapd->iconf->punct_bitmap) elen += EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE; *pos++ = WLAN_EID_EXTENSION; *pos++ = 1 + elen; *pos++ = WLAN_EID_EXT_EHT_OPERATION; oper = (struct ieee80211_eht_operation *) pos; oper->oper_params = EHT_OPER_INFO_PRESENT; /* TODO: Fill in appropriate EHT-MCS max Nss information */ oper->basic_eht_mcs_nss_set[0] = 0x11; oper->basic_eht_mcs_nss_set[1] = 0x00; oper->basic_eht_mcs_nss_set[2] = 0x00; oper->basic_eht_mcs_nss_set[3] = 0x00; if (is_6ghz_op_class(conf->op_class)) chwidth = op_class_to_ch_width(conf->op_class); else chwidth = conf->eht_oper_chwidth; seg0 = hostapd_get_oper_centr_freq_seg0_idx(conf); switch (chwidth) { case CONF_OPER_CHWIDTH_320MHZ: oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_320MHZ; seg1 = seg0; if (hapd->iconf->channel < seg0) seg0 -= 16; else seg0 += 16; break; case CONF_OPER_CHWIDTH_160MHZ: oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_160MHZ; seg1 = seg0; if (hapd->iconf->channel < seg0) seg0 -= 8; else seg0 += 8; break; case CONF_OPER_CHWIDTH_80MHZ: oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_80MHZ; break; case CONF_OPER_CHWIDTH_USE_HT: if (seg0) oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_40MHZ; break; default: return eid; } oper->oper_info.ccfs0 = seg0 ? seg0 : hapd->iconf->channel; oper->oper_info.ccfs1 = seg1; if (hapd->iconf->punct_bitmap) { oper->oper_params |= EHT_OPER_DISABLED_SUBCHAN_BITMAP_PRESENT; oper->oper_info.disabled_chan_bitmap = host_to_le16(hapd->iconf->punct_bitmap); } return pos + elen; } static bool check_valid_eht_mcs_nss(struct hostapd_data *hapd, const u8 *ap_mcs, const u8 *sta_mcs, u8 mcs_count, u8 map_len) { unsigned int i, j; for (i = 0; i < mcs_count; i++) { ap_mcs += i * 3; sta_mcs += i * 3; for (j = 0; j < map_len; j++) { if (((ap_mcs[j] >> 4) & 0xFF) == 0) continue; if ((sta_mcs[j] & 0xFF) == 0) continue; return true; } } wpa_printf(MSG_DEBUG, "No matching EHT MCS found between AP TX and STA RX"); return false; } static bool check_valid_eht_mcs(struct hostapd_data *hapd, const u8 *sta_eht_capab, enum ieee80211_op_mode opmode) { struct hostapd_hw_modes *mode; const struct ieee80211_eht_capabilities *capab; const u8 *ap_mcs, *sta_mcs; u8 mcs_count = 1; mode = hapd->iface->current_mode; if (!mode) return true; ap_mcs = mode->eht_capab[opmode].mcs; capab = (const struct ieee80211_eht_capabilities *) sta_eht_capab; sta_mcs = capab->optional; if (ieee80211_eht_mcs_set_size(mode->mode, hapd->iconf->op_class, hapd->iconf->he_oper_chwidth, mode->he_capab[opmode].phy_cap, mode->eht_capab[opmode].phy_cap) == EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY) return check_valid_eht_mcs_nss( hapd, ap_mcs, sta_mcs, 1, EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY); switch (hapd->iface->conf->eht_oper_chwidth) { case CONF_OPER_CHWIDTH_320MHZ: mcs_count++; /* fall through */ case CONF_OPER_CHWIDTH_80P80MHZ: case CONF_OPER_CHWIDTH_160MHZ: mcs_count++; break; default: break; } return check_valid_eht_mcs_nss(hapd, ap_mcs, sta_mcs, mcs_count, EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS); } static bool ieee80211_invalid_eht_cap_size(enum hostapd_hw_mode mode, u8 opclass, u8 he_oper_chwidth, const u8 *he_cap, const u8 *eht_cap, size_t len) { const struct ieee80211_he_capabilities *he_capab; struct ieee80211_eht_capabilities *cap; const u8 *he_phy_cap; size_t cap_len; u16 ppe_thres_hdr; he_capab = (const struct ieee80211_he_capabilities *) he_cap; he_phy_cap = he_capab->he_phy_capab_info; cap = (struct ieee80211_eht_capabilities *) eht_cap; cap_len = sizeof(*cap) - sizeof(cap->optional); if (len < cap_len) return true; cap_len += ieee80211_eht_mcs_set_size(mode, opclass, he_oper_chwidth, he_phy_cap, cap->phy_cap); if (len < cap_len) return true; ppe_thres_hdr = len > cap_len + 1 ? WPA_GET_LE16(&eht_cap[cap_len]) : 0x01ff; cap_len += ieee80211_eht_ppet_size(ppe_thres_hdr, cap->phy_cap); return len < cap_len; } u16 copy_sta_eht_capab(struct hostapd_data *hapd, struct sta_info *sta, enum ieee80211_op_mode opmode, const u8 *he_capab, size_t he_capab_len, const u8 *eht_capab, size_t eht_capab_len) { struct hostapd_hw_modes *c_mode = hapd->iface->current_mode; enum hostapd_hw_mode mode = c_mode ? c_mode->mode : NUM_HOSTAPD_MODES; if (!hapd->iconf->ieee80211be || hapd->conf->disable_11be || !he_capab || he_capab_len < IEEE80211_HE_CAPAB_MIN_LEN || !eht_capab || ieee80211_invalid_eht_cap_size(mode, hapd->iconf->op_class, hapd->iconf->he_oper_chwidth, he_capab, eht_capab, eht_capab_len) || !check_valid_eht_mcs(hapd, eht_capab, opmode)) { sta->flags &= ~WLAN_STA_EHT; os_free(sta->eht_capab); sta->eht_capab = NULL; return WLAN_STATUS_SUCCESS; } os_free(sta->eht_capab); sta->eht_capab = os_memdup(eht_capab, eht_capab_len); if (!sta->eht_capab) { sta->eht_capab_len = 0; return WLAN_STATUS_UNSPECIFIED_FAILURE; } sta->flags |= WLAN_STA_EHT; sta->eht_capab_len = eht_capab_len; return WLAN_STATUS_SUCCESS; } void hostapd_get_eht_capab(struct hostapd_data *hapd, const struct ieee80211_eht_capabilities *src, struct ieee80211_eht_capabilities *dest, size_t len) { if (!src || !dest) return; if (len > sizeof(*dest)) len = sizeof(*dest); /* TODO: mask out unsupported features */ os_memset(dest, 0, sizeof(*dest)); os_memcpy(dest, src, len); } u8 * hostapd_eid_eht_basic_ml(struct hostapd_data *hapd, u8 *eid, struct sta_info *info, bool include_mld_id) { struct wpabuf *buf; u16 control; u8 *pos = eid; const u8 *ptr; size_t len, slice_len; u8 link_id; u8 common_info_len; /* * As the Multi-Link element can exceed the size of 255 bytes need to * first build it and then handle fragmentation. */ buf = wpabuf_alloc(1024); if (!buf) return pos; /* Multi-Link Control field */ control = MULTI_LINK_CONTROL_TYPE_BASIC | BASIC_MULTI_LINK_CTRL_PRES_LINK_ID | BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT | BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA | BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA; /* * Set the basic Multi-Link common information. Hard code the common * info length to 13 based on the length of the present fields: * Length (1) + MLD address (6) + Link ID (1) + * BSS Parameters Change Count (1) + EML Capabilities (2) + * MLD Capabilities and Operations (2) */ common_info_len = 13; if (include_mld_id) { /* AP MLD ID */ control |= BASIC_MULTI_LINK_CTRL_PRES_AP_MLD_ID; common_info_len++; } wpabuf_put_le16(buf, control); wpabuf_put_u8(buf, common_info_len); /* Own MLD MAC Address */ wpabuf_put_data(buf, hapd->mld_addr, ETH_ALEN); /* Own Link ID */ wpabuf_put_u8(buf, hapd->mld_link_id); /* Currently hard code the BSS Parameters Change Count to 0x1 */ wpabuf_put_u8(buf, 0x1); wpa_printf(MSG_DEBUG, "MLD: EML Capabilities=0x%x", hapd->iface->mld_eml_capa); wpabuf_put_le16(buf, hapd->iface->mld_eml_capa); wpa_printf(MSG_DEBUG, "MLD: MLD Capabilities and Operations=0x%x", hapd->iface->mld_mld_capa); wpabuf_put_le16(buf, hapd->iface->mld_mld_capa); if (include_mld_id) { wpa_printf(MSG_DEBUG, "MLD: AP MLD ID=0x%x", hapd->conf->mld_id); wpabuf_put_u8(buf, hapd->conf->mld_id); } if (!info) goto out; /* 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 hostapd_data *link_bss; /* * control (2) + station info length (1) + MAC address (6) + * beacon interval (2) + TSF offset (8) + DTIM info (2) + BSS * parameters change counter (1) + station profile length. */ const size_t fixed_len = 22; size_t total_len = fixed_len + link->resp_sta_profile_len; /* Skip the local one */ if (link_id == hapd->mld_link_id || !link->valid) continue; link_bss = hostapd_mld_get_link_bss(hapd, link_id); if (!link_bss) { wpa_printf(MSG_ERROR, "MLD: Couldn't find link BSS - skip it"); continue; } /* Per-STA Profile subelement */ wpabuf_put_u8(buf, EHT_ML_SUB_ELEM_PER_STA_PROFILE); if (total_len <= 255) wpabuf_put_u8(buf, total_len); else wpabuf_put_u8(buf, 255); /* STA Control */ control = (link_id & 0xf) | EHT_PER_STA_CTRL_MAC_ADDR_PRESENT_MSK | EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK | EHT_PER_STA_CTRL_TSF_OFFSET_PRESENT_MSK | EHT_PER_STA_CTRL_BEACON_INTERVAL_PRESENT_MSK | EHT_PER_STA_CTRL_DTIM_INFO_PRESENT_MSK | EHT_PER_STA_CTRL_BSS_PARAM_CNT_PRESENT_MSK; wpabuf_put_le16(buf, control); /* STA Info */ /* STA Info Length */ wpabuf_put_u8(buf, fixed_len - 2); wpabuf_put_data(buf, link->local_addr, ETH_ALEN); wpabuf_put_le16(buf, link_bss->iconf->beacon_int); /* TSF Offset */ /* * TODO: Currently setting TSF offset to zero. However, this * information needs to come from the driver. */ wpabuf_put_le64(buf, 0); /* DTIM Info */ wpabuf_put_le16(buf, link_bss->conf->dtim_period); /* BSS Parameters Change Count */ /* TODO: Currently hard code the BSS Parameters Change Count to * 0x1 */ wpabuf_put_u8(buf, 0x1); /* Fragment the sub element if needed */ if (total_len <= 255) { wpabuf_put_data(buf, link->resp_sta_profile, link->resp_sta_profile_len); } else { ptr = link->resp_sta_profile; len = link->resp_sta_profile_len; slice_len = 255 - fixed_len; wpabuf_put_data(buf, ptr, slice_len); len -= slice_len; ptr += slice_len; while (len) { if (len <= 255) slice_len = len; else slice_len = 255; wpabuf_put_u8(buf, EHT_ML_SUB_ELEM_FRAGMENT); wpabuf_put_u8(buf, slice_len); wpabuf_put_data(buf, ptr, slice_len); len -= slice_len; ptr += slice_len; } } } out: /* Fragment the Multi-Link element, if needed */ len = wpabuf_len(buf); ptr = wpabuf_head(buf); if (len <= 254) slice_len = len; else slice_len = 254; *pos++ = WLAN_EID_EXTENSION; *pos++ = slice_len + 1; *pos++ = WLAN_EID_EXT_MULTI_LINK; os_memcpy(pos, ptr, slice_len); ptr += slice_len; pos += slice_len; len -= slice_len; while (len) { if (len <= 255) slice_len = len; else slice_len = 255; *pos++ = WLAN_EID_FRAGMENT; *pos++ = slice_len; os_memcpy(pos, ptr, slice_len); ptr += slice_len; pos += slice_len; len -= slice_len; } wpabuf_free(buf); return pos; } struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd) { struct wpabuf *buf = wpabuf_alloc(12); if (!buf) return NULL; wpabuf_put_u8(buf, WLAN_EID_EXTENSION); wpabuf_put_u8(buf, 10); wpabuf_put_u8(buf, WLAN_EID_EXT_MULTI_LINK); wpabuf_put_le16(buf, MULTI_LINK_CONTROL_TYPE_BASIC); wpabuf_put_u8(buf, ETH_ALEN + 1); wpabuf_put_data(buf, hapd->mld_addr, ETH_ALEN); return buf; } static const u8 * sae_commit_skip_fixed_fields(const struct ieee80211_mgmt *mgmt, size_t len, const u8 *pos, u16 status_code) { u16 group; size_t prime_len; struct crypto_ec *ec; if (status_code != WLAN_STATUS_SAE_HASH_TO_ELEMENT) return pos; /* SAE H2E commit message (group, scalar, FFE) */ if (len < 2) { wpa_printf(MSG_DEBUG, "EHT: SAE Group is not present"); return NULL; } group = WPA_GET_LE16(pos); pos += 2; /* TODO: How to parse when the group is unknown? */ ec = crypto_ec_init(group); if (!ec) { const struct dh_group *dh = dh_groups_get(group); if (!dh) { wpa_printf(MSG_DEBUG, "EHT: Unknown SAE group %u", group); return NULL; } prime_len = dh->prime_len; } else { prime_len = crypto_ec_prime_len(ec); } wpa_printf(MSG_DEBUG, "EHT: SAE scalar length is %zu", prime_len); /* scalar */ pos += prime_len; if (ec) { pos += prime_len * 2; crypto_ec_deinit(ec); } else { pos += prime_len; } if (pos - mgmt->u.auth.variable > (int) len) { wpa_printf(MSG_DEBUG, "EHT: Too short SAE commit Authentication frame"); return NULL; } wpa_hexdump(MSG_DEBUG, "EHT: SAE: Authentication frame elements", pos, (int) len - (pos - mgmt->u.auth.variable)); return pos; } static const u8 * sae_confirm_skip_fixed_fields(struct hostapd_data *hapd, const struct ieee80211_mgmt *mgmt, size_t len, const u8 *pos, u16 status_code) { struct sta_info *sta; if (status_code == WLAN_STATUS_REJECTED_WITH_SUGGESTED_BSS_TRANSITION) return pos; /* send confirm integer */ pos += 2; /* * At this stage we should already have an MLD station and actually SA * will be replaced with the MLD MAC address by the driver. */ sta = ap_get_sta(hapd, mgmt->sa); if (!sta) { wpa_printf(MSG_DEBUG, "SAE: No MLD STA for SAE confirm"); return NULL; } if (!sta->sae || sta->sae->state < SAE_COMMITTED || !sta->sae->tmp) { if (sta->sae) wpa_printf(MSG_DEBUG, "SAE: Invalid state=%u", sta->sae->state); else wpa_printf(MSG_DEBUG, "SAE: No SAE context"); return NULL; } wpa_printf(MSG_DEBUG, "SAE: confirm: kck_len=%zu", sta->sae->tmp->kck_len); pos += sta->sae->tmp->kck_len; if (pos - mgmt->u.auth.variable > (int) len) { wpa_printf(MSG_DEBUG, "EHT: Too short SAE confirm Authentication frame"); return NULL; } return pos; } static const u8 * auth_skip_fixed_fields(struct hostapd_data *hapd, const struct ieee80211_mgmt *mgmt, size_t len) { u16 auth_alg = le_to_host16(mgmt->u.auth.auth_alg); u16 auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction); u16 status_code = le_to_host16(mgmt->u.auth.status_code); const u8 *pos = mgmt->u.auth.variable; /* Skip fixed fields as based on IEE P802.11-REVme/D3.0, Table 9-69 * (Presence of fields and elements in Authentications frames) */ switch (auth_alg) { case WLAN_AUTH_OPEN: return pos; case WLAN_AUTH_SAE: if (auth_transaction == 1) { if (status_code == WLAN_STATUS_SUCCESS) { wpa_printf(MSG_DEBUG, "EHT: SAE H2E is mandatory for MLD"); goto out; } return sae_commit_skip_fixed_fields(mgmt, len, pos, status_code); } else if (auth_transaction == 2) { return sae_confirm_skip_fixed_fields(hapd, mgmt, len, pos, status_code); } return pos; /* TODO: Support additional algorithms that can be used for MLO */ case WLAN_AUTH_FT: case WLAN_AUTH_FILS_SK: case WLAN_AUTH_FILS_SK_PFS: case WLAN_AUTH_FILS_PK: case WLAN_AUTH_PASN: default: break; } out: wpa_printf(MSG_DEBUG, "TODO: Authentication algorithm %u not supported with MLD", auth_alg); return NULL; } const u8 * hostapd_process_ml_auth(struct hostapd_data *hapd, const struct ieee80211_mgmt *mgmt, size_t len) { struct ieee802_11_elems elems; const u8 *pos; if (!hapd->conf->mld_ap) return NULL; len -= offsetof(struct ieee80211_mgmt, u.auth.variable); pos = auth_skip_fixed_fields(hapd, mgmt, len); if (!pos) return NULL; if (ieee802_11_parse_elems(pos, (int)len - (pos - mgmt->u.auth.variable), &elems, 0) == ParseFailed) { wpa_printf(MSG_DEBUG, "MLD: Failed parsing Authentication frame"); } if (!elems.basic_mle || !elems.basic_mle_len) return NULL; 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; }