*/
#define RNR_HEADER_LEN 2
#define RNR_TBTT_HEADER_LEN 4
+#define RNR_TBTT_INFO_HDR_TYPE_MSK 0x03
+#define RNR_TBTT_INFO_HDR_FILTERED_AP 0x04
+#define RNR_TBTT_INFO_HDR_CNT_MSK 0xf0
#define RNR_TBTT_INFO_COUNT(x) (((x) & 0xf) << 4)
#define RNR_TBTT_INFO_COUNT_MAX 16
+#define RNR_TBTT_INFO_COUNT_VAL(x) (((x) & 0xf0) >> 4)
#define RNR_TBTT_INFO_LEN 13
#define RNR_TBTT_INFO_MLD_LEN 16
#define RNR_NEIGHBOR_AP_OFFSET_UNKNOWN 255
if (bss == wpa_s->current_bss)
return 1;
+ if (bss == wpa_s->ml_connect_probe_bss)
+ return 1;
+
if (wpa_s->current_bss &&
(bss->ssid_len != wpa_s->current_bss->ssid_len ||
os_memcmp(bss->ssid, wpa_s->current_bss->ssid,
break;
}
}
+
if (wpa_s->current_bss == bss)
wpa_s->current_bss = nbss;
+
+ if (wpa_s->ml_connect_probe_bss == bss)
+ wpa_s->ml_connect_probe_bss = nbss;
+
wpa_bss_update_pending_connect(wpa_s, bss, nbss);
bss = nbss;
os_memcpy(bss->ies, res + 1,
return ieee802_11_defrag_mle(&elems, type);
}
+
+
+static void
+wpa_bss_parse_ml_rnr_ap_info(struct wpa_supplicant *wpa_s,
+ struct wpa_bss *bss, u8 mbssid_idx,
+ const struct ieee80211_neighbor_ap_info *ap_info,
+ size_t len, u16 *seen, u16 *missing)
+{
+ const u8 *pos, *end;
+ const u8 *mld_params;
+ u8 count, mld_params_offset;
+ u8 i, type, link_id;
+
+ count = RNR_TBTT_INFO_COUNT_VAL(ap_info->tbtt_info_hdr) + 1;
+ type = ap_info->tbtt_info_hdr & RNR_TBTT_INFO_HDR_TYPE_MSK;
+
+ /* MLD information is at offset 13 or at start */
+ if (type == 0 && ap_info->tbtt_info_len >= RNR_TBTT_INFO_MLD_LEN) {
+ /* MLD info is appended */
+ mld_params_offset = RNR_TBTT_INFO_LEN;
+ } else {
+ /* TODO: Support NSTR AP */
+ return;
+ }
+
+ pos = (const u8 *) ap_info;
+ end = pos + len;
+ pos += sizeof(*ap_info);
+
+ for (i = 0; i < count; i++) {
+ if (bss->n_mld_links >= MAX_NUM_MLD_LINKS)
+ return;
+
+ if (end - pos < ap_info->tbtt_info_len)
+ break;
+
+ mld_params = pos + mld_params_offset;
+
+ link_id = *(mld_params + 1) & EHT_ML_LINK_ID_MSK;
+
+ if (*mld_params != mbssid_idx) {
+ wpa_printf(MSG_DEBUG,
+ "MLD: Reported link not part of MLD");
+ } else if (!(BIT(link_id) & *seen)) {
+ struct wpa_bss *neigh_bss =
+ wpa_bss_get_bssid(wpa_s, ap_info->data + 1);
+
+ *seen |= BIT(link_id);
+ wpa_printf(MSG_DEBUG, "MLD: mld ID=%u, link ID=%u",
+ *mld_params, link_id);
+
+ if (neigh_bss) {
+ struct mld_link *l;
+
+ l = &bss->mld_links[bss->n_mld_links];
+ l->link_id = link_id;
+ os_memcpy(l->bssid, ap_info->data + 1,
+ ETH_ALEN);
+ l->freq = neigh_bss->freq;
+ bss->n_mld_links++;
+ } else {
+ *missing |= BIT(link_id);
+ }
+ }
+
+ pos += ap_info->tbtt_info_len;
+ }
+}
+
+
+/**
+ * wpa_bss_parse_basic_ml_element - Parse the Basic Multi-Link element
+ * @wpa_s: Pointer to wpa_supplicant data
+ * @bss: BSS table entry
+ * @mld_addr: AP MLD address (or %NULL)
+ * @link_info: Array to store link information (or %NULL),
+ * should be initialized and #MAX_NUM_MLD_LINKS elements long
+ * @missing_links: Result bitmask of links that were not discovered (or %NULL)
+ * Returns: 0 on success or -1 for non-MLD or parsing failures
+ *
+ * Parses the Basic Multi-Link element of the BSS into @link_info using the scan
+ * information stored in the wpa_supplicant data to fill in information for
+ * links where possible. The @missing_links out parameter will contain any links
+ * for which no corresponding BSS was found.
+ */
+int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
+ struct wpa_bss *bss,
+ u8 *ap_mld_addr,
+ u16 *missing_links)
+{
+ struct ieee802_11_elems elems;
+ struct wpabuf *mlbuf;
+ const struct element *elem;
+ u8 mbssid_idx = 0;
+ u8 ml_ie_len;
+ const struct ieee80211_eht_ml *eht_ml;
+ const struct eht_ml_basic_common_info *ml_basic_common_info;
+ u8 i, link_id;
+ const u16 control_mask =
+ MULTI_LINK_CONTROL_TYPE_MASK |
+ BASIC_MULTI_LINK_CTRL_PRES_LINK_ID |
+ BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT |
+ BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA;
+ const u16 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_MLD_CAPA;
+ u16 missing = 0;
+ u16 seen;
+ const u8 *ies_pos = wpa_bss_ie_ptr(bss);
+ size_t ies_len = bss->ie_len ? bss->ie_len : bss->beacon_ie_len;
+ int ret = -1;
+ struct mld_link *l;
+
+ if (ieee802_11_parse_elems(ies_pos, ies_len, &elems, 1) ==
+ ParseFailed) {
+ wpa_dbg(wpa_s, MSG_DEBUG, "MLD: Failed to parse elements");
+ return ret;
+ }
+
+ mlbuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+ if (!mlbuf) {
+ wpa_dbg(wpa_s, MSG_DEBUG, "MLD: No Multi-Link element");
+ return ret;
+ }
+
+ ml_ie_len = wpabuf_len(mlbuf);
+
+ /*
+ * for ext ID + 2 control + common info len + MLD address +
+ * link info
+ */
+ if (ml_ie_len < 2UL + 1UL + ETH_ALEN + 1UL)
+ goto out;
+
+ eht_ml = (const struct ieee80211_eht_ml *) wpabuf_head(mlbuf);
+ if ((le_to_host16(eht_ml->ml_control) & control_mask) != control) {
+ wpa_printf(MSG_DEBUG,
+ "MLD: Unexpected Multi-Link element control=0x%x (mask 0x%x expected 0x%x)",
+ le_to_host16(eht_ml->ml_control), control_mask,
+ control);
+ goto out;
+ }
+
+ ml_basic_common_info =
+ (const struct eht_ml_basic_common_info *) eht_ml->variable;
+
+ /* Common info length should be valid */
+ if (ml_basic_common_info->len < ETH_ALEN + 1UL)
+ goto out;
+
+ /* Get the MLD address and MLD link ID */
+ if (ap_mld_addr)
+ os_memcpy(ap_mld_addr, ml_basic_common_info->mld_addr,
+ ETH_ALEN);
+
+ bss->n_mld_links = 0;
+ l = &bss->mld_links[bss->n_mld_links];
+ link_id = ml_basic_common_info->variable[0] & EHT_ML_LINK_ID_MSK;
+ l->link_id = link_id;
+ os_memcpy(l->bssid, bss->bssid, ETH_ALEN);
+ l->freq = bss->freq;
+
+ seen = BIT(link_id);
+ bss->n_mld_links++;
+
+ /*
+ * The AP MLD ID in the RNR corresponds to the MBSSID index, see
+ * IEEE P802.11be/D4.0, 9.4.2.169.2 (Neighbor AP Information field).
+ *
+ * For the transmitting BSSID it is clear that both the MBSSID index
+ * and the AP MLD ID in the RNR are zero.
+ *
+ * For nontransmitted BSSIDs we will have a BSS generated from the
+ * MBSSID element(s) using inheritance rules. Included in the elements
+ * is the MBSSID Index Element. The RNR is copied from the Beacon/Probe
+ * Response frame that was send by the transmitting BSSID. As such, the
+ * reported AP MLD ID in the RNR will match the value in the MBSSID
+ * Index Element.
+ */
+ elem = (const struct element *)
+ wpa_bss_get_ie(bss, WLAN_EID_MULTIPLE_BSSID_INDEX);
+ if (elem && elem->datalen >= 1)
+ mbssid_idx = elem->data[0];
+
+ for_each_element_id(elem, WLAN_EID_REDUCED_NEIGHBOR_REPORT,
+ wpa_bss_ie_ptr(bss),
+ bss->ie_len ? bss->ie_len : bss->beacon_ie_len) {
+ const struct ieee80211_neighbor_ap_info *ap_info;
+ const u8 *pos = elem->data;
+ size_t len = elem->datalen;
+
+ /* RNR IE may contain more than one Neighbor AP Info */
+ while (sizeof(*ap_info) <= len) {
+ size_t ap_info_len = sizeof(*ap_info);
+ u8 count;
+
+ ap_info = (const struct ieee80211_neighbor_ap_info *)
+ pos;
+ count = RNR_TBTT_INFO_COUNT_VAL(ap_info->tbtt_info_hdr) + 1;
+ ap_info_len += count * ap_info->tbtt_info_len;
+
+ if (ap_info_len > len)
+ goto out;
+
+ wpa_bss_parse_ml_rnr_ap_info(wpa_s, bss, mbssid_idx,
+ ap_info, len, &seen,
+ &missing);
+
+ pos += ap_info_len;
+ len -= ap_info_len;
+ }
+ }
+
+ wpa_printf(MSG_DEBUG, "MLD: n_mld_links=%u (unresolved: 0x%04hx)",
+ bss->n_mld_links, missing);
+
+ for (i = 0; i < bss->n_mld_links; i++) {
+ wpa_printf(MSG_DEBUG, "MLD: link=%u, bssid=" MACSTR,
+ bss->mld_links[i].link_id,
+ MAC2STR(bss->mld_links[i].bssid));
+ }
+
+ if (missing_links)
+ *missing_links = missing;
+
+ ret = 0;
+out:
+ wpabuf_free(mlbuf);
+ return ret;
+}
size_t beacon_ie_len;
/** MLD address of the AP */
u8 mld_addr[ETH_ALEN];
+
+ /** An array of MLD links */
+ u8 n_mld_links;
+ struct mld_link {
+ u8 link_id;
+ u8 bssid[ETH_ALEN];
+ int freq;
+ } mld_links[MAX_NUM_MLD_LINKS];
+
/* followed by ie_len octets of IEs */
/* followed by beacon_ie_len octets of IEs */
u8 ies[];
struct os_reltime *update_time);
struct wpabuf * wpa_bss_defrag_mle(const struct wpa_bss *bss, u8 type);
+int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
+ struct wpa_bss *bss,
+ u8 *ap_mld_addr,
+ u16 *missing_links);
#endif /* BSS_H */
}
+static bool ml_link_probe_scan(struct wpa_supplicant *wpa_s)
+{
+ if (!wpa_s->ml_connect_probe_ssid || !wpa_s->ml_connect_probe_bss)
+ return false;
+
+ wpa_msg(wpa_s, MSG_DEBUG,
+ "Request association with " MACSTR " after ML probe",
+ MAC2STR(wpa_s->ml_connect_probe_bss->bssid));
+
+ wpa_supplicant_associate(wpa_s, wpa_s->ml_connect_probe_bss,
+ wpa_s->ml_connect_probe_ssid);
+
+ wpa_s->ml_connect_probe_ssid = NULL;
+ wpa_s->ml_connect_probe_bss = NULL;
+
+ return true;
+}
+
+
+static int wpa_supplicant_connect_ml_missing(struct wpa_supplicant *wpa_s,
+ struct wpa_bss *selected,
+ struct wpa_ssid *ssid)
+{
+ int *freqs;
+ u16 missing_links = 0;
+
+ if (!((wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO) &&
+ (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME)))
+ return 0;
+
+ /* Try to resolve any missing link information */
+ if (wpa_bss_parse_basic_ml_element(wpa_s, selected, NULL,
+ &missing_links) || !missing_links)
+ return 0;
+
+ wpa_dbg(wpa_s, MSG_DEBUG,
+ "MLD: Doing an ML probe for missing links 0x%04x",
+ missing_links);
+
+ freqs = os_malloc(sizeof(int) * 2);
+ if (!freqs)
+ return 0;
+
+ wpa_s->ml_connect_probe_ssid = ssid;
+ wpa_s->ml_connect_probe_bss = selected;
+
+ freqs[0] = selected->freq;
+ freqs[1] = 0;
+
+ wpa_s->manual_scan_passive = 0;
+ wpa_s->manual_scan_use_id = 0;
+ wpa_s->manual_scan_only_new = 0;
+ wpa_s->scan_id_count = 0;
+ os_free(wpa_s->manual_scan_freqs);
+ wpa_s->manual_scan_freqs = freqs;
+
+ os_memcpy(wpa_s->ml_probe_bssid, selected->bssid, ETH_ALEN);
+ wpa_s->ml_probe_mld_id = -1;
+ wpa_s->ml_probe_links = missing_links;
+
+ wpa_s->normal_scans = 0;
+ wpa_s->scan_req = MANUAL_SCAN_REQ;
+ wpa_s->after_wps = 0;
+ wpa_s->known_wps_freq = 0;
+ wpa_supplicant_req_scan(wpa_s, 0, 0);
+
+ return 1;
+}
+
+
int wpa_supplicant_connect(struct wpa_supplicant *wpa_s,
struct wpa_bss *selected,
struct wpa_ssid *ssid)
wpa_supplicant_req_new_scan(wpa_s, 10, 0);
return 0;
}
+
+ if (wpa_supplicant_connect_ml_missing(wpa_s, selected, ssid))
+ return 0;
+
wpa_msg(wpa_s, MSG_DEBUG, "Request association with " MACSTR,
MAC2STR(selected->bssid));
wpa_supplicant_associate(wpa_s, selected, ssid);
wpas_beacon_rep_scan_process(wpa_s, scan_res, &data->scan_info) > 0)
goto scan_work_done;
+ if (ml_link_probe_scan(wpa_s))
+ goto scan_work_done;
+
if ((wpa_s->conf->ap_scan == 2 && !wpas_wps_searching(wpa_s)))
goto scan_work_done;
wpa_s->last_ssid = NULL;
if (wpa_s->current_ssid == ssid)
wpa_s->current_ssid = NULL;
+ if (wpa_s->ml_connect_probe_ssid == ssid) {
+ wpa_s->ml_connect_probe_ssid = NULL;
+ wpa_s->ml_connect_probe_bss = NULL;
+ }
#if defined(CONFIG_SME) && defined(CONFIG_SAE)
if (wpa_s->sme.ext_auth_wpa_ssid == ssid)
wpa_s->sme.ext_auth_wpa_ssid = NULL;
}
+static struct wpabuf * wpa_supplicant_ml_probe_ie(int mld_id, u16 links)
+{
+ struct wpabuf *extra_ie;
+ u16 control = MULTI_LINK_CONTROL_TYPE_PROBE_REQ;
+ size_t len = 3 + 4 + 4 * MAX_NUM_MLD_LINKS;
+ u8 link_id;
+ u8 *len_pos;
+
+ if (mld_id >= 0) {
+ control |= EHT_ML_PRES_BM_PROBE_REQ_AP_MLD_ID;
+ len++;
+ }
+
+ extra_ie = wpabuf_alloc(len);
+ if (!extra_ie)
+ return NULL;
+
+ wpabuf_put_u8(extra_ie, WLAN_EID_EXTENSION);
+ len_pos = wpabuf_put(extra_ie, 1);
+ wpabuf_put_u8(extra_ie, WLAN_EID_EXT_MULTI_LINK);
+
+ wpabuf_put_le16(extra_ie, control);
+
+ /* common info length and MLD ID (if requested) */
+ if (mld_id >= 0) {
+ wpabuf_put_u8(extra_ie, 2);
+ wpabuf_put_u8(extra_ie, mld_id);
+
+ wpa_printf(MSG_DEBUG, "MLD: ML probe targeted at MLD ID %d",
+ mld_id);
+ } else {
+ wpabuf_put_u8(extra_ie, 1);
+
+ wpa_printf(MSG_DEBUG, "MLD: ML probe targeted at receiving AP");
+ }
+
+ if (!links)
+ wpa_printf(MSG_DEBUG, "MLD: Probing all links");
+ else
+ wpa_printf(MSG_DEBUG, "MLD: Probing links 0x%04x", links);
+
+ for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+ if (!(links & BIT(link_id)))
+ continue;
+
+ wpabuf_put_u8(extra_ie, EHT_ML_SUB_ELEM_PER_STA_PROFILE);
+
+ /* Subelement length includes only the control */
+ wpabuf_put_u8(extra_ie, 2);
+
+ control = link_id | EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK;
+
+ wpabuf_put_le16(extra_ie, control);
+ }
+
+ *len_pos = (u8 *) wpabuf_put(extra_ie, 0) - len_pos - 1;
+
+ return extra_ie;
+}
+
+
static struct wpabuf * wpa_supplicant_extra_ies(struct wpa_supplicant *wpa_s)
{
struct wpabuf *extra_ie = NULL;
enum wps_request_type req_type = WPS_REQ_ENROLLEE_INFO;
#endif /* CONFIG_WPS */
+ if (!is_zero_ether_addr(wpa_s->ml_probe_bssid)) {
+ extra_ie = wpa_supplicant_ml_probe_ie(wpa_s->ml_probe_mld_id,
+ wpa_s->ml_probe_links);
+
+ /* No other elements should be included in the probe request */
+ wpa_printf(MSG_DEBUG, "MLD: Scan including only ML element");
+ return extra_ie;
+ }
+
#ifdef CONFIG_P2P
if (wpa_s->p2p_group_interface == P2P_GROUP_INTERFACE_CLIENT)
wpa_drv_get_ext_capa(wpa_s, WPA_IF_P2P_CLIENT);
"Scan a previously specified BSSID " MACSTR,
MAC2STR(params.bssid));
}
+ } else if (!is_zero_ether_addr(wpa_s->ml_probe_bssid)) {
+ wpa_printf(MSG_DEBUG, "Scanning for ML probe request");
+ params.bssid = wpa_s->ml_probe_bssid;
+ params.min_probe_req_content = true;
}
+
if (wpa_s->last_scan_req == MANUAL_SCAN_REQ &&
wpa_s->manual_non_coloc_6ghz) {
wpa_dbg(wpa_s, MSG_DEBUG, "Collocated 6 GHz logic is disabled");
if (params.bssid)
os_memset(wpa_s->next_scan_bssid, 0, ETH_ALEN);
}
+
+ wpa_s->ml_probe_mld_id = -1;
+ wpa_s->ml_probe_links = 0;
+ os_memset(wpa_s->ml_probe_bssid, 0, sizeof(wpa_s->ml_probe_bssid));
}
{
struct wpa_ssid *old_ssid;
+ wpa_s->ml_connect_probe_ssid = NULL;
+ wpa_s->ml_connect_probe_bss = NULL;
wpas_connect_work_done(wpa_s);
wpa_clear_keys(wpa_s, addr);
old_ssid = wpa_s->current_ssid;
dl_list_init(&wpa_s->drv_signal_override);
#endif /* CONFIG_TESTING_OPTIONS */
dl_list_init(&wpa_s->active_scs_ids);
+ wpa_s->ml_probe_mld_id = -1;
return wpa_s;
}
unsigned int suitable_network;
unsigned int no_suitable_network;
+ u8 ml_probe_bssid[ETH_ALEN];
+ int ml_probe_mld_id;
+ u16 ml_probe_links;
+
u64 drv_flags;
u64 drv_flags2;
unsigned int drv_enc;
unsigned int wait_for_dscp_req:1;
struct wpa_signal_info last_signal_info;
+
+ struct wpa_ssid *ml_connect_probe_ssid;
+ struct wpa_bss *ml_connect_probe_bss;
};