1*a90b9d01SCy Schubert /* 2*a90b9d01SCy Schubert * hostapd / IEEE 802.11be EHT 3*a90b9d01SCy Schubert * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. 4*a90b9d01SCy Schubert * 5*a90b9d01SCy Schubert * This software may be distributed under the terms of the BSD license. 6*a90b9d01SCy Schubert * See README for more details. 7*a90b9d01SCy Schubert */ 8*a90b9d01SCy Schubert 9*a90b9d01SCy Schubert #include "utils/includes.h" 10*a90b9d01SCy Schubert #include "utils/common.h" 11*a90b9d01SCy Schubert #include "crypto/crypto.h" 12*a90b9d01SCy Schubert #include "crypto/dh_groups.h" 13*a90b9d01SCy Schubert #include "hostapd.h" 14*a90b9d01SCy Schubert #include "sta_info.h" 15*a90b9d01SCy Schubert #include "ieee802_11.h" 16*a90b9d01SCy Schubert 17*a90b9d01SCy Schubert 18*a90b9d01SCy Schubert static u16 ieee80211_eht_ppet_size(u16 ppe_thres_hdr, const u8 *phy_cap_info) 19*a90b9d01SCy Schubert { 20*a90b9d01SCy Schubert u8 ru; 21*a90b9d01SCy Schubert u16 sz = 0; 22*a90b9d01SCy Schubert 23*a90b9d01SCy Schubert if ((phy_cap_info[EHT_PHYCAP_PPE_THRESHOLD_PRESENT_IDX] & 24*a90b9d01SCy Schubert EHT_PHYCAP_PPE_THRESHOLD_PRESENT) == 0) 25*a90b9d01SCy Schubert return 0; 26*a90b9d01SCy Schubert 27*a90b9d01SCy Schubert ru = (ppe_thres_hdr & 28*a90b9d01SCy Schubert EHT_PPE_THRES_RU_INDEX_MASK) >> EHT_PPE_THRES_RU_INDEX_SHIFT; 29*a90b9d01SCy Schubert while (ru) { 30*a90b9d01SCy Schubert if (ru & 0x1) 31*a90b9d01SCy Schubert sz++; 32*a90b9d01SCy Schubert ru >>= 1; 33*a90b9d01SCy Schubert } 34*a90b9d01SCy Schubert 35*a90b9d01SCy Schubert sz = sz * (1 + ((ppe_thres_hdr & EHT_PPE_THRES_NSS_MASK) >> 36*a90b9d01SCy Schubert EHT_PPE_THRES_NSS_SHIFT)); 37*a90b9d01SCy Schubert sz = (sz * 6) + 9; 38*a90b9d01SCy Schubert if (sz % 8) 39*a90b9d01SCy Schubert sz += 8; 40*a90b9d01SCy Schubert sz /= 8; 41*a90b9d01SCy Schubert 42*a90b9d01SCy Schubert return sz; 43*a90b9d01SCy Schubert } 44*a90b9d01SCy Schubert 45*a90b9d01SCy Schubert 46*a90b9d01SCy Schubert static u8 ieee80211_eht_mcs_set_size(enum hostapd_hw_mode mode, u8 opclass, 47*a90b9d01SCy Schubert int he_oper_chwidth, const u8 *he_phy_cap, 48*a90b9d01SCy Schubert const u8 *eht_phy_cap) 49*a90b9d01SCy Schubert { 50*a90b9d01SCy Schubert u8 sz = EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS; 51*a90b9d01SCy Schubert bool band24, band5, band6; 52*a90b9d01SCy Schubert u8 he_phy_cap_chwidth = ~HE_PHYCAP_CHANNEL_WIDTH_MASK; 53*a90b9d01SCy Schubert u8 cap_chwidth; 54*a90b9d01SCy Schubert 55*a90b9d01SCy Schubert switch (he_oper_chwidth) { 56*a90b9d01SCy Schubert case CONF_OPER_CHWIDTH_80P80MHZ: 57*a90b9d01SCy Schubert he_phy_cap_chwidth |= 58*a90b9d01SCy Schubert HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G; 59*a90b9d01SCy Schubert /* fall through */ 60*a90b9d01SCy Schubert case CONF_OPER_CHWIDTH_160MHZ: 61*a90b9d01SCy Schubert he_phy_cap_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G; 62*a90b9d01SCy Schubert /* fall through */ 63*a90b9d01SCy Schubert case CONF_OPER_CHWIDTH_80MHZ: 64*a90b9d01SCy Schubert case CONF_OPER_CHWIDTH_USE_HT: 65*a90b9d01SCy Schubert he_phy_cap_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G | 66*a90b9d01SCy Schubert HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G; 67*a90b9d01SCy Schubert break; 68*a90b9d01SCy Schubert } 69*a90b9d01SCy Schubert 70*a90b9d01SCy Schubert cap_chwidth = he_phy_cap[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX]; 71*a90b9d01SCy Schubert if (he_oper_chwidth != -1) 72*a90b9d01SCy Schubert he_phy_cap_chwidth &= cap_chwidth; 73*a90b9d01SCy Schubert else 74*a90b9d01SCy Schubert he_phy_cap_chwidth = cap_chwidth; 75*a90b9d01SCy Schubert 76*a90b9d01SCy Schubert band24 = mode == HOSTAPD_MODE_IEEE80211B || 77*a90b9d01SCy Schubert mode == HOSTAPD_MODE_IEEE80211G || 78*a90b9d01SCy Schubert mode == NUM_HOSTAPD_MODES; 79*a90b9d01SCy Schubert band5 = mode == HOSTAPD_MODE_IEEE80211A || 80*a90b9d01SCy Schubert mode == NUM_HOSTAPD_MODES; 81*a90b9d01SCy Schubert band6 = is_6ghz_op_class(opclass); 82*a90b9d01SCy Schubert 83*a90b9d01SCy Schubert if (band24 && 84*a90b9d01SCy Schubert (he_phy_cap_chwidth & HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G) == 0) 85*a90b9d01SCy Schubert return EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY; 86*a90b9d01SCy Schubert 87*a90b9d01SCy Schubert if (band5 && 88*a90b9d01SCy Schubert (he_phy_cap_chwidth & 89*a90b9d01SCy Schubert (HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G | 90*a90b9d01SCy Schubert HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G | 91*a90b9d01SCy Schubert HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G)) == 0) 92*a90b9d01SCy Schubert return EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY; 93*a90b9d01SCy Schubert 94*a90b9d01SCy Schubert if (band5 && 95*a90b9d01SCy Schubert (he_phy_cap_chwidth & 96*a90b9d01SCy Schubert (HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G | 97*a90b9d01SCy Schubert HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G))) 98*a90b9d01SCy Schubert sz += EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS; 99*a90b9d01SCy Schubert 100*a90b9d01SCy Schubert if (band6 && 101*a90b9d01SCy Schubert (eht_phy_cap[EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_IDX] & 102*a90b9d01SCy Schubert EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_MASK)) 103*a90b9d01SCy Schubert sz += EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS; 104*a90b9d01SCy Schubert 105*a90b9d01SCy Schubert return sz; 106*a90b9d01SCy Schubert } 107*a90b9d01SCy Schubert 108*a90b9d01SCy Schubert 109*a90b9d01SCy Schubert size_t hostapd_eid_eht_capab_len(struct hostapd_data *hapd, 110*a90b9d01SCy Schubert enum ieee80211_op_mode opmode) 111*a90b9d01SCy Schubert { 112*a90b9d01SCy Schubert struct hostapd_hw_modes *mode; 113*a90b9d01SCy Schubert struct eht_capabilities *eht_cap; 114*a90b9d01SCy Schubert size_t len = 3 + 2 + EHT_PHY_CAPAB_LEN; 115*a90b9d01SCy Schubert 116*a90b9d01SCy Schubert mode = hapd->iface->current_mode; 117*a90b9d01SCy Schubert if (!mode) 118*a90b9d01SCy Schubert return 0; 119*a90b9d01SCy Schubert 120*a90b9d01SCy Schubert eht_cap = &mode->eht_capab[opmode]; 121*a90b9d01SCy Schubert if (!eht_cap->eht_supported) 122*a90b9d01SCy Schubert return 0; 123*a90b9d01SCy Schubert 124*a90b9d01SCy Schubert len += ieee80211_eht_mcs_set_size(mode->mode, hapd->iconf->op_class, 125*a90b9d01SCy Schubert hapd->iconf->he_oper_chwidth, 126*a90b9d01SCy Schubert mode->he_capab[opmode].phy_cap, 127*a90b9d01SCy Schubert eht_cap->phy_cap); 128*a90b9d01SCy Schubert len += ieee80211_eht_ppet_size(WPA_GET_LE16(&eht_cap->ppet[0]), 129*a90b9d01SCy Schubert eht_cap->phy_cap); 130*a90b9d01SCy Schubert 131*a90b9d01SCy Schubert return len; 132*a90b9d01SCy Schubert } 133*a90b9d01SCy Schubert 134*a90b9d01SCy Schubert 135*a90b9d01SCy Schubert u8 * hostapd_eid_eht_capab(struct hostapd_data *hapd, u8 *eid, 136*a90b9d01SCy Schubert enum ieee80211_op_mode opmode) 137*a90b9d01SCy Schubert { 138*a90b9d01SCy Schubert struct hostapd_hw_modes *mode; 139*a90b9d01SCy Schubert struct eht_capabilities *eht_cap; 140*a90b9d01SCy Schubert struct ieee80211_eht_capabilities *cap; 141*a90b9d01SCy Schubert size_t mcs_nss_len, ppe_thresh_len; 142*a90b9d01SCy Schubert u8 *pos = eid, *length_pos; 143*a90b9d01SCy Schubert 144*a90b9d01SCy Schubert mode = hapd->iface->current_mode; 145*a90b9d01SCy Schubert if (!mode) 146*a90b9d01SCy Schubert return eid; 147*a90b9d01SCy Schubert 148*a90b9d01SCy Schubert eht_cap = &mode->eht_capab[opmode]; 149*a90b9d01SCy Schubert if (!eht_cap->eht_supported) 150*a90b9d01SCy Schubert return eid; 151*a90b9d01SCy Schubert 152*a90b9d01SCy Schubert *pos++ = WLAN_EID_EXTENSION; 153*a90b9d01SCy Schubert length_pos = pos++; 154*a90b9d01SCy Schubert *pos++ = WLAN_EID_EXT_EHT_CAPABILITIES; 155*a90b9d01SCy Schubert 156*a90b9d01SCy Schubert cap = (struct ieee80211_eht_capabilities *) pos; 157*a90b9d01SCy Schubert os_memset(cap, 0, sizeof(*cap)); 158*a90b9d01SCy Schubert cap->mac_cap = host_to_le16(eht_cap->mac_cap); 159*a90b9d01SCy Schubert os_memcpy(cap->phy_cap, eht_cap->phy_cap, EHT_PHY_CAPAB_LEN); 160*a90b9d01SCy Schubert 161*a90b9d01SCy Schubert if (!is_6ghz_op_class(hapd->iconf->op_class)) 162*a90b9d01SCy Schubert cap->phy_cap[EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_IDX] &= 163*a90b9d01SCy Schubert ~EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_MASK; 164*a90b9d01SCy Schubert if (!hapd->iface->conf->eht_phy_capab.su_beamformer) 165*a90b9d01SCy Schubert cap->phy_cap[EHT_PHYCAP_SU_BEAMFORMER_IDX] &= 166*a90b9d01SCy Schubert ~EHT_PHYCAP_SU_BEAMFORMER; 167*a90b9d01SCy Schubert 168*a90b9d01SCy Schubert if (!hapd->iface->conf->eht_phy_capab.su_beamformee) 169*a90b9d01SCy Schubert cap->phy_cap[EHT_PHYCAP_SU_BEAMFORMEE_IDX] &= 170*a90b9d01SCy Schubert ~EHT_PHYCAP_SU_BEAMFORMEE; 171*a90b9d01SCy Schubert 172*a90b9d01SCy Schubert if (!hapd->iface->conf->eht_phy_capab.mu_beamformer) 173*a90b9d01SCy Schubert cap->phy_cap[EHT_PHYCAP_MU_BEAMFORMER_IDX] &= 174*a90b9d01SCy Schubert ~EHT_PHYCAP_MU_BEAMFORMER_MASK; 175*a90b9d01SCy Schubert 176*a90b9d01SCy Schubert pos = cap->optional; 177*a90b9d01SCy Schubert 178*a90b9d01SCy Schubert mcs_nss_len = ieee80211_eht_mcs_set_size(mode->mode, 179*a90b9d01SCy Schubert hapd->iconf->op_class, 180*a90b9d01SCy Schubert hapd->iconf->he_oper_chwidth, 181*a90b9d01SCy Schubert mode->he_capab[opmode].phy_cap, 182*a90b9d01SCy Schubert eht_cap->phy_cap); 183*a90b9d01SCy Schubert if (mcs_nss_len) { 184*a90b9d01SCy Schubert os_memcpy(pos, eht_cap->mcs, mcs_nss_len); 185*a90b9d01SCy Schubert pos += mcs_nss_len; 186*a90b9d01SCy Schubert } 187*a90b9d01SCy Schubert 188*a90b9d01SCy Schubert ppe_thresh_len = ieee80211_eht_ppet_size( 189*a90b9d01SCy Schubert WPA_GET_LE16(&eht_cap->ppet[0]), 190*a90b9d01SCy Schubert eht_cap->phy_cap); 191*a90b9d01SCy Schubert if (ppe_thresh_len) { 192*a90b9d01SCy Schubert os_memcpy(pos, eht_cap->ppet, ppe_thresh_len); 193*a90b9d01SCy Schubert pos += ppe_thresh_len; 194*a90b9d01SCy Schubert } 195*a90b9d01SCy Schubert 196*a90b9d01SCy Schubert *length_pos = pos - (eid + 2); 197*a90b9d01SCy Schubert return pos; 198*a90b9d01SCy Schubert } 199*a90b9d01SCy Schubert 200*a90b9d01SCy Schubert 201*a90b9d01SCy Schubert u8 * hostapd_eid_eht_operation(struct hostapd_data *hapd, u8 *eid) 202*a90b9d01SCy Schubert { 203*a90b9d01SCy Schubert struct hostapd_config *conf = hapd->iconf; 204*a90b9d01SCy Schubert struct ieee80211_eht_operation *oper; 205*a90b9d01SCy Schubert u8 *pos = eid, seg0 = 0, seg1 = 0; 206*a90b9d01SCy Schubert enum oper_chan_width chwidth; 207*a90b9d01SCy Schubert size_t elen = 1 + 4; 208*a90b9d01SCy Schubert bool eht_oper_info_present; 209*a90b9d01SCy Schubert u16 punct_bitmap = hostapd_get_punct_bitmap(hapd); 210*a90b9d01SCy Schubert 211*a90b9d01SCy Schubert if (!hapd->iface->current_mode) 212*a90b9d01SCy Schubert return eid; 213*a90b9d01SCy Schubert 214*a90b9d01SCy Schubert if (is_6ghz_op_class(conf->op_class)) 215*a90b9d01SCy Schubert chwidth = op_class_to_ch_width(conf->op_class); 216*a90b9d01SCy Schubert else 217*a90b9d01SCy Schubert chwidth = conf->eht_oper_chwidth; 218*a90b9d01SCy Schubert 219*a90b9d01SCy Schubert eht_oper_info_present = chwidth == CONF_OPER_CHWIDTH_320MHZ || 220*a90b9d01SCy Schubert punct_bitmap; 221*a90b9d01SCy Schubert 222*a90b9d01SCy Schubert if (eht_oper_info_present) 223*a90b9d01SCy Schubert elen += 3; 224*a90b9d01SCy Schubert 225*a90b9d01SCy Schubert if (punct_bitmap) 226*a90b9d01SCy Schubert elen += EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE; 227*a90b9d01SCy Schubert 228*a90b9d01SCy Schubert *pos++ = WLAN_EID_EXTENSION; 229*a90b9d01SCy Schubert *pos++ = 1 + elen; 230*a90b9d01SCy Schubert *pos++ = WLAN_EID_EXT_EHT_OPERATION; 231*a90b9d01SCy Schubert 232*a90b9d01SCy Schubert oper = (struct ieee80211_eht_operation *) pos; 233*a90b9d01SCy Schubert oper->oper_params = 0; 234*a90b9d01SCy Schubert 235*a90b9d01SCy Schubert if (hapd->iconf->eht_default_pe_duration) 236*a90b9d01SCy Schubert oper->oper_params |= EHT_OPER_DEFAULT_PE_DURATION; 237*a90b9d01SCy Schubert 238*a90b9d01SCy Schubert /* TODO: Fill in appropriate EHT-MCS max Nss information */ 239*a90b9d01SCy Schubert oper->basic_eht_mcs_nss_set[0] = 0x11; 240*a90b9d01SCy Schubert oper->basic_eht_mcs_nss_set[1] = 0x00; 241*a90b9d01SCy Schubert oper->basic_eht_mcs_nss_set[2] = 0x00; 242*a90b9d01SCy Schubert oper->basic_eht_mcs_nss_set[3] = 0x00; 243*a90b9d01SCy Schubert 244*a90b9d01SCy Schubert if (!eht_oper_info_present) 245*a90b9d01SCy Schubert return pos + elen; 246*a90b9d01SCy Schubert 247*a90b9d01SCy Schubert oper->oper_params |= EHT_OPER_INFO_PRESENT; 248*a90b9d01SCy Schubert seg0 = hostapd_get_oper_centr_freq_seg0_idx(conf); 249*a90b9d01SCy Schubert 250*a90b9d01SCy Schubert switch (chwidth) { 251*a90b9d01SCy Schubert case CONF_OPER_CHWIDTH_320MHZ: 252*a90b9d01SCy Schubert oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_320MHZ; 253*a90b9d01SCy Schubert seg1 = seg0; 254*a90b9d01SCy Schubert if (hapd->iconf->channel < seg0) 255*a90b9d01SCy Schubert seg0 -= 16; 256*a90b9d01SCy Schubert else 257*a90b9d01SCy Schubert seg0 += 16; 258*a90b9d01SCy Schubert break; 259*a90b9d01SCy Schubert case CONF_OPER_CHWIDTH_160MHZ: 260*a90b9d01SCy Schubert oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_160MHZ; 261*a90b9d01SCy Schubert seg1 = seg0; 262*a90b9d01SCy Schubert if (hapd->iconf->channel < seg0) 263*a90b9d01SCy Schubert seg0 -= 8; 264*a90b9d01SCy Schubert else 265*a90b9d01SCy Schubert seg0 += 8; 266*a90b9d01SCy Schubert break; 267*a90b9d01SCy Schubert case CONF_OPER_CHWIDTH_80MHZ: 268*a90b9d01SCy Schubert oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_80MHZ; 269*a90b9d01SCy Schubert break; 270*a90b9d01SCy Schubert case CONF_OPER_CHWIDTH_USE_HT: 271*a90b9d01SCy Schubert if (seg0) 272*a90b9d01SCy Schubert oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_40MHZ; 273*a90b9d01SCy Schubert break; 274*a90b9d01SCy Schubert default: 275*a90b9d01SCy Schubert return eid; 276*a90b9d01SCy Schubert } 277*a90b9d01SCy Schubert 278*a90b9d01SCy Schubert oper->oper_info.ccfs0 = seg0 ? seg0 : hapd->iconf->channel; 279*a90b9d01SCy Schubert oper->oper_info.ccfs1 = seg1; 280*a90b9d01SCy Schubert 281*a90b9d01SCy Schubert if (punct_bitmap) { 282*a90b9d01SCy Schubert oper->oper_params |= EHT_OPER_DISABLED_SUBCHAN_BITMAP_PRESENT; 283*a90b9d01SCy Schubert oper->oper_info.disabled_chan_bitmap = 284*a90b9d01SCy Schubert host_to_le16(punct_bitmap); 285*a90b9d01SCy Schubert } 286*a90b9d01SCy Schubert 287*a90b9d01SCy Schubert return pos + elen; 288*a90b9d01SCy Schubert } 289*a90b9d01SCy Schubert 290*a90b9d01SCy Schubert 291*a90b9d01SCy Schubert static bool check_valid_eht_mcs_nss(struct hostapd_data *hapd, const u8 *ap_mcs, 292*a90b9d01SCy Schubert const u8 *sta_mcs, u8 mcs_count, u8 map_len) 293*a90b9d01SCy Schubert { 294*a90b9d01SCy Schubert unsigned int i, j; 295*a90b9d01SCy Schubert 296*a90b9d01SCy Schubert for (i = 0; i < mcs_count; i++) { 297*a90b9d01SCy Schubert ap_mcs += i * 3; 298*a90b9d01SCy Schubert sta_mcs += i * 3; 299*a90b9d01SCy Schubert 300*a90b9d01SCy Schubert for (j = 0; j < map_len; j++) { 301*a90b9d01SCy Schubert if (((ap_mcs[j] >> 4) & 0xFF) == 0) 302*a90b9d01SCy Schubert continue; 303*a90b9d01SCy Schubert 304*a90b9d01SCy Schubert if ((sta_mcs[j] & 0xFF) == 0) 305*a90b9d01SCy Schubert continue; 306*a90b9d01SCy Schubert 307*a90b9d01SCy Schubert return true; 308*a90b9d01SCy Schubert } 309*a90b9d01SCy Schubert } 310*a90b9d01SCy Schubert 311*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 312*a90b9d01SCy Schubert "No matching EHT MCS found between AP TX and STA RX"); 313*a90b9d01SCy Schubert return false; 314*a90b9d01SCy Schubert } 315*a90b9d01SCy Schubert 316*a90b9d01SCy Schubert 317*a90b9d01SCy Schubert static bool check_valid_eht_mcs(struct hostapd_data *hapd, 318*a90b9d01SCy Schubert const u8 *sta_eht_capab, 319*a90b9d01SCy Schubert enum ieee80211_op_mode opmode) 320*a90b9d01SCy Schubert { 321*a90b9d01SCy Schubert struct hostapd_hw_modes *mode; 322*a90b9d01SCy Schubert const struct ieee80211_eht_capabilities *capab; 323*a90b9d01SCy Schubert const u8 *ap_mcs, *sta_mcs; 324*a90b9d01SCy Schubert u8 mcs_count = 1; 325*a90b9d01SCy Schubert 326*a90b9d01SCy Schubert mode = hapd->iface->current_mode; 327*a90b9d01SCy Schubert if (!mode) 328*a90b9d01SCy Schubert return true; 329*a90b9d01SCy Schubert 330*a90b9d01SCy Schubert ap_mcs = mode->eht_capab[opmode].mcs; 331*a90b9d01SCy Schubert capab = (const struct ieee80211_eht_capabilities *) sta_eht_capab; 332*a90b9d01SCy Schubert sta_mcs = capab->optional; 333*a90b9d01SCy Schubert 334*a90b9d01SCy Schubert if (ieee80211_eht_mcs_set_size(mode->mode, hapd->iconf->op_class, 335*a90b9d01SCy Schubert hapd->iconf->he_oper_chwidth, 336*a90b9d01SCy Schubert mode->he_capab[opmode].phy_cap, 337*a90b9d01SCy Schubert mode->eht_capab[opmode].phy_cap) == 338*a90b9d01SCy Schubert EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY) 339*a90b9d01SCy Schubert return check_valid_eht_mcs_nss( 340*a90b9d01SCy Schubert hapd, ap_mcs, sta_mcs, 1, 341*a90b9d01SCy Schubert EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY); 342*a90b9d01SCy Schubert 343*a90b9d01SCy Schubert switch (hapd->iface->conf->eht_oper_chwidth) { 344*a90b9d01SCy Schubert case CONF_OPER_CHWIDTH_320MHZ: 345*a90b9d01SCy Schubert mcs_count++; 346*a90b9d01SCy Schubert /* fall through */ 347*a90b9d01SCy Schubert case CONF_OPER_CHWIDTH_80P80MHZ: 348*a90b9d01SCy Schubert case CONF_OPER_CHWIDTH_160MHZ: 349*a90b9d01SCy Schubert mcs_count++; 350*a90b9d01SCy Schubert break; 351*a90b9d01SCy Schubert default: 352*a90b9d01SCy Schubert break; 353*a90b9d01SCy Schubert } 354*a90b9d01SCy Schubert 355*a90b9d01SCy Schubert return check_valid_eht_mcs_nss(hapd, ap_mcs, sta_mcs, mcs_count, 356*a90b9d01SCy Schubert EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS); 357*a90b9d01SCy Schubert } 358*a90b9d01SCy Schubert 359*a90b9d01SCy Schubert 360*a90b9d01SCy Schubert static bool ieee80211_invalid_eht_cap_size(enum hostapd_hw_mode mode, 361*a90b9d01SCy Schubert u8 opclass, const u8 *he_cap, 362*a90b9d01SCy Schubert const u8 *eht_cap, size_t len) 363*a90b9d01SCy Schubert { 364*a90b9d01SCy Schubert const struct ieee80211_he_capabilities *he_capab; 365*a90b9d01SCy Schubert struct ieee80211_eht_capabilities *cap; 366*a90b9d01SCy Schubert const u8 *he_phy_cap; 367*a90b9d01SCy Schubert size_t cap_len; 368*a90b9d01SCy Schubert u16 ppe_thres_hdr; 369*a90b9d01SCy Schubert 370*a90b9d01SCy Schubert he_capab = (const struct ieee80211_he_capabilities *) he_cap; 371*a90b9d01SCy Schubert he_phy_cap = he_capab->he_phy_capab_info; 372*a90b9d01SCy Schubert cap = (struct ieee80211_eht_capabilities *) eht_cap; 373*a90b9d01SCy Schubert cap_len = sizeof(*cap) - sizeof(cap->optional); 374*a90b9d01SCy Schubert if (len < cap_len) 375*a90b9d01SCy Schubert return true; 376*a90b9d01SCy Schubert 377*a90b9d01SCy Schubert cap_len += ieee80211_eht_mcs_set_size(mode, opclass, -1, he_phy_cap, 378*a90b9d01SCy Schubert cap->phy_cap); 379*a90b9d01SCy Schubert if (len < cap_len) 380*a90b9d01SCy Schubert return true; 381*a90b9d01SCy Schubert 382*a90b9d01SCy Schubert ppe_thres_hdr = len > cap_len + 1 ? 383*a90b9d01SCy Schubert WPA_GET_LE16(&eht_cap[cap_len]) : 0x01ff; 384*a90b9d01SCy Schubert cap_len += ieee80211_eht_ppet_size(ppe_thres_hdr, cap->phy_cap); 385*a90b9d01SCy Schubert 386*a90b9d01SCy Schubert return len < cap_len; 387*a90b9d01SCy Schubert } 388*a90b9d01SCy Schubert 389*a90b9d01SCy Schubert 390*a90b9d01SCy Schubert u16 copy_sta_eht_capab(struct hostapd_data *hapd, struct sta_info *sta, 391*a90b9d01SCy Schubert enum ieee80211_op_mode opmode, 392*a90b9d01SCy Schubert const u8 *he_capab, size_t he_capab_len, 393*a90b9d01SCy Schubert const u8 *eht_capab, size_t eht_capab_len) 394*a90b9d01SCy Schubert { 395*a90b9d01SCy Schubert struct hostapd_hw_modes *c_mode = hapd->iface->current_mode; 396*a90b9d01SCy Schubert enum hostapd_hw_mode mode = c_mode ? c_mode->mode : NUM_HOSTAPD_MODES; 397*a90b9d01SCy Schubert 398*a90b9d01SCy Schubert if (!hapd->iconf->ieee80211be || hapd->conf->disable_11be || 399*a90b9d01SCy Schubert !he_capab || he_capab_len < IEEE80211_HE_CAPAB_MIN_LEN || 400*a90b9d01SCy Schubert !eht_capab || 401*a90b9d01SCy Schubert ieee80211_invalid_eht_cap_size(mode, hapd->iconf->op_class, 402*a90b9d01SCy Schubert he_capab, eht_capab, 403*a90b9d01SCy Schubert eht_capab_len) || 404*a90b9d01SCy Schubert !check_valid_eht_mcs(hapd, eht_capab, opmode)) { 405*a90b9d01SCy Schubert sta->flags &= ~WLAN_STA_EHT; 406*a90b9d01SCy Schubert os_free(sta->eht_capab); 407*a90b9d01SCy Schubert sta->eht_capab = NULL; 408*a90b9d01SCy Schubert return WLAN_STATUS_SUCCESS; 409*a90b9d01SCy Schubert } 410*a90b9d01SCy Schubert 411*a90b9d01SCy Schubert os_free(sta->eht_capab); 412*a90b9d01SCy Schubert sta->eht_capab = os_memdup(eht_capab, eht_capab_len); 413*a90b9d01SCy Schubert if (!sta->eht_capab) { 414*a90b9d01SCy Schubert sta->eht_capab_len = 0; 415*a90b9d01SCy Schubert return WLAN_STATUS_UNSPECIFIED_FAILURE; 416*a90b9d01SCy Schubert } 417*a90b9d01SCy Schubert 418*a90b9d01SCy Schubert sta->flags |= WLAN_STA_EHT; 419*a90b9d01SCy Schubert sta->eht_capab_len = eht_capab_len; 420*a90b9d01SCy Schubert 421*a90b9d01SCy Schubert return WLAN_STATUS_SUCCESS; 422*a90b9d01SCy Schubert } 423*a90b9d01SCy Schubert 424*a90b9d01SCy Schubert 425*a90b9d01SCy Schubert void hostapd_get_eht_capab(struct hostapd_data *hapd, 426*a90b9d01SCy Schubert const struct ieee80211_eht_capabilities *src, 427*a90b9d01SCy Schubert struct ieee80211_eht_capabilities *dest, 428*a90b9d01SCy Schubert size_t len) 429*a90b9d01SCy Schubert { 430*a90b9d01SCy Schubert if (!src || !dest) 431*a90b9d01SCy Schubert return; 432*a90b9d01SCy Schubert 433*a90b9d01SCy Schubert if (len > sizeof(*dest)) 434*a90b9d01SCy Schubert len = sizeof(*dest); 435*a90b9d01SCy Schubert /* TODO: mask out unsupported features */ 436*a90b9d01SCy Schubert 437*a90b9d01SCy Schubert os_memset(dest, 0, sizeof(*dest)); 438*a90b9d01SCy Schubert os_memcpy(dest, src, len); 439*a90b9d01SCy Schubert } 440*a90b9d01SCy Schubert 441*a90b9d01SCy Schubert 442*a90b9d01SCy Schubert static u8 * hostapd_eid_eht_basic_ml_common(struct hostapd_data *hapd, 443*a90b9d01SCy Schubert u8 *eid, struct mld_info *mld_info, 444*a90b9d01SCy Schubert bool include_mld_id) 445*a90b9d01SCy Schubert { 446*a90b9d01SCy Schubert struct wpabuf *buf; 447*a90b9d01SCy Schubert u16 control; 448*a90b9d01SCy Schubert u8 *pos = eid; 449*a90b9d01SCy Schubert const u8 *ptr; 450*a90b9d01SCy Schubert size_t len, slice_len; 451*a90b9d01SCy Schubert u8 link_id; 452*a90b9d01SCy Schubert u8 common_info_len; 453*a90b9d01SCy Schubert u16 mld_cap; 454*a90b9d01SCy Schubert u8 max_simul_links, active_links; 455*a90b9d01SCy Schubert 456*a90b9d01SCy Schubert /* 457*a90b9d01SCy Schubert * As the Multi-Link element can exceed the size of 255 bytes need to 458*a90b9d01SCy Schubert * first build it and then handle fragmentation. 459*a90b9d01SCy Schubert */ 460*a90b9d01SCy Schubert buf = wpabuf_alloc(1024); 461*a90b9d01SCy Schubert if (!buf) 462*a90b9d01SCy Schubert return pos; 463*a90b9d01SCy Schubert 464*a90b9d01SCy Schubert /* Multi-Link Control field */ 465*a90b9d01SCy Schubert control = MULTI_LINK_CONTROL_TYPE_BASIC | 466*a90b9d01SCy Schubert BASIC_MULTI_LINK_CTRL_PRES_LINK_ID | 467*a90b9d01SCy Schubert BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT | 468*a90b9d01SCy Schubert BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA | 469*a90b9d01SCy Schubert BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA; 470*a90b9d01SCy Schubert 471*a90b9d01SCy Schubert /* 472*a90b9d01SCy Schubert * Set the basic Multi-Link common information. Hard code the common 473*a90b9d01SCy Schubert * info length to 13 based on the length of the present fields: 474*a90b9d01SCy Schubert * Length (1) + MLD address (6) + Link ID (1) + 475*a90b9d01SCy Schubert * BSS Parameters Change Count (1) + EML Capabilities (2) + 476*a90b9d01SCy Schubert * MLD Capabilities and Operations (2) 477*a90b9d01SCy Schubert */ 478*a90b9d01SCy Schubert #define EHT_ML_COMMON_INFO_LEN 13 479*a90b9d01SCy Schubert common_info_len = EHT_ML_COMMON_INFO_LEN; 480*a90b9d01SCy Schubert 481*a90b9d01SCy Schubert if (include_mld_id) { 482*a90b9d01SCy Schubert /* AP MLD ID */ 483*a90b9d01SCy Schubert control |= BASIC_MULTI_LINK_CTRL_PRES_AP_MLD_ID; 484*a90b9d01SCy Schubert common_info_len++; 485*a90b9d01SCy Schubert } 486*a90b9d01SCy Schubert 487*a90b9d01SCy Schubert wpabuf_put_le16(buf, control); 488*a90b9d01SCy Schubert 489*a90b9d01SCy Schubert wpabuf_put_u8(buf, common_info_len); 490*a90b9d01SCy Schubert 491*a90b9d01SCy Schubert /* Own MLD MAC Address */ 492*a90b9d01SCy Schubert wpabuf_put_data(buf, hapd->mld->mld_addr, ETH_ALEN); 493*a90b9d01SCy Schubert 494*a90b9d01SCy Schubert /* Own Link ID */ 495*a90b9d01SCy Schubert wpabuf_put_u8(buf, hapd->mld_link_id); 496*a90b9d01SCy Schubert 497*a90b9d01SCy Schubert /* Currently hard code the BSS Parameters Change Count to 0x1 */ 498*a90b9d01SCy Schubert wpabuf_put_u8(buf, 0x1); 499*a90b9d01SCy Schubert 500*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: EML Capabilities=0x%x", 501*a90b9d01SCy Schubert hapd->iface->mld_eml_capa); 502*a90b9d01SCy Schubert wpabuf_put_le16(buf, hapd->iface->mld_eml_capa); 503*a90b9d01SCy Schubert 504*a90b9d01SCy Schubert mld_cap = hapd->iface->mld_mld_capa; 505*a90b9d01SCy Schubert max_simul_links = mld_cap & EHT_ML_MLD_CAPA_MAX_NUM_SIM_LINKS_MASK; 506*a90b9d01SCy Schubert active_links = hapd->mld->num_links - 1; 507*a90b9d01SCy Schubert 508*a90b9d01SCy Schubert if (active_links > max_simul_links) { 509*a90b9d01SCy Schubert wpa_printf(MSG_ERROR, 510*a90b9d01SCy Schubert "MLD: Error in max simultaneous links, advertised: 0x%x current: 0x%x", 511*a90b9d01SCy Schubert max_simul_links, active_links); 512*a90b9d01SCy Schubert active_links = max_simul_links; 513*a90b9d01SCy Schubert } 514*a90b9d01SCy Schubert 515*a90b9d01SCy Schubert mld_cap &= ~EHT_ML_MLD_CAPA_MAX_NUM_SIM_LINKS_MASK; 516*a90b9d01SCy Schubert mld_cap |= active_links & EHT_ML_MLD_CAPA_MAX_NUM_SIM_LINKS_MASK; 517*a90b9d01SCy Schubert 518*a90b9d01SCy Schubert /* TODO: Advertise T2LM based on driver support as well */ 519*a90b9d01SCy Schubert mld_cap &= ~EHT_ML_MLD_CAPA_TID_TO_LINK_MAP_NEG_SUPP_MSK; 520*a90b9d01SCy Schubert 521*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: MLD Capabilities and Operations=0x%x", 522*a90b9d01SCy Schubert mld_cap); 523*a90b9d01SCy Schubert wpabuf_put_le16(buf, mld_cap); 524*a90b9d01SCy Schubert 525*a90b9d01SCy Schubert if (include_mld_id) { 526*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: AP MLD ID=0x%x", 527*a90b9d01SCy Schubert hostapd_get_mld_id(hapd)); 528*a90b9d01SCy Schubert wpabuf_put_u8(buf, hostapd_get_mld_id(hapd)); 529*a90b9d01SCy Schubert } 530*a90b9d01SCy Schubert 531*a90b9d01SCy Schubert if (!mld_info) 532*a90b9d01SCy Schubert goto out; 533*a90b9d01SCy Schubert 534*a90b9d01SCy Schubert /* Add link info for the other links */ 535*a90b9d01SCy Schubert for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { 536*a90b9d01SCy Schubert struct mld_link_info *link = &mld_info->links[link_id]; 537*a90b9d01SCy Schubert struct hostapd_data *link_bss; 538*a90b9d01SCy Schubert 539*a90b9d01SCy Schubert /* 540*a90b9d01SCy Schubert * control (2) + station info length (1) + MAC address (6) + 541*a90b9d01SCy Schubert * beacon interval (2) + TSF offset (8) + DTIM info (2) + BSS 542*a90b9d01SCy Schubert * parameters change counter (1) + station profile length. 543*a90b9d01SCy Schubert */ 544*a90b9d01SCy Schubert #define EHT_ML_STA_INFO_LEN 22 545*a90b9d01SCy Schubert size_t total_len = EHT_ML_STA_INFO_LEN + 546*a90b9d01SCy Schubert link->resp_sta_profile_len; 547*a90b9d01SCy Schubert 548*a90b9d01SCy Schubert /* Skip the local one */ 549*a90b9d01SCy Schubert if (link_id == hapd->mld_link_id || !link->valid) 550*a90b9d01SCy Schubert continue; 551*a90b9d01SCy Schubert 552*a90b9d01SCy Schubert link_bss = hostapd_mld_get_link_bss(hapd, link_id); 553*a90b9d01SCy Schubert if (!link_bss) { 554*a90b9d01SCy Schubert wpa_printf(MSG_ERROR, 555*a90b9d01SCy Schubert "MLD: Couldn't find link BSS - skip it"); 556*a90b9d01SCy Schubert continue; 557*a90b9d01SCy Schubert } 558*a90b9d01SCy Schubert 559*a90b9d01SCy Schubert /* Per-STA Profile subelement */ 560*a90b9d01SCy Schubert wpabuf_put_u8(buf, EHT_ML_SUB_ELEM_PER_STA_PROFILE); 561*a90b9d01SCy Schubert 562*a90b9d01SCy Schubert if (total_len <= 255) 563*a90b9d01SCy Schubert wpabuf_put_u8(buf, total_len); 564*a90b9d01SCy Schubert else 565*a90b9d01SCy Schubert wpabuf_put_u8(buf, 255); 566*a90b9d01SCy Schubert 567*a90b9d01SCy Schubert /* STA Control */ 568*a90b9d01SCy Schubert control = (link_id & 0xf) | 569*a90b9d01SCy Schubert EHT_PER_STA_CTRL_MAC_ADDR_PRESENT_MSK | 570*a90b9d01SCy Schubert EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK | 571*a90b9d01SCy Schubert EHT_PER_STA_CTRL_TSF_OFFSET_PRESENT_MSK | 572*a90b9d01SCy Schubert EHT_PER_STA_CTRL_BEACON_INTERVAL_PRESENT_MSK | 573*a90b9d01SCy Schubert EHT_PER_STA_CTRL_DTIM_INFO_PRESENT_MSK | 574*a90b9d01SCy Schubert EHT_PER_STA_CTRL_BSS_PARAM_CNT_PRESENT_MSK; 575*a90b9d01SCy Schubert wpabuf_put_le16(buf, control); 576*a90b9d01SCy Schubert 577*a90b9d01SCy Schubert /* STA Info */ 578*a90b9d01SCy Schubert 579*a90b9d01SCy Schubert /* STA Info Length */ 580*a90b9d01SCy Schubert wpabuf_put_u8(buf, EHT_ML_STA_INFO_LEN - 2); 581*a90b9d01SCy Schubert wpabuf_put_data(buf, link->local_addr, ETH_ALEN); 582*a90b9d01SCy Schubert wpabuf_put_le16(buf, link_bss->iconf->beacon_int); 583*a90b9d01SCy Schubert 584*a90b9d01SCy Schubert /* TSF Offset */ 585*a90b9d01SCy Schubert /* 586*a90b9d01SCy Schubert * TODO: Currently setting TSF offset to zero. However, this 587*a90b9d01SCy Schubert * information needs to come from the driver. 588*a90b9d01SCy Schubert */ 589*a90b9d01SCy Schubert wpabuf_put_le64(buf, 0); 590*a90b9d01SCy Schubert 591*a90b9d01SCy Schubert /* DTIM Info */ 592*a90b9d01SCy Schubert wpabuf_put_u8(buf, 0); /* DTIM Count */ 593*a90b9d01SCy Schubert wpabuf_put_u8(buf, link_bss->conf->dtim_period); 594*a90b9d01SCy Schubert 595*a90b9d01SCy Schubert /* BSS Parameters Change Count */ 596*a90b9d01SCy Schubert wpabuf_put_u8(buf, hapd->eht_mld_bss_param_change); 597*a90b9d01SCy Schubert 598*a90b9d01SCy Schubert if (!link->resp_sta_profile) 599*a90b9d01SCy Schubert continue; 600*a90b9d01SCy Schubert 601*a90b9d01SCy Schubert /* Fragment the sub element if needed */ 602*a90b9d01SCy Schubert if (total_len <= 255) { 603*a90b9d01SCy Schubert wpabuf_put_data(buf, link->resp_sta_profile, 604*a90b9d01SCy Schubert link->resp_sta_profile_len); 605*a90b9d01SCy Schubert } else { 606*a90b9d01SCy Schubert ptr = link->resp_sta_profile; 607*a90b9d01SCy Schubert len = link->resp_sta_profile_len; 608*a90b9d01SCy Schubert 609*a90b9d01SCy Schubert slice_len = 255 - EHT_ML_STA_INFO_LEN; 610*a90b9d01SCy Schubert 611*a90b9d01SCy Schubert wpabuf_put_data(buf, ptr, slice_len); 612*a90b9d01SCy Schubert len -= slice_len; 613*a90b9d01SCy Schubert ptr += slice_len; 614*a90b9d01SCy Schubert 615*a90b9d01SCy Schubert while (len) { 616*a90b9d01SCy Schubert if (len <= 255) 617*a90b9d01SCy Schubert slice_len = len; 618*a90b9d01SCy Schubert else 619*a90b9d01SCy Schubert slice_len = 255; 620*a90b9d01SCy Schubert 621*a90b9d01SCy Schubert wpabuf_put_u8(buf, EHT_ML_SUB_ELEM_FRAGMENT); 622*a90b9d01SCy Schubert wpabuf_put_u8(buf, slice_len); 623*a90b9d01SCy Schubert wpabuf_put_data(buf, ptr, slice_len); 624*a90b9d01SCy Schubert 625*a90b9d01SCy Schubert len -= slice_len; 626*a90b9d01SCy Schubert ptr += slice_len; 627*a90b9d01SCy Schubert } 628*a90b9d01SCy Schubert } 629*a90b9d01SCy Schubert } 630*a90b9d01SCy Schubert 631*a90b9d01SCy Schubert out: 632*a90b9d01SCy Schubert /* Fragment the Multi-Link element, if needed */ 633*a90b9d01SCy Schubert len = wpabuf_len(buf); 634*a90b9d01SCy Schubert ptr = wpabuf_head(buf); 635*a90b9d01SCy Schubert 636*a90b9d01SCy Schubert if (len <= 254) 637*a90b9d01SCy Schubert slice_len = len; 638*a90b9d01SCy Schubert else 639*a90b9d01SCy Schubert slice_len = 254; 640*a90b9d01SCy Schubert 641*a90b9d01SCy Schubert *pos++ = WLAN_EID_EXTENSION; 642*a90b9d01SCy Schubert *pos++ = slice_len + 1; 643*a90b9d01SCy Schubert *pos++ = WLAN_EID_EXT_MULTI_LINK; 644*a90b9d01SCy Schubert os_memcpy(pos, ptr, slice_len); 645*a90b9d01SCy Schubert 646*a90b9d01SCy Schubert ptr += slice_len; 647*a90b9d01SCy Schubert pos += slice_len; 648*a90b9d01SCy Schubert len -= slice_len; 649*a90b9d01SCy Schubert 650*a90b9d01SCy Schubert while (len) { 651*a90b9d01SCy Schubert if (len <= 255) 652*a90b9d01SCy Schubert slice_len = len; 653*a90b9d01SCy Schubert else 654*a90b9d01SCy Schubert slice_len = 255; 655*a90b9d01SCy Schubert 656*a90b9d01SCy Schubert *pos++ = WLAN_EID_FRAGMENT; 657*a90b9d01SCy Schubert *pos++ = slice_len; 658*a90b9d01SCy Schubert os_memcpy(pos, ptr, slice_len); 659*a90b9d01SCy Schubert 660*a90b9d01SCy Schubert ptr += slice_len; 661*a90b9d01SCy Schubert pos += slice_len; 662*a90b9d01SCy Schubert len -= slice_len; 663*a90b9d01SCy Schubert } 664*a90b9d01SCy Schubert 665*a90b9d01SCy Schubert wpabuf_free(buf); 666*a90b9d01SCy Schubert return pos; 667*a90b9d01SCy Schubert } 668*a90b9d01SCy Schubert 669*a90b9d01SCy Schubert 670*a90b9d01SCy Schubert static u8 * hostapd_eid_eht_reconf_ml(struct hostapd_data *hapd, u8 *eid) 671*a90b9d01SCy Schubert { 672*a90b9d01SCy Schubert #ifdef CONFIG_TESTING_OPTIONS 673*a90b9d01SCy Schubert struct hostapd_data *other_hapd; 674*a90b9d01SCy Schubert u16 control; 675*a90b9d01SCy Schubert u8 *pos = eid; 676*a90b9d01SCy Schubert unsigned int i; 677*a90b9d01SCy Schubert 678*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: Reconfiguration ML"); 679*a90b9d01SCy Schubert 680*a90b9d01SCy Schubert /* First check if the element needs to be added */ 681*a90b9d01SCy Schubert for (i = 0; i < hapd->iface->interfaces->count; i++) { 682*a90b9d01SCy Schubert other_hapd = hapd->iface->interfaces->iface[i]->bss[0]; 683*a90b9d01SCy Schubert 684*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: Reconfiguration ML: %u", 685*a90b9d01SCy Schubert other_hapd->eht_mld_link_removal_count); 686*a90b9d01SCy Schubert 687*a90b9d01SCy Schubert if (other_hapd->eht_mld_link_removal_count) 688*a90b9d01SCy Schubert break; 689*a90b9d01SCy Schubert } 690*a90b9d01SCy Schubert 691*a90b9d01SCy Schubert /* No link is going to be removed */ 692*a90b9d01SCy Schubert if (i == hapd->iface->interfaces->count) 693*a90b9d01SCy Schubert return eid; 694*a90b9d01SCy Schubert 695*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: Reconfiguration ML: Adding element"); 696*a90b9d01SCy Schubert 697*a90b9d01SCy Schubert /* The length will be set at the end */ 698*a90b9d01SCy Schubert *pos++ = WLAN_EID_EXTENSION; 699*a90b9d01SCy Schubert *pos++ = 0; 700*a90b9d01SCy Schubert *pos++ = WLAN_EID_EXT_MULTI_LINK; 701*a90b9d01SCy Schubert 702*a90b9d01SCy Schubert /* Set the Multi-Link Control field */ 703*a90b9d01SCy Schubert control = MULTI_LINK_CONTROL_TYPE_RECONF; 704*a90b9d01SCy Schubert WPA_PUT_LE16(pos, control); 705*a90b9d01SCy Schubert pos += 2; 706*a90b9d01SCy Schubert 707*a90b9d01SCy Schubert /* Common Info doesn't include any information */ 708*a90b9d01SCy Schubert *pos++ = 1; 709*a90b9d01SCy Schubert 710*a90b9d01SCy Schubert /* Add the per station profiles */ 711*a90b9d01SCy Schubert for (i = 0; i < hapd->iface->interfaces->count; i++) { 712*a90b9d01SCy Schubert other_hapd = hapd->iface->interfaces->iface[i]->bss[0]; 713*a90b9d01SCy Schubert if (!other_hapd->eht_mld_link_removal_count) 714*a90b9d01SCy Schubert continue; 715*a90b9d01SCy Schubert 716*a90b9d01SCy Schubert /* Subelement ID is 0 */ 717*a90b9d01SCy Schubert *pos++ = 0; 718*a90b9d01SCy Schubert *pos++ = 5; 719*a90b9d01SCy Schubert 720*a90b9d01SCy Schubert control = other_hapd->mld_link_id | 721*a90b9d01SCy Schubert EHT_PER_STA_RECONF_CTRL_AP_REMOVAL_TIMER; 722*a90b9d01SCy Schubert 723*a90b9d01SCy Schubert WPA_PUT_LE16(pos, control); 724*a90b9d01SCy Schubert pos += 2; 725*a90b9d01SCy Schubert 726*a90b9d01SCy Schubert /* STA profile length */ 727*a90b9d01SCy Schubert *pos++ = 3; 728*a90b9d01SCy Schubert 729*a90b9d01SCy Schubert WPA_PUT_LE16(pos, other_hapd->eht_mld_link_removal_count); 730*a90b9d01SCy Schubert pos += 2; 731*a90b9d01SCy Schubert } 732*a90b9d01SCy Schubert 733*a90b9d01SCy Schubert eid[1] = pos - eid - 2; 734*a90b9d01SCy Schubert 735*a90b9d01SCy Schubert wpa_hexdump(MSG_DEBUG, "MLD: Reconfiguration ML", eid, eid[1] + 2); 736*a90b9d01SCy Schubert return pos; 737*a90b9d01SCy Schubert #else /* CONFIG_TESTING_OPTIONS */ 738*a90b9d01SCy Schubert return eid; 739*a90b9d01SCy Schubert #endif /* CONFIG_TESTING_OPTIONS */ 740*a90b9d01SCy Schubert } 741*a90b9d01SCy Schubert 742*a90b9d01SCy Schubert 743*a90b9d01SCy Schubert static size_t hostapd_eid_eht_ml_len(struct mld_info *info, 744*a90b9d01SCy Schubert bool include_mld_id) 745*a90b9d01SCy Schubert { 746*a90b9d01SCy Schubert size_t len = 0; 747*a90b9d01SCy Schubert size_t eht_ml_len = 2 + EHT_ML_COMMON_INFO_LEN; 748*a90b9d01SCy Schubert u8 link_id; 749*a90b9d01SCy Schubert 750*a90b9d01SCy Schubert if (include_mld_id) 751*a90b9d01SCy Schubert eht_ml_len++; 752*a90b9d01SCy Schubert 753*a90b9d01SCy Schubert for (link_id = 0; info && link_id < ARRAY_SIZE(info->links); 754*a90b9d01SCy Schubert link_id++) { 755*a90b9d01SCy Schubert struct mld_link_info *link; 756*a90b9d01SCy Schubert size_t sta_len = EHT_ML_STA_INFO_LEN; 757*a90b9d01SCy Schubert 758*a90b9d01SCy Schubert link = &info->links[link_id]; 759*a90b9d01SCy Schubert if (!link->valid) 760*a90b9d01SCy Schubert continue; 761*a90b9d01SCy Schubert 762*a90b9d01SCy Schubert sta_len += link->resp_sta_profile_len; 763*a90b9d01SCy Schubert 764*a90b9d01SCy Schubert /* Element data and (fragmentation) headers */ 765*a90b9d01SCy Schubert eht_ml_len += sta_len; 766*a90b9d01SCy Schubert eht_ml_len += 2 + sta_len / 255 * 2; 767*a90b9d01SCy Schubert } 768*a90b9d01SCy Schubert 769*a90b9d01SCy Schubert /* Element data */ 770*a90b9d01SCy Schubert len += eht_ml_len; 771*a90b9d01SCy Schubert 772*a90b9d01SCy Schubert /* First header (254 bytes of data) */ 773*a90b9d01SCy Schubert len += 3; 774*a90b9d01SCy Schubert 775*a90b9d01SCy Schubert /* Fragmentation headers; +1 for shorter first chunk */ 776*a90b9d01SCy Schubert len += (eht_ml_len + 1) / 255 * 2; 777*a90b9d01SCy Schubert 778*a90b9d01SCy Schubert return len; 779*a90b9d01SCy Schubert } 780*a90b9d01SCy Schubert #undef EHT_ML_COMMON_INFO_LEN 781*a90b9d01SCy Schubert #undef EHT_ML_STA_INFO_LEN 782*a90b9d01SCy Schubert 783*a90b9d01SCy Schubert 784*a90b9d01SCy Schubert u8 * hostapd_eid_eht_ml_beacon(struct hostapd_data *hapd, 785*a90b9d01SCy Schubert struct mld_info *info, 786*a90b9d01SCy Schubert u8 *eid, bool include_mld_id) 787*a90b9d01SCy Schubert { 788*a90b9d01SCy Schubert eid = hostapd_eid_eht_basic_ml_common(hapd, eid, info, include_mld_id); 789*a90b9d01SCy Schubert return hostapd_eid_eht_reconf_ml(hapd, eid); 790*a90b9d01SCy Schubert } 791*a90b9d01SCy Schubert 792*a90b9d01SCy Schubert 793*a90b9d01SCy Schubert 794*a90b9d01SCy Schubert u8 * hostapd_eid_eht_ml_assoc(struct hostapd_data *hapd, struct sta_info *info, 795*a90b9d01SCy Schubert u8 *eid) 796*a90b9d01SCy Schubert { 797*a90b9d01SCy Schubert if (!ap_sta_is_mld(hapd, info)) 798*a90b9d01SCy Schubert return eid; 799*a90b9d01SCy Schubert 800*a90b9d01SCy Schubert eid = hostapd_eid_eht_basic_ml_common(hapd, eid, &info->mld_info, 801*a90b9d01SCy Schubert false); 802*a90b9d01SCy Schubert ap_sta_free_sta_profile(&info->mld_info); 803*a90b9d01SCy Schubert return hostapd_eid_eht_reconf_ml(hapd, eid); 804*a90b9d01SCy Schubert } 805*a90b9d01SCy Schubert 806*a90b9d01SCy Schubert 807*a90b9d01SCy Schubert size_t hostapd_eid_eht_ml_beacon_len(struct hostapd_data *hapd, 808*a90b9d01SCy Schubert struct mld_info *info, 809*a90b9d01SCy Schubert bool include_mld_id) 810*a90b9d01SCy Schubert { 811*a90b9d01SCy Schubert return hostapd_eid_eht_ml_len(info, include_mld_id); 812*a90b9d01SCy Schubert } 813*a90b9d01SCy Schubert 814*a90b9d01SCy Schubert 815*a90b9d01SCy Schubert struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd) 816*a90b9d01SCy Schubert { 817*a90b9d01SCy Schubert struct wpabuf *buf = wpabuf_alloc(12); 818*a90b9d01SCy Schubert 819*a90b9d01SCy Schubert if (!buf) 820*a90b9d01SCy Schubert return NULL; 821*a90b9d01SCy Schubert 822*a90b9d01SCy Schubert wpabuf_put_u8(buf, WLAN_EID_EXTENSION); 823*a90b9d01SCy Schubert wpabuf_put_u8(buf, 10); 824*a90b9d01SCy Schubert wpabuf_put_u8(buf, WLAN_EID_EXT_MULTI_LINK); 825*a90b9d01SCy Schubert wpabuf_put_le16(buf, MULTI_LINK_CONTROL_TYPE_BASIC); 826*a90b9d01SCy Schubert wpabuf_put_u8(buf, ETH_ALEN + 1); 827*a90b9d01SCy Schubert wpabuf_put_data(buf, hapd->mld->mld_addr, ETH_ALEN); 828*a90b9d01SCy Schubert 829*a90b9d01SCy Schubert return buf; 830*a90b9d01SCy Schubert } 831*a90b9d01SCy Schubert 832*a90b9d01SCy Schubert 833*a90b9d01SCy Schubert #ifdef CONFIG_SAE 834*a90b9d01SCy Schubert 835*a90b9d01SCy Schubert static const u8 * 836*a90b9d01SCy Schubert sae_commit_skip_fixed_fields(const struct ieee80211_mgmt *mgmt, size_t len, 837*a90b9d01SCy Schubert const u8 *pos, u16 status_code) 838*a90b9d01SCy Schubert { 839*a90b9d01SCy Schubert u16 group; 840*a90b9d01SCy Schubert size_t prime_len; 841*a90b9d01SCy Schubert struct crypto_ec *ec; 842*a90b9d01SCy Schubert 843*a90b9d01SCy Schubert if (status_code != WLAN_STATUS_SAE_HASH_TO_ELEMENT) 844*a90b9d01SCy Schubert return pos; 845*a90b9d01SCy Schubert 846*a90b9d01SCy Schubert /* SAE H2E commit message (group, scalar, FFE) */ 847*a90b9d01SCy Schubert if (len < 2) { 848*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 849*a90b9d01SCy Schubert "EHT: SAE Group is not present"); 850*a90b9d01SCy Schubert return NULL; 851*a90b9d01SCy Schubert } 852*a90b9d01SCy Schubert 853*a90b9d01SCy Schubert group = WPA_GET_LE16(pos); 854*a90b9d01SCy Schubert pos += 2; 855*a90b9d01SCy Schubert 856*a90b9d01SCy Schubert /* TODO: How to parse when the group is unknown? */ 857*a90b9d01SCy Schubert ec = crypto_ec_init(group); 858*a90b9d01SCy Schubert if (!ec) { 859*a90b9d01SCy Schubert const struct dh_group *dh = dh_groups_get(group); 860*a90b9d01SCy Schubert 861*a90b9d01SCy Schubert if (!dh) { 862*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "EHT: Unknown SAE group %u", 863*a90b9d01SCy Schubert group); 864*a90b9d01SCy Schubert return NULL; 865*a90b9d01SCy Schubert } 866*a90b9d01SCy Schubert 867*a90b9d01SCy Schubert prime_len = dh->prime_len; 868*a90b9d01SCy Schubert } else { 869*a90b9d01SCy Schubert prime_len = crypto_ec_prime_len(ec); 870*a90b9d01SCy Schubert } 871*a90b9d01SCy Schubert 872*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "EHT: SAE scalar length is %zu", prime_len); 873*a90b9d01SCy Schubert 874*a90b9d01SCy Schubert /* scalar */ 875*a90b9d01SCy Schubert pos += prime_len; 876*a90b9d01SCy Schubert 877*a90b9d01SCy Schubert if (ec) { 878*a90b9d01SCy Schubert pos += prime_len * 2; 879*a90b9d01SCy Schubert crypto_ec_deinit(ec); 880*a90b9d01SCy Schubert } else { 881*a90b9d01SCy Schubert pos += prime_len; 882*a90b9d01SCy Schubert } 883*a90b9d01SCy Schubert 884*a90b9d01SCy Schubert if (pos - mgmt->u.auth.variable > (int) len) { 885*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 886*a90b9d01SCy Schubert "EHT: Too short SAE commit Authentication frame"); 887*a90b9d01SCy Schubert return NULL; 888*a90b9d01SCy Schubert } 889*a90b9d01SCy Schubert 890*a90b9d01SCy Schubert wpa_hexdump(MSG_DEBUG, "EHT: SAE: Authentication frame elements", 891*a90b9d01SCy Schubert pos, (int) len - (pos - mgmt->u.auth.variable)); 892*a90b9d01SCy Schubert 893*a90b9d01SCy Schubert return pos; 894*a90b9d01SCy Schubert } 895*a90b9d01SCy Schubert 896*a90b9d01SCy Schubert 897*a90b9d01SCy Schubert static const u8 * 898*a90b9d01SCy Schubert sae_confirm_skip_fixed_fields(struct hostapd_data *hapd, 899*a90b9d01SCy Schubert const struct ieee80211_mgmt *mgmt, size_t len, 900*a90b9d01SCy Schubert const u8 *pos, u16 status_code) 901*a90b9d01SCy Schubert { 902*a90b9d01SCy Schubert struct sta_info *sta; 903*a90b9d01SCy Schubert 904*a90b9d01SCy Schubert if (status_code == WLAN_STATUS_REJECTED_WITH_SUGGESTED_BSS_TRANSITION) 905*a90b9d01SCy Schubert return pos; 906*a90b9d01SCy Schubert 907*a90b9d01SCy Schubert /* send confirm integer */ 908*a90b9d01SCy Schubert pos += 2; 909*a90b9d01SCy Schubert 910*a90b9d01SCy Schubert /* 911*a90b9d01SCy Schubert * At this stage we should already have an MLD station and actually SA 912*a90b9d01SCy Schubert * will be replaced with the MLD MAC address by the driver. 913*a90b9d01SCy Schubert */ 914*a90b9d01SCy Schubert sta = ap_get_sta(hapd, mgmt->sa); 915*a90b9d01SCy Schubert if (!sta) { 916*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "SAE: No MLD STA for SAE confirm"); 917*a90b9d01SCy Schubert return NULL; 918*a90b9d01SCy Schubert } 919*a90b9d01SCy Schubert 920*a90b9d01SCy Schubert if (!sta->sae || sta->sae->state < SAE_COMMITTED || !sta->sae->tmp) { 921*a90b9d01SCy Schubert if (sta->sae) 922*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "SAE: Invalid state=%u", 923*a90b9d01SCy Schubert sta->sae->state); 924*a90b9d01SCy Schubert else 925*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "SAE: No SAE context"); 926*a90b9d01SCy Schubert return NULL; 927*a90b9d01SCy Schubert } 928*a90b9d01SCy Schubert 929*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "SAE: confirm: kck_len=%zu", 930*a90b9d01SCy Schubert sta->sae->tmp->kck_len); 931*a90b9d01SCy Schubert 932*a90b9d01SCy Schubert pos += sta->sae->tmp->kck_len; 933*a90b9d01SCy Schubert 934*a90b9d01SCy Schubert if (pos - mgmt->u.auth.variable > (int) len) { 935*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 936*a90b9d01SCy Schubert "EHT: Too short SAE confirm Authentication frame"); 937*a90b9d01SCy Schubert return NULL; 938*a90b9d01SCy Schubert } 939*a90b9d01SCy Schubert 940*a90b9d01SCy Schubert return pos; 941*a90b9d01SCy Schubert } 942*a90b9d01SCy Schubert 943*a90b9d01SCy Schubert #endif /* CONFIG_SAE */ 944*a90b9d01SCy Schubert 945*a90b9d01SCy Schubert 946*a90b9d01SCy Schubert static const u8 * auth_skip_fixed_fields(struct hostapd_data *hapd, 947*a90b9d01SCy Schubert const struct ieee80211_mgmt *mgmt, 948*a90b9d01SCy Schubert size_t len) 949*a90b9d01SCy Schubert { 950*a90b9d01SCy Schubert u16 auth_alg = le_to_host16(mgmt->u.auth.auth_alg); 951*a90b9d01SCy Schubert #ifdef CONFIG_SAE 952*a90b9d01SCy Schubert u16 auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction); 953*a90b9d01SCy Schubert u16 status_code = le_to_host16(mgmt->u.auth.status_code); 954*a90b9d01SCy Schubert #endif /* CONFIG_SAE */ 955*a90b9d01SCy Schubert const u8 *pos = mgmt->u.auth.variable; 956*a90b9d01SCy Schubert 957*a90b9d01SCy Schubert /* Skip fixed fields as based on IEE P802.11-REVme/D3.0, Table 9-69 958*a90b9d01SCy Schubert * (Presence of fields and elements in Authentications frames) */ 959*a90b9d01SCy Schubert switch (auth_alg) { 960*a90b9d01SCy Schubert case WLAN_AUTH_OPEN: 961*a90b9d01SCy Schubert return pos; 962*a90b9d01SCy Schubert #ifdef CONFIG_SAE 963*a90b9d01SCy Schubert case WLAN_AUTH_SAE: 964*a90b9d01SCy Schubert if (auth_transaction == 1) { 965*a90b9d01SCy Schubert if (status_code == WLAN_STATUS_SUCCESS) { 966*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 967*a90b9d01SCy Schubert "EHT: SAE H2E is mandatory for MLD"); 968*a90b9d01SCy Schubert goto out; 969*a90b9d01SCy Schubert } 970*a90b9d01SCy Schubert 971*a90b9d01SCy Schubert return sae_commit_skip_fixed_fields(mgmt, len, pos, 972*a90b9d01SCy Schubert status_code); 973*a90b9d01SCy Schubert } else if (auth_transaction == 2) { 974*a90b9d01SCy Schubert return sae_confirm_skip_fixed_fields(hapd, mgmt, len, 975*a90b9d01SCy Schubert pos, status_code); 976*a90b9d01SCy Schubert } 977*a90b9d01SCy Schubert 978*a90b9d01SCy Schubert return pos; 979*a90b9d01SCy Schubert #endif /* CONFIG_SAE */ 980*a90b9d01SCy Schubert /* TODO: Support additional algorithms that can be used for MLO */ 981*a90b9d01SCy Schubert case WLAN_AUTH_FT: 982*a90b9d01SCy Schubert case WLAN_AUTH_FILS_SK: 983*a90b9d01SCy Schubert case WLAN_AUTH_FILS_SK_PFS: 984*a90b9d01SCy Schubert case WLAN_AUTH_FILS_PK: 985*a90b9d01SCy Schubert case WLAN_AUTH_PASN: 986*a90b9d01SCy Schubert default: 987*a90b9d01SCy Schubert break; 988*a90b9d01SCy Schubert } 989*a90b9d01SCy Schubert 990*a90b9d01SCy Schubert #ifdef CONFIG_SAE 991*a90b9d01SCy Schubert out: 992*a90b9d01SCy Schubert #endif /* CONFIG_SAE */ 993*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 994*a90b9d01SCy Schubert "TODO: Authentication algorithm %u not supported with MLD", 995*a90b9d01SCy Schubert auth_alg); 996*a90b9d01SCy Schubert return NULL; 997*a90b9d01SCy Schubert } 998*a90b9d01SCy Schubert 999*a90b9d01SCy Schubert 1000*a90b9d01SCy Schubert const u8 * hostapd_process_ml_auth(struct hostapd_data *hapd, 1001*a90b9d01SCy Schubert const struct ieee80211_mgmt *mgmt, 1002*a90b9d01SCy Schubert size_t len) 1003*a90b9d01SCy Schubert { 1004*a90b9d01SCy Schubert struct ieee802_11_elems elems; 1005*a90b9d01SCy Schubert const u8 *pos; 1006*a90b9d01SCy Schubert 1007*a90b9d01SCy Schubert if (!hapd->conf->mld_ap) 1008*a90b9d01SCy Schubert return NULL; 1009*a90b9d01SCy Schubert 1010*a90b9d01SCy Schubert len -= offsetof(struct ieee80211_mgmt, u.auth.variable); 1011*a90b9d01SCy Schubert 1012*a90b9d01SCy Schubert pos = auth_skip_fixed_fields(hapd, mgmt, len); 1013*a90b9d01SCy Schubert if (!pos) 1014*a90b9d01SCy Schubert return NULL; 1015*a90b9d01SCy Schubert 1016*a90b9d01SCy Schubert if (ieee802_11_parse_elems(pos, 1017*a90b9d01SCy Schubert (int)len - (pos - mgmt->u.auth.variable), 1018*a90b9d01SCy Schubert &elems, 0) == ParseFailed) { 1019*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1020*a90b9d01SCy Schubert "MLD: Failed parsing Authentication frame"); 1021*a90b9d01SCy Schubert } 1022*a90b9d01SCy Schubert 1023*a90b9d01SCy Schubert if (!elems.basic_mle || !elems.basic_mle_len) 1024*a90b9d01SCy Schubert return NULL; 1025*a90b9d01SCy Schubert 1026*a90b9d01SCy Schubert return get_basic_mle_mld_addr(elems.basic_mle, elems.basic_mle_len); 1027*a90b9d01SCy Schubert } 1028*a90b9d01SCy Schubert 1029*a90b9d01SCy Schubert 1030*a90b9d01SCy Schubert static int hostapd_mld_validate_assoc_info(struct hostapd_data *hapd, 1031*a90b9d01SCy Schubert struct sta_info *sta) 1032*a90b9d01SCy Schubert { 1033*a90b9d01SCy Schubert u8 link_id; 1034*a90b9d01SCy Schubert struct mld_info *info = &sta->mld_info; 1035*a90b9d01SCy Schubert 1036*a90b9d01SCy Schubert if (!ap_sta_is_mld(hapd, sta)) { 1037*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: Not a non-AP MLD"); 1038*a90b9d01SCy Schubert return 0; 1039*a90b9d01SCy Schubert } 1040*a90b9d01SCy Schubert 1041*a90b9d01SCy Schubert /* 1042*a90b9d01SCy Schubert * Iterate over the links negotiated in the (Re)Association Request 1043*a90b9d01SCy Schubert * frame and validate that they are indeed valid links in the local AP 1044*a90b9d01SCy Schubert * MLD. 1045*a90b9d01SCy Schubert * 1046*a90b9d01SCy Schubert * While at it, also update the local address for the links in the 1047*a90b9d01SCy Schubert * mld_info, so it could be easily available for later flows, e.g., for 1048*a90b9d01SCy Schubert * the RSN Authenticator, etc. 1049*a90b9d01SCy Schubert */ 1050*a90b9d01SCy Schubert for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { 1051*a90b9d01SCy Schubert struct hostapd_data *other_hapd; 1052*a90b9d01SCy Schubert 1053*a90b9d01SCy Schubert if (!info->links[link_id].valid || link_id == hapd->mld_link_id) 1054*a90b9d01SCy Schubert continue; 1055*a90b9d01SCy Schubert 1056*a90b9d01SCy Schubert other_hapd = hostapd_mld_get_link_bss(hapd, link_id); 1057*a90b9d01SCy Schubert if (!other_hapd) { 1058*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: Invalid link ID=%u", 1059*a90b9d01SCy Schubert link_id); 1060*a90b9d01SCy Schubert return -1; 1061*a90b9d01SCy Schubert } 1062*a90b9d01SCy Schubert 1063*a90b9d01SCy Schubert os_memcpy(info->links[link_id].local_addr, other_hapd->own_addr, 1064*a90b9d01SCy Schubert ETH_ALEN); 1065*a90b9d01SCy Schubert } 1066*a90b9d01SCy Schubert 1067*a90b9d01SCy Schubert return 0; 1068*a90b9d01SCy Schubert } 1069*a90b9d01SCy Schubert 1070*a90b9d01SCy Schubert 1071*a90b9d01SCy Schubert int hostapd_process_ml_assoc_req_addr(struct hostapd_data *hapd, 1072*a90b9d01SCy Schubert const u8 *basic_mle, size_t basic_mle_len, 1073*a90b9d01SCy Schubert u8 *mld_addr) 1074*a90b9d01SCy Schubert { 1075*a90b9d01SCy Schubert struct wpabuf *mlbuf = ieee802_11_defrag(basic_mle, basic_mle_len, 1076*a90b9d01SCy Schubert true); 1077*a90b9d01SCy Schubert struct ieee80211_eht_ml *ml; 1078*a90b9d01SCy Schubert struct eht_ml_basic_common_info *common_info; 1079*a90b9d01SCy Schubert size_t ml_len, common_info_len; 1080*a90b9d01SCy Schubert int ret = -1; 1081*a90b9d01SCy Schubert u16 ml_control; 1082*a90b9d01SCy Schubert 1083*a90b9d01SCy Schubert if (!mlbuf) 1084*a90b9d01SCy Schubert return WLAN_STATUS_SUCCESS; 1085*a90b9d01SCy Schubert 1086*a90b9d01SCy Schubert ml = (struct ieee80211_eht_ml *) wpabuf_head(mlbuf); 1087*a90b9d01SCy Schubert ml_len = wpabuf_len(mlbuf); 1088*a90b9d01SCy Schubert 1089*a90b9d01SCy Schubert if (ml_len < sizeof(*ml)) 1090*a90b9d01SCy Schubert goto out; 1091*a90b9d01SCy Schubert 1092*a90b9d01SCy Schubert ml_control = le_to_host16(ml->ml_control); 1093*a90b9d01SCy Schubert if ((ml_control & MULTI_LINK_CONTROL_TYPE_MASK) != 1094*a90b9d01SCy Schubert MULTI_LINK_CONTROL_TYPE_BASIC) { 1095*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: Invalid ML type=%u", 1096*a90b9d01SCy Schubert ml_control & MULTI_LINK_CONTROL_TYPE_MASK); 1097*a90b9d01SCy Schubert goto out; 1098*a90b9d01SCy Schubert } 1099*a90b9d01SCy Schubert 1100*a90b9d01SCy Schubert /* Common Info Length and MLD MAC Address must always be present */ 1101*a90b9d01SCy Schubert common_info_len = 1 + ETH_ALEN; 1102*a90b9d01SCy Schubert 1103*a90b9d01SCy Schubert if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_LINK_ID) { 1104*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: Link ID Info not expected"); 1105*a90b9d01SCy Schubert goto out; 1106*a90b9d01SCy Schubert } 1107*a90b9d01SCy Schubert 1108*a90b9d01SCy Schubert if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT) { 1109*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1110*a90b9d01SCy Schubert "MLD: BSS Parameters Change Count not expected"); 1111*a90b9d01SCy Schubert goto out; 1112*a90b9d01SCy Schubert } 1113*a90b9d01SCy Schubert 1114*a90b9d01SCy Schubert if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO) { 1115*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1116*a90b9d01SCy Schubert "MLD: Medium Synchronization Delay Information not expected"); 1117*a90b9d01SCy Schubert goto out; 1118*a90b9d01SCy Schubert } 1119*a90b9d01SCy Schubert 1120*a90b9d01SCy Schubert if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) 1121*a90b9d01SCy Schubert common_info_len += 2; 1122*a90b9d01SCy Schubert 1123*a90b9d01SCy Schubert if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA) 1124*a90b9d01SCy Schubert common_info_len += 2; 1125*a90b9d01SCy Schubert 1126*a90b9d01SCy Schubert if (sizeof(*ml) + common_info_len > ml_len) { 1127*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: Not enough bytes for common info"); 1128*a90b9d01SCy Schubert goto out; 1129*a90b9d01SCy Schubert } 1130*a90b9d01SCy Schubert 1131*a90b9d01SCy Schubert common_info = (struct eht_ml_basic_common_info *) ml->variable; 1132*a90b9d01SCy Schubert 1133*a90b9d01SCy Schubert /* Common information length includes the length octet */ 1134*a90b9d01SCy Schubert if (common_info->len != common_info_len) { 1135*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1136*a90b9d01SCy Schubert "MLD: Invalid common info len=%u", common_info->len); 1137*a90b9d01SCy Schubert goto out; 1138*a90b9d01SCy Schubert } 1139*a90b9d01SCy Schubert 1140*a90b9d01SCy Schubert /* Get the MLD MAC Address */ 1141*a90b9d01SCy Schubert os_memcpy(mld_addr, common_info->mld_addr, ETH_ALEN); 1142*a90b9d01SCy Schubert ret = 0; 1143*a90b9d01SCy Schubert 1144*a90b9d01SCy Schubert out: 1145*a90b9d01SCy Schubert wpabuf_free(mlbuf); 1146*a90b9d01SCy Schubert return ret; 1147*a90b9d01SCy Schubert } 1148*a90b9d01SCy Schubert 1149*a90b9d01SCy Schubert 1150*a90b9d01SCy Schubert u16 hostapd_process_ml_assoc_req(struct hostapd_data *hapd, 1151*a90b9d01SCy Schubert struct ieee802_11_elems *elems, 1152*a90b9d01SCy Schubert struct sta_info *sta) 1153*a90b9d01SCy Schubert { 1154*a90b9d01SCy Schubert struct wpabuf *mlbuf; 1155*a90b9d01SCy Schubert const struct ieee80211_eht_ml *ml; 1156*a90b9d01SCy Schubert const struct eht_ml_basic_common_info *common_info; 1157*a90b9d01SCy Schubert size_t ml_len, common_info_len; 1158*a90b9d01SCy Schubert struct mld_link_info *link_info; 1159*a90b9d01SCy Schubert struct mld_info *info = &sta->mld_info; 1160*a90b9d01SCy Schubert const u8 *pos; 1161*a90b9d01SCy Schubert int ret = -1; 1162*a90b9d01SCy Schubert u16 ml_control; 1163*a90b9d01SCy Schubert 1164*a90b9d01SCy Schubert mlbuf = ieee802_11_defrag(elems->basic_mle, elems->basic_mle_len, true); 1165*a90b9d01SCy Schubert if (!mlbuf) 1166*a90b9d01SCy Schubert return WLAN_STATUS_SUCCESS; 1167*a90b9d01SCy Schubert 1168*a90b9d01SCy Schubert ml = wpabuf_head(mlbuf); 1169*a90b9d01SCy Schubert ml_len = wpabuf_len(mlbuf); 1170*a90b9d01SCy Schubert 1171*a90b9d01SCy Schubert ml_control = le_to_host16(ml->ml_control); 1172*a90b9d01SCy Schubert if ((ml_control & MULTI_LINK_CONTROL_TYPE_MASK) != 1173*a90b9d01SCy Schubert MULTI_LINK_CONTROL_TYPE_BASIC) { 1174*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: Invalid ML type=%u", 1175*a90b9d01SCy Schubert ml_control & MULTI_LINK_CONTROL_TYPE_MASK); 1176*a90b9d01SCy Schubert goto out; 1177*a90b9d01SCy Schubert } 1178*a90b9d01SCy Schubert 1179*a90b9d01SCy Schubert /* Common Info length and MLD MAC address must always be present */ 1180*a90b9d01SCy Schubert common_info_len = 1 + ETH_ALEN; 1181*a90b9d01SCy Schubert 1182*a90b9d01SCy Schubert if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_LINK_ID) { 1183*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: Link ID info not expected"); 1184*a90b9d01SCy Schubert goto out; 1185*a90b9d01SCy Schubert } 1186*a90b9d01SCy Schubert 1187*a90b9d01SCy Schubert if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT) { 1188*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: BSS params change not expected"); 1189*a90b9d01SCy Schubert goto out; 1190*a90b9d01SCy Schubert } 1191*a90b9d01SCy Schubert 1192*a90b9d01SCy Schubert if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO) { 1193*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: Sync delay not expected"); 1194*a90b9d01SCy Schubert goto out; 1195*a90b9d01SCy Schubert } 1196*a90b9d01SCy Schubert 1197*a90b9d01SCy Schubert if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) { 1198*a90b9d01SCy Schubert common_info_len += 2; 1199*a90b9d01SCy Schubert } else { 1200*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: EML capabilities not present"); 1201*a90b9d01SCy Schubert } 1202*a90b9d01SCy Schubert 1203*a90b9d01SCy Schubert if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA) { 1204*a90b9d01SCy Schubert common_info_len += 2; 1205*a90b9d01SCy Schubert 1206*a90b9d01SCy Schubert } else { 1207*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: MLD capabilities not present"); 1208*a90b9d01SCy Schubert goto out; 1209*a90b9d01SCy Schubert } 1210*a90b9d01SCy Schubert 1211*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: expected_common_info_len=%lu", 1212*a90b9d01SCy Schubert common_info_len); 1213*a90b9d01SCy Schubert 1214*a90b9d01SCy Schubert if (sizeof(*ml) + common_info_len > ml_len) { 1215*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: Not enough bytes for common info"); 1216*a90b9d01SCy Schubert goto out; 1217*a90b9d01SCy Schubert } 1218*a90b9d01SCy Schubert 1219*a90b9d01SCy Schubert common_info = (const struct eht_ml_basic_common_info *) ml->variable; 1220*a90b9d01SCy Schubert 1221*a90b9d01SCy Schubert /* Common information length includes the length octet */ 1222*a90b9d01SCy Schubert if (common_info->len != common_info_len) { 1223*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1224*a90b9d01SCy Schubert "MLD: Invalid common info len=%u (expected %zu)", 1225*a90b9d01SCy Schubert common_info->len, common_info_len); 1226*a90b9d01SCy Schubert goto out; 1227*a90b9d01SCy Schubert } 1228*a90b9d01SCy Schubert 1229*a90b9d01SCy Schubert pos = common_info->variable; 1230*a90b9d01SCy Schubert 1231*a90b9d01SCy Schubert if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) { 1232*a90b9d01SCy Schubert info->common_info.eml_capa = WPA_GET_LE16(pos); 1233*a90b9d01SCy Schubert pos += 2; 1234*a90b9d01SCy Schubert } else { 1235*a90b9d01SCy Schubert info->common_info.eml_capa = 0; 1236*a90b9d01SCy Schubert } 1237*a90b9d01SCy Schubert 1238*a90b9d01SCy Schubert info->common_info.mld_capa = WPA_GET_LE16(pos); 1239*a90b9d01SCy Schubert pos += 2; 1240*a90b9d01SCy Schubert 1241*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: addr=" MACSTR ", eml=0x%x, mld=0x%x", 1242*a90b9d01SCy Schubert MAC2STR(info->common_info.mld_addr), 1243*a90b9d01SCy Schubert info->common_info.eml_capa, info->common_info.mld_capa); 1244*a90b9d01SCy Schubert 1245*a90b9d01SCy Schubert /* Check the MLD MAC Address */ 1246*a90b9d01SCy Schubert if (!ether_addr_equal(info->common_info.mld_addr, 1247*a90b9d01SCy Schubert common_info->mld_addr)) { 1248*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1249*a90b9d01SCy Schubert "MLD: MLD address mismatch between authentication (" 1250*a90b9d01SCy Schubert MACSTR ") and association (" MACSTR ")", 1251*a90b9d01SCy Schubert MAC2STR(info->common_info.mld_addr), 1252*a90b9d01SCy Schubert MAC2STR(common_info->mld_addr)); 1253*a90b9d01SCy Schubert goto out; 1254*a90b9d01SCy Schubert } 1255*a90b9d01SCy Schubert 1256*a90b9d01SCy Schubert info->links[hapd->mld_link_id].valid = 1; 1257*a90b9d01SCy Schubert 1258*a90b9d01SCy Schubert /* Parse the link info field */ 1259*a90b9d01SCy Schubert ml_len -= sizeof(*ml) + common_info_len; 1260*a90b9d01SCy Schubert 1261*a90b9d01SCy Schubert while (ml_len > 2) { 1262*a90b9d01SCy Schubert size_t sub_elem_len = *(pos + 1); 1263*a90b9d01SCy Schubert size_t sta_info_len; 1264*a90b9d01SCy Schubert u16 control; 1265*a90b9d01SCy Schubert 1266*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: sub element len=%zu", 1267*a90b9d01SCy Schubert sub_elem_len); 1268*a90b9d01SCy Schubert 1269*a90b9d01SCy Schubert if (2 + sub_elem_len > ml_len) { 1270*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1271*a90b9d01SCy Schubert "MLD: Invalid link info len: %zu %zu", 1272*a90b9d01SCy Schubert 2 + sub_elem_len, ml_len); 1273*a90b9d01SCy Schubert goto out; 1274*a90b9d01SCy Schubert } 1275*a90b9d01SCy Schubert 1276*a90b9d01SCy Schubert if (*pos == MULTI_LINK_SUB_ELEM_ID_VENDOR) { 1277*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1278*a90b9d01SCy Schubert "MLD: Skip vendor specific subelement"); 1279*a90b9d01SCy Schubert 1280*a90b9d01SCy Schubert pos += 2 + sub_elem_len; 1281*a90b9d01SCy Schubert ml_len -= 2 + sub_elem_len; 1282*a90b9d01SCy Schubert continue; 1283*a90b9d01SCy Schubert } 1284*a90b9d01SCy Schubert 1285*a90b9d01SCy Schubert if (*pos != MULTI_LINK_SUB_ELEM_ID_PER_STA_PROFILE) { 1286*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1287*a90b9d01SCy Schubert "MLD: Skip unknown Multi-Link element subelement ID=%u", 1288*a90b9d01SCy Schubert *pos); 1289*a90b9d01SCy Schubert pos += 2 + sub_elem_len; 1290*a90b9d01SCy Schubert ml_len -= 2 + sub_elem_len; 1291*a90b9d01SCy Schubert continue; 1292*a90b9d01SCy Schubert } 1293*a90b9d01SCy Schubert 1294*a90b9d01SCy Schubert /* Skip the subelement ID and the length */ 1295*a90b9d01SCy Schubert pos += 2; 1296*a90b9d01SCy Schubert ml_len -= 2; 1297*a90b9d01SCy Schubert 1298*a90b9d01SCy Schubert /* Get the station control field */ 1299*a90b9d01SCy Schubert if (sub_elem_len < 2) { 1300*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1301*a90b9d01SCy Schubert "MLD: Too short Per-STA Profile subelement"); 1302*a90b9d01SCy Schubert goto out; 1303*a90b9d01SCy Schubert } 1304*a90b9d01SCy Schubert control = WPA_GET_LE16(pos); 1305*a90b9d01SCy Schubert link_info = &info->links[control & 1306*a90b9d01SCy Schubert EHT_PER_STA_CTRL_LINK_ID_MSK]; 1307*a90b9d01SCy Schubert pos += 2; 1308*a90b9d01SCy Schubert ml_len -= 2; 1309*a90b9d01SCy Schubert sub_elem_len -= 2; 1310*a90b9d01SCy Schubert 1311*a90b9d01SCy Schubert if (!(control & EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK)) { 1312*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1313*a90b9d01SCy Schubert "MLD: Per-STA complete profile expected"); 1314*a90b9d01SCy Schubert goto out; 1315*a90b9d01SCy Schubert } 1316*a90b9d01SCy Schubert 1317*a90b9d01SCy Schubert if (!(control & EHT_PER_STA_CTRL_MAC_ADDR_PRESENT_MSK)) { 1318*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1319*a90b9d01SCy Schubert "MLD: Per-STA MAC address not present"); 1320*a90b9d01SCy Schubert goto out; 1321*a90b9d01SCy Schubert } 1322*a90b9d01SCy Schubert 1323*a90b9d01SCy Schubert if ((control & (EHT_PER_STA_CTRL_BEACON_INTERVAL_PRESENT_MSK | 1324*a90b9d01SCy Schubert EHT_PER_STA_CTRL_DTIM_INFO_PRESENT_MSK))) { 1325*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1326*a90b9d01SCy Schubert "MLD: Beacon/DTIM interval not expected"); 1327*a90b9d01SCy Schubert goto out; 1328*a90b9d01SCy Schubert } 1329*a90b9d01SCy Schubert 1330*a90b9d01SCy Schubert /* The length octet and the MAC address must be present */ 1331*a90b9d01SCy Schubert sta_info_len = 1 + ETH_ALEN; 1332*a90b9d01SCy Schubert 1333*a90b9d01SCy Schubert if (control & EHT_PER_STA_CTRL_NSTR_LINK_PAIR_PRESENT_MSK) { 1334*a90b9d01SCy Schubert if (control & EHT_PER_STA_CTRL_NSTR_BM_SIZE_MSK) 1335*a90b9d01SCy Schubert link_info->nstr_bitmap_len = 2; 1336*a90b9d01SCy Schubert else 1337*a90b9d01SCy Schubert link_info->nstr_bitmap_len = 1; 1338*a90b9d01SCy Schubert } 1339*a90b9d01SCy Schubert 1340*a90b9d01SCy Schubert sta_info_len += link_info->nstr_bitmap_len; 1341*a90b9d01SCy Schubert 1342*a90b9d01SCy Schubert if (sta_info_len > ml_len || sta_info_len != *pos || 1343*a90b9d01SCy Schubert sta_info_len > sub_elem_len) { 1344*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: Invalid STA Info length"); 1345*a90b9d01SCy Schubert goto out; 1346*a90b9d01SCy Schubert } 1347*a90b9d01SCy Schubert 1348*a90b9d01SCy Schubert /* skip the length */ 1349*a90b9d01SCy Schubert pos++; 1350*a90b9d01SCy Schubert ml_len--; 1351*a90b9d01SCy Schubert 1352*a90b9d01SCy Schubert /* get the link address */ 1353*a90b9d01SCy Schubert os_memcpy(link_info->peer_addr, pos, ETH_ALEN); 1354*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, 1355*a90b9d01SCy Schubert "MLD: assoc: link id=%u, addr=" MACSTR, 1356*a90b9d01SCy Schubert control & EHT_PER_STA_CTRL_LINK_ID_MSK, 1357*a90b9d01SCy Schubert MAC2STR(link_info->peer_addr)); 1358*a90b9d01SCy Schubert 1359*a90b9d01SCy Schubert pos += ETH_ALEN; 1360*a90b9d01SCy Schubert ml_len -= ETH_ALEN; 1361*a90b9d01SCy Schubert 1362*a90b9d01SCy Schubert /* Get the NSTR bitmap */ 1363*a90b9d01SCy Schubert if (link_info->nstr_bitmap_len) { 1364*a90b9d01SCy Schubert os_memcpy(link_info->nstr_bitmap, pos, 1365*a90b9d01SCy Schubert link_info->nstr_bitmap_len); 1366*a90b9d01SCy Schubert pos += link_info->nstr_bitmap_len; 1367*a90b9d01SCy Schubert ml_len -= link_info->nstr_bitmap_len; 1368*a90b9d01SCy Schubert } 1369*a90b9d01SCy Schubert 1370*a90b9d01SCy Schubert sub_elem_len -= sta_info_len; 1371*a90b9d01SCy Schubert 1372*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: STA Profile len=%zu", sub_elem_len); 1373*a90b9d01SCy Schubert if (sub_elem_len > ml_len) 1374*a90b9d01SCy Schubert goto out; 1375*a90b9d01SCy Schubert 1376*a90b9d01SCy Schubert if (sub_elem_len > 2) 1377*a90b9d01SCy Schubert link_info->capability = WPA_GET_LE16(pos); 1378*a90b9d01SCy Schubert 1379*a90b9d01SCy Schubert pos += sub_elem_len; 1380*a90b9d01SCy Schubert ml_len -= sub_elem_len; 1381*a90b9d01SCy Schubert 1382*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: link ctrl=0x%x, " MACSTR 1383*a90b9d01SCy Schubert ", nstr bitmap len=%u", 1384*a90b9d01SCy Schubert control, MAC2STR(link_info->peer_addr), 1385*a90b9d01SCy Schubert link_info->nstr_bitmap_len); 1386*a90b9d01SCy Schubert 1387*a90b9d01SCy Schubert link_info->valid = true; 1388*a90b9d01SCy Schubert } 1389*a90b9d01SCy Schubert 1390*a90b9d01SCy Schubert if (ml_len) { 1391*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "MLD: %zu bytes left after parsing. fail", 1392*a90b9d01SCy Schubert ml_len); 1393*a90b9d01SCy Schubert goto out; 1394*a90b9d01SCy Schubert } 1395*a90b9d01SCy Schubert 1396*a90b9d01SCy Schubert ret = hostapd_mld_validate_assoc_info(hapd, sta); 1397*a90b9d01SCy Schubert out: 1398*a90b9d01SCy Schubert wpabuf_free(mlbuf); 1399*a90b9d01SCy Schubert if (ret) { 1400*a90b9d01SCy Schubert os_memset(info, 0, sizeof(*info)); 1401*a90b9d01SCy Schubert return WLAN_STATUS_UNSPECIFIED_FAILURE; 1402*a90b9d01SCy Schubert } 1403*a90b9d01SCy Schubert 1404*a90b9d01SCy Schubert return WLAN_STATUS_SUCCESS; 1405*a90b9d01SCy Schubert } 1406