aboutsummaryrefslogtreecommitdiff
path: root/src/common/ieee802_11_common.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/ieee802_11_common.c')
-rw-r--r--src/common/ieee802_11_common.c900
1 files changed, 765 insertions, 135 deletions
diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c
index 44335de8..cd1b198f 100644
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -199,11 +199,76 @@ static int ieee802_11_parse_vendor_specific(const u8 *pos, size_t elen,
}
+static int ieee802_11_parse_mle(const u8 *pos, size_t elen, size_t **total_len,
+ struct ieee802_11_elems *elems,
+ int show_errors)
+{
+ u8 mle_type = pos[0] & MULTI_LINK_CONTROL_TYPE_MASK;
+
+ switch (mle_type) {
+ case MULTI_LINK_CONTROL_TYPE_BASIC:
+ elems->basic_mle = pos;
+ elems->basic_mle_len = elen;
+ *total_len = &elems->basic_mle_len;
+ break;
+ case MULTI_LINK_CONTROL_TYPE_PROBE_REQ:
+ elems->probe_req_mle = pos;
+ elems->probe_req_mle_len = elen;
+ *total_len = &elems->probe_req_mle_len;
+ break;
+ case MULTI_LINK_CONTROL_TYPE_RECONF:
+ elems->reconf_mle = pos;
+ elems->reconf_mle_len = elen;
+ *total_len = &elems->reconf_mle_len;
+ break;
+ case MULTI_LINK_CONTROL_TYPE_TDLS:
+ elems->tdls_mle = pos;
+ elems->tdls_mle_len = elen;
+ *total_len = &elems->tdls_mle_len;
+ break;
+ case MULTI_LINK_CONTROL_TYPE_PRIOR_ACCESS:
+ elems->prior_access_mle = pos;
+ elems->prior_access_mle_len = elen;
+ *total_len = &elems->prior_access_mle_len;
+ break;
+ default:
+ if (show_errors) {
+ wpa_printf(MSG_MSGDUMP,
+ "Unknown Multi-Link element type %u",
+ mle_type);
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static size_t ieee802_11_fragments_length(struct ieee802_11_elems *elems,
+ const u8 *start, size_t len)
+{
+ const struct element *elem;
+ size_t frags_len = 0;
+
+ for_each_element(elem, start, len) {
+ if (elem->id != WLAN_EID_FRAGMENT)
+ break;
+
+ frags_len += elem->datalen + 2;
+ elems->num_frag_elems++;
+ }
+
+ return frags_len;
+}
+
+
static int ieee802_11_parse_extension(const u8 *pos, size_t elen,
struct ieee802_11_elems *elems,
+ const u8 *start, size_t len,
int show_errors)
{
u8 ext_id;
+ size_t *total_len = NULL;
if (elen < 1) {
if (show_errors) {
@@ -216,8 +281,6 @@ static int ieee802_11_parse_extension(const u8 *pos, size_t elen,
ext_id = *pos++;
elen--;
- elems->frag_ies.last_eid_ext = 0;
-
switch (ext_id) {
case WLAN_EID_EXT_ASSOC_DELAY_INFO:
if (elen != 1)
@@ -244,6 +307,7 @@ static int ieee802_11_parse_extension(const u8 *pos, size_t elen,
break;
elems->fils_hlp = pos;
elems->fils_hlp_len = elen;
+ total_len = &elems->fils_hlp_len;
break;
case WLAN_EID_EXT_FILS_IP_ADDR_ASSIGN:
if (elen < 1)
@@ -260,6 +324,7 @@ static int ieee802_11_parse_extension(const u8 *pos, size_t elen,
case WLAN_EID_EXT_WRAPPED_DATA:
elems->wrapped_data = pos;
elems->wrapped_data_len = elen;
+ total_len = &elems->wrapped_data_len;
break;
case WLAN_EID_EXT_FILS_PUBLIC_KEY:
if (elen < 1)
@@ -312,13 +377,28 @@ static int ieee802_11_parse_extension(const u8 *pos, size_t elen,
elems->pasn_params_len = elen;
break;
case WLAN_EID_EXT_EHT_CAPABILITIES:
+ if (elen < EHT_CAPABILITIES_IE_MIN_LEN)
+ break;
elems->eht_capabilities = pos;
elems->eht_capabilities_len = elen;
break;
case WLAN_EID_EXT_EHT_OPERATION:
+ if (elen < EHT_OPERATION_IE_MIN_LEN)
+ break;
elems->eht_operation = pos;
elems->eht_operation_len = elen;
break;
+ case WLAN_EID_EXT_MULTI_LINK:
+ if (elen < 2)
+ break;
+ if (ieee802_11_parse_mle(pos, elen, &total_len, elems,
+ show_errors))
+ return -1;
+ break;
+ case WLAN_EID_EXT_KNOWN_BSSID:
+ elems->mbssid_known_bss = pos;
+ elems->mbssid_known_bss_len = elen;
+ break;
default:
if (show_errors) {
wpa_printf(MSG_MSGDUMP,
@@ -328,56 +408,21 @@ static int ieee802_11_parse_extension(const u8 *pos, size_t elen,
return -1;
}
- if (elen == 254)
- elems->frag_ies.last_eid_ext = ext_id;
+ if (elen == 254 && total_len)
+ *total_len += ieee802_11_fragments_length(
+ elems, pos + elen, (start + len) - (pos + elen));
return 0;
}
-static void ieee802_11_parse_fragment(struct frag_ies_info *frag_ies,
- const u8 *pos, u8 elen)
-{
- if (frag_ies->n_frags >= MAX_NUM_FRAG_IES_SUPPORTED) {
- wpa_printf(MSG_MSGDUMP, "Too many element fragments - skip");
- return;
- }
-
- /*
- * Note: while EID == 0 is a valid ID (SSID IE), it should not be
- * fragmented.
- */
- if (!frag_ies->last_eid) {
- wpa_printf(MSG_MSGDUMP,
- "Fragment without a valid last element - skip");
- return;
- }
-
- frag_ies->frags[frag_ies->n_frags].ie = pos;
- frag_ies->frags[frag_ies->n_frags].ie_len = elen;
- frag_ies->frags[frag_ies->n_frags].eid = frag_ies->last_eid;
- frag_ies->frags[frag_ies->n_frags].eid_ext = frag_ies->last_eid_ext;
- frag_ies->n_frags++;
-}
-
-
-/**
- * ieee802_11_parse_elems - Parse information elements in management frames
- * @start: Pointer to the start of IEs
- * @len: Length of IE buffer in octets
- * @elems: Data structure for parsed elements
- * @show_errors: Whether to show parsing errors in debug log
- * Returns: Parsing result
- */
-ParseRes ieee802_11_parse_elems(const u8 *start, size_t len,
- struct ieee802_11_elems *elems,
- int show_errors)
+static ParseRes __ieee802_11_parse_elems(const u8 *start, size_t len,
+ struct ieee802_11_elems *elems,
+ int show_errors)
{
const struct element *elem;
int unknown = 0;
- os_memset(elems, 0, sizeof(*elems));
-
if (!start)
return ParseOK;
@@ -385,6 +430,12 @@ ParseRes ieee802_11_parse_elems(const u8 *start, size_t len,
u8 id = elem->id, elen = elem->datalen;
const u8 *pos = elem->data;
+ if (id == WLAN_EID_FRAGMENT && elems->num_frag_elems > 0) {
+ elems->num_frag_elems--;
+ continue;
+ }
+ elems->num_frag_elems = 0;
+
switch (id) {
case WLAN_EID_SSID:
if (elen > SSID_MAX_LEN) {
@@ -588,11 +639,13 @@ ParseRes ieee802_11_parse_elems(const u8 *start, size_t len,
elems->s1g_capab = pos;
break;
case WLAN_EID_FRAGMENT:
- ieee802_11_parse_fragment(&elems->frag_ies, pos, elen);
+ wpa_printf(MSG_MSGDUMP,
+ "Fragment without a valid last element - skip");
+
break;
case WLAN_EID_EXTENSION:
- if (ieee802_11_parse_extension(pos, elen, elems,
- show_errors))
+ if (ieee802_11_parse_extension(pos, elen, elems, start,
+ len, show_errors))
unknown++;
break;
default:
@@ -604,12 +657,6 @@ ParseRes ieee802_11_parse_elems(const u8 *start, size_t len,
id, elen);
break;
}
-
- if (id != WLAN_EID_FRAGMENT && elen == 255)
- elems->frag_ies.last_eid = id;
-
- if (id == WLAN_EID_EXTENSION && !elems->frag_ies.last_eid_ext)
- elems->frag_ies.last_eid = 0;
}
if (!for_each_element_completed(elem, start, len)) {
@@ -627,6 +674,431 @@ done:
}
+/**
+ * ieee802_11_parse_elems - Parse information elements in management frames
+ * @start: Pointer to the start of IEs
+ * @len: Length of IE buffer in octets
+ * @elems: Data structure for parsed elements
+ * @show_errors: Whether to show parsing errors in debug log
+ * Returns: Parsing result
+ */
+ParseRes ieee802_11_parse_elems(const u8 *start, size_t len,
+ struct ieee802_11_elems *elems,
+ int show_errors)
+{
+ os_memset(elems, 0, sizeof(*elems));
+
+ return __ieee802_11_parse_elems(start, len, elems, show_errors);
+}
+
+
+/**
+ * ieee802_11_elems_clear_ids - Clear the data for the given element IDs
+ * @ids: Array of element IDs for which data should be cleared.
+ * @num: The number of entries in the array
+ */
+void ieee802_11_elems_clear_ids(struct ieee802_11_elems *elems,
+ const u8 *ids, size_t num)
+{
+ size_t i;
+
+ for (i = 0; i < num; i++) {
+ switch (ids[i]) {
+ case WLAN_EID_SSID:
+ elems->ssid = NULL;
+ elems->ssid_len = 0;
+ break;
+ case WLAN_EID_SUPP_RATES:
+ elems->supp_rates = NULL;
+ elems->supp_rates_len = 0;
+ break;
+ case WLAN_EID_DS_PARAMS:
+ elems->ds_params = NULL;
+ break;
+ case WLAN_EID_CHALLENGE:
+ elems->challenge = NULL;
+ elems->challenge_len = 0;
+ break;
+ case WLAN_EID_ERP_INFO:
+ elems->erp_info = NULL;
+ break;
+ case WLAN_EID_EXT_SUPP_RATES:
+ elems->ext_supp_rates = NULL;
+ elems->ext_supp_rates_len = 0;
+ break;
+ case WLAN_EID_RSN:
+ elems->rsn_ie = NULL;
+ elems->rsn_ie_len = 0;
+ break;
+ case WLAN_EID_RSNX:
+ elems->rsnxe = NULL;
+ elems->rsnxe_len = 0;
+ break;
+ case WLAN_EID_PWR_CAPABILITY:
+ elems->power_capab = NULL;
+ elems->power_capab_len = 0;
+ break;
+ case WLAN_EID_SUPPORTED_CHANNELS:
+ elems->supp_channels = NULL;
+ elems->supp_channels_len = 0;
+ break;
+ case WLAN_EID_MOBILITY_DOMAIN:
+ elems->mdie = NULL;
+ elems->mdie_len = 0;
+ break;
+ case WLAN_EID_FAST_BSS_TRANSITION:
+ elems->ftie = NULL;
+ elems->ftie_len = 0;
+ break;
+ case WLAN_EID_TIMEOUT_INTERVAL:
+ elems->timeout_int = NULL;
+ break;
+ case WLAN_EID_HT_CAP:
+ elems->ht_capabilities = NULL;
+ break;
+ case WLAN_EID_HT_OPERATION:
+ elems->ht_operation = NULL;
+ break;
+ case WLAN_EID_MESH_CONFIG:
+ elems->mesh_config = NULL;
+ elems->mesh_config_len = 0;
+ break;
+ case WLAN_EID_MESH_ID:
+ elems->mesh_id = NULL;
+ elems->mesh_id_len = 0;
+ break;
+ case WLAN_EID_PEER_MGMT:
+ elems->peer_mgmt = NULL;
+ elems->peer_mgmt_len = 0;
+ break;
+ case WLAN_EID_VHT_CAP:
+ elems->vht_capabilities = NULL;
+ break;
+ case WLAN_EID_VHT_OPERATION:
+ elems->vht_operation = NULL;
+ break;
+ case WLAN_EID_VHT_OPERATING_MODE_NOTIFICATION:
+ elems->vht_opmode_notif = NULL;
+ break;
+ case WLAN_EID_LINK_ID:
+ elems->link_id = NULL;
+ break;
+ case WLAN_EID_INTERWORKING:
+ elems->interworking = NULL;
+ elems->interworking_len = 0;
+ break;
+ case WLAN_EID_QOS_MAP_SET:
+ elems->qos_map_set = NULL;
+ elems->qos_map_set_len = 0;
+ break;
+ case WLAN_EID_EXT_CAPAB:
+ elems->ext_capab = NULL;
+ elems->ext_capab_len = 0;
+ break;
+ case WLAN_EID_BSS_MAX_IDLE_PERIOD:
+ elems->bss_max_idle_period = NULL;
+ break;
+ case WLAN_EID_SSID_LIST:
+ elems->ssid_list = NULL;
+ elems->ssid_list_len = 0;
+ break;
+ case WLAN_EID_AMPE:
+ elems->ampe = NULL;
+ elems->ampe_len = 0;
+ break;
+ case WLAN_EID_MIC:
+ elems->mic = NULL;
+ elems->mic_len = 0;
+ break;
+ case WLAN_EID_MULTI_BAND:
+ os_memset(&elems->mb_ies, 0, sizeof(elems->mb_ies));
+ elems->mb_ies.nof_ies = 0;
+ break;
+ case WLAN_EID_SUPPORTED_OPERATING_CLASSES:
+ elems->supp_op_classes = NULL;
+ elems->supp_op_classes_len = 0;
+ break;
+ case WLAN_EID_RRM_ENABLED_CAPABILITIES:
+ elems->rrm_enabled = NULL;
+ elems->rrm_enabled_len = 0;
+ break;
+ case WLAN_EID_CAG_NUMBER:
+ elems->cag_number = NULL;
+ elems->cag_number_len = 0;
+ break;
+ case WLAN_EID_AP_CSN:
+ elems->ap_csn = NULL;
+ break;
+ case WLAN_EID_FILS_INDICATION:
+ elems->fils_indic = NULL;
+ elems->fils_indic_len = 0;
+ break;
+ case WLAN_EID_DILS:
+ elems->dils = NULL;
+ elems->dils_len = 0;
+ break;
+ case WLAN_EID_S1G_CAPABILITIES:
+ elems->s1g_capab = NULL;
+ break;
+ }
+ }
+}
+
+
+/**
+ * ieee802_11_elems_clear_ext_ids - Clear the data for the given element
+ * extension IDs
+ * @ids: Array of element extension IDs for which data should be cleared.
+ * @num: The number of entries in the array
+ */
+void ieee802_11_elems_clear_ext_ids(struct ieee802_11_elems *elems,
+ const u8 *ids, size_t num)
+{
+ size_t i;
+
+ for (i = 0; i < num; i++) {
+ switch (ids[i]) {
+ case WLAN_EID_EXT_ASSOC_DELAY_INFO:
+ elems->assoc_delay_info = NULL;
+ break;
+ case WLAN_EID_EXT_FILS_REQ_PARAMS:
+ elems->fils_req_params = NULL;
+ elems->fils_req_params_len = 0;
+ break;
+ case WLAN_EID_EXT_FILS_KEY_CONFIRM:
+ elems->fils_key_confirm = NULL;
+ elems->fils_key_confirm_len = 0;
+ break;
+ case WLAN_EID_EXT_FILS_SESSION:
+ elems->fils_session = NULL;
+ break;
+ case WLAN_EID_EXT_FILS_HLP_CONTAINER:
+ elems->fils_hlp = NULL;
+ elems->fils_hlp_len = 0;
+ break;
+ case WLAN_EID_EXT_FILS_IP_ADDR_ASSIGN:
+ elems->fils_ip_addr_assign = NULL;
+ elems->fils_ip_addr_assign_len = 0;
+ break;
+ case WLAN_EID_EXT_KEY_DELIVERY:
+ elems->key_delivery = NULL;
+ elems->key_delivery_len = 0;
+ break;
+ case WLAN_EID_EXT_WRAPPED_DATA:
+ elems->wrapped_data = NULL;
+ elems->wrapped_data_len = 0;
+ break;
+ case WLAN_EID_EXT_FILS_PUBLIC_KEY:
+ elems->fils_pk = NULL;
+ elems->fils_pk_len = 0;
+ break;
+ case WLAN_EID_EXT_FILS_NONCE:
+ elems->fils_nonce = NULL;
+ break;
+ case WLAN_EID_EXT_OWE_DH_PARAM:
+ elems->owe_dh = NULL;
+ elems->owe_dh_len = 0;
+ break;
+ case WLAN_EID_EXT_PASSWORD_IDENTIFIER:
+ elems->password_id = NULL;
+ elems->password_id_len = 0;
+ break;
+ case WLAN_EID_EXT_HE_CAPABILITIES:
+ elems->he_capabilities = NULL;
+ elems->he_capabilities_len = 0;
+ break;
+ case WLAN_EID_EXT_HE_OPERATION:
+ elems->he_operation = NULL;
+ elems->he_operation_len = 0;
+ break;
+ case WLAN_EID_EXT_OCV_OCI:
+ elems->oci = NULL;
+ elems->oci_len = 0;
+ break;
+ case WLAN_EID_EXT_SHORT_SSID_LIST:
+ elems->short_ssid_list = NULL;
+ elems->short_ssid_list_len = 0;
+ break;
+ case WLAN_EID_EXT_HE_6GHZ_BAND_CAP:
+ elems->he_6ghz_band_cap = NULL;
+ break;
+ case WLAN_EID_EXT_PASN_PARAMS:
+ elems->pasn_params = NULL;
+ elems->pasn_params_len = 0;
+ break;
+ case WLAN_EID_EXT_MULTI_LINK:
+ elems->basic_mle = NULL;
+ elems->probe_req_mle = NULL;
+ elems->reconf_mle = NULL;
+ elems->tdls_mle = NULL;
+ elems->prior_access_mle = NULL;
+
+ elems->basic_mle_len = 0;
+ elems->probe_req_mle_len = 0;
+ elems->reconf_mle_len = 0;
+ elems->tdls_mle_len = 0;
+ elems->prior_access_mle_len = 0;
+ break;
+ case WLAN_EID_EXT_EHT_CAPABILITIES:
+ elems->eht_capabilities = NULL;
+ elems->eht_capabilities_len = 0;
+ break;
+ case WLAN_EID_EXT_EHT_OPERATION:
+ elems->eht_operation = NULL;
+ elems->eht_operation_len = 0;
+ break;
+ }
+ }
+}
+
+
+ParseRes ieee802_11_parse_link_assoc_req(const u8 *start, size_t len,
+ struct ieee802_11_elems *elems,
+ struct wpabuf *mlbuf,
+ u8 link_id, bool show_errors)
+{
+ const struct ieee80211_eht_ml *ml;
+ const u8 *pos;
+ ParseRes res = ParseFailed;
+
+ pos = wpabuf_head(mlbuf);
+ len = wpabuf_len(mlbuf);
+
+ /* Must have control and common info length */
+ if (len < sizeof(*ml) + 1 || len < sizeof(*ml) + pos[sizeof(*ml)])
+ goto out;
+
+ ml = (const struct ieee80211_eht_ml *) pos;
+
+ /* As we are interested with the Per-STA profile, ignore other types */
+ if ((le_to_host16(ml->ml_control) & MULTI_LINK_CONTROL_TYPE_MASK) !=
+ MULTI_LINK_CONTROL_TYPE_BASIC)
+ goto out;
+
+ /* Skip the common info */
+ len -= sizeof(*ml) + pos[sizeof(*ml)];
+ pos += sizeof(*ml) + pos[sizeof(*ml)];
+
+ while (len > 2) {
+ size_t sub_elem_len = *(pos + 1);
+ size_t sta_info_len;
+ u16 link_info_control;
+ const u8 *non_inherit;
+
+ wpa_printf(MSG_DEBUG,
+ "MLD: sub element: len=%zu, sub_elem_len=%zu",
+ len, sub_elem_len);
+
+ if (2 + sub_elem_len > len) {
+ if (show_errors)
+ wpa_printf(MSG_DEBUG,
+ "MLD: error: len=%zu, sub_elem_len=%zu",
+ len, sub_elem_len);
+ goto out;
+ }
+
+ if (*pos != 0) {
+ pos += 2 + sub_elem_len;
+ len -= 2 + sub_elem_len;
+ continue;
+ }
+
+ if (sub_elem_len < 3) {
+ if (show_errors)
+ wpa_printf(MSG_DEBUG,
+ "MLD: error: sub_elem_len=%zu < 5",
+ sub_elem_len);
+ goto out;
+ }
+
+ link_info_control = WPA_GET_LE16(pos + 2);
+ if ((link_info_control & BASIC_MLE_STA_CTRL_LINK_ID_MASK) !=
+ link_id) {
+ pos += 2 + sub_elem_len;
+ len -= 2 + sub_elem_len;
+ continue;
+ }
+
+ sta_info_len = *(pos + 4);
+ if (sub_elem_len < sta_info_len + 3 || sta_info_len < 1) {
+ if (show_errors)
+ wpa_printf(MSG_DEBUG,
+ "MLD: error: sub_elem_len=%zu, sta_info_len=%zu",
+ sub_elem_len, sta_info_len);
+ goto out;
+ }
+
+ pos += sta_info_len + 4;
+ sub_elem_len -= sta_info_len + 2;
+
+ if (sub_elem_len < 2) {
+ if (show_errors)
+ wpa_printf(MSG_DEBUG,
+ "MLD: missing capability info");
+ goto out;
+ }
+
+ pos += 2;
+ sub_elem_len -= 2;
+
+ /* Handle non-inheritance */
+ non_inherit = get_ie_ext(pos, sub_elem_len,
+ WLAN_EID_EXT_NON_INHERITANCE);
+ if (non_inherit && non_inherit[1] > 1) {
+ u8 non_inherit_len = non_inherit[1] - 1;
+
+ /*
+ * Do not include the Non-Inheritance element when
+ * parsing below. It should be the last element in the
+ * subelement.
+ */
+ if (3U + non_inherit_len > sub_elem_len)
+ goto out;
+ sub_elem_len -= 3 + non_inherit_len;
+
+ /* Skip the ID, length and extension ID */
+ non_inherit += 3;
+
+ if (non_inherit_len < 1UL + non_inherit[0]) {
+ if (show_errors)
+ wpa_printf(MSG_DEBUG,
+ "MLD: Invalid inheritance");
+ goto out;
+ }
+
+ ieee802_11_elems_clear_ids(elems, &non_inherit[1],
+ non_inherit[0]);
+
+ non_inherit_len -= 1 + non_inherit[0];
+ non_inherit += 1 + non_inherit[0];
+
+ if (non_inherit_len < 1UL + non_inherit[0]) {
+ if (show_errors)
+ wpa_printf(MSG_DEBUG,
+ "MLD: Invalid inheritance");
+ goto out;
+ }
+
+ ieee802_11_elems_clear_ext_ids(elems, &non_inherit[1],
+ non_inherit[0]);
+ }
+
+ wpa_printf(MSG_DEBUG, "MLD: link: sub_elem_len=%zu",
+ sub_elem_len);
+
+ if (sub_elem_len)
+ res = __ieee802_11_parse_elems(pos, sub_elem_len,
+ elems, show_errors);
+ else
+ res = ParseOK;
+ break;
+ }
+
+out:
+ return res;
+}
+
+
int ieee802_11_ie_count(const u8 *ies, size_t ies_len)
{
const struct element *elem;
@@ -886,7 +1358,7 @@ enum hostapd_hw_mode ieee80211_freq_to_chan(int freq, u8 *channel)
{
u8 op_class;
- return ieee80211_freq_to_channel_ext(freq, 0, CHANWIDTH_USE_HT,
+ return ieee80211_freq_to_channel_ext(freq, 0, CONF_OPER_CHWIDTH_USE_HT,
&op_class, channel);
}
@@ -896,15 +1368,15 @@ enum hostapd_hw_mode ieee80211_freq_to_chan(int freq, u8 *channel)
* for HT40, VHT, and HE. DFS channels are not covered.
* @freq: Frequency (MHz) to convert
* @sec_channel: 0 = non-HT40, 1 = sec. channel above, -1 = sec. channel below
- * @chanwidth: VHT/EDMG channel width (CHANWIDTH_*)
+ * @chanwidth: VHT/EDMG/etc. channel width
* @op_class: Buffer for returning operating class
* @channel: Buffer for returning channel number
* Returns: hw_mode on success, NUM_HOSTAPD_MODES on failure
*/
-enum hostapd_hw_mode ieee80211_freq_to_channel_ext(unsigned int freq,
- int sec_channel,
- int chanwidth,
- u8 *op_class, u8 *channel)
+enum hostapd_hw_mode
+ieee80211_freq_to_channel_ext(unsigned int freq, int sec_channel,
+ enum oper_chan_width chanwidth,
+ u8 *op_class, u8 *channel)
{
u8 vht_opclass;
@@ -952,13 +1424,13 @@ enum hostapd_hw_mode ieee80211_freq_to_channel_ext(unsigned int freq,
}
switch (chanwidth) {
- case CHANWIDTH_80MHZ:
+ case CONF_OPER_CHWIDTH_80MHZ:
vht_opclass = 128;
break;
- case CHANWIDTH_160MHZ:
+ case CONF_OPER_CHWIDTH_160MHZ:
vht_opclass = 129;
break;
- case CHANWIDTH_80P80MHZ:
+ case CONF_OPER_CHWIDTH_80P80MHZ:
vht_opclass = 130;
break;
default:
@@ -1057,15 +1529,18 @@ enum hostapd_hw_mode ieee80211_freq_to_channel_ext(unsigned int freq,
return NUM_HOSTAPD_MODES;
switch (chanwidth) {
- case CHANWIDTH_80MHZ:
+ case CONF_OPER_CHWIDTH_80MHZ:
*op_class = 133;
break;
- case CHANWIDTH_160MHZ:
+ case CONF_OPER_CHWIDTH_160MHZ:
*op_class = 134;
break;
- case CHANWIDTH_80P80MHZ:
+ case CONF_OPER_CHWIDTH_80P80MHZ:
*op_class = 135;
break;
+ case CONF_OPER_CHWIDTH_320MHZ:
+ *op_class = 137;
+ break;
default:
if (sec_channel)
*op_class = 132;
@@ -1090,12 +1565,12 @@ enum hostapd_hw_mode ieee80211_freq_to_channel_ext(unsigned int freq,
return NUM_HOSTAPD_MODES;
switch (chanwidth) {
- case CHANWIDTH_USE_HT:
- case CHANWIDTH_2160MHZ:
+ case CONF_OPER_CHWIDTH_USE_HT:
+ case CONF_OPER_CHWIDTH_2160MHZ:
*channel = (freq - 56160) / 2160;
*op_class = 180;
break;
- case CHANWIDTH_4320MHZ:
+ case CONF_OPER_CHWIDTH_4320MHZ:
/* EDMG channels 9 - 13 */
if (freq > 56160 + 2160 * 5)
return NUM_HOSTAPD_MODES;
@@ -1103,7 +1578,7 @@ enum hostapd_hw_mode ieee80211_freq_to_channel_ext(unsigned int freq,
*channel = (freq - 56160) / 2160 + 8;
*op_class = 181;
break;
- case CHANWIDTH_6480MHZ:
+ case CONF_OPER_CHWIDTH_6480MHZ:
/* EDMG channels 17 - 20 */
if (freq > 56160 + 2160 * 4)
return NUM_HOSTAPD_MODES;
@@ -1111,7 +1586,7 @@ enum hostapd_hw_mode ieee80211_freq_to_channel_ext(unsigned int freq,
*channel = (freq - 56160) / 2160 + 16;
*op_class = 182;
break;
- case CHANWIDTH_8640MHZ:
+ case CONF_OPER_CHWIDTH_8640MHZ:
/* EDMG channels 25 - 27 */
if (freq > 56160 + 2160 * 3)
return NUM_HOSTAPD_MODES;
@@ -1140,28 +1615,31 @@ int ieee80211_chaninfo_to_channel(unsigned int freq, enum chan_width chanwidth,
case CHAN_WIDTH_20_NOHT:
case CHAN_WIDTH_20:
case CHAN_WIDTH_40:
- cw = CHANWIDTH_USE_HT;
+ cw = CONF_OPER_CHWIDTH_USE_HT;
break;
case CHAN_WIDTH_80:
- cw = CHANWIDTH_80MHZ;
+ cw = CONF_OPER_CHWIDTH_80MHZ;
break;
case CHAN_WIDTH_80P80:
- cw = CHANWIDTH_80P80MHZ;
+ cw = CONF_OPER_CHWIDTH_80P80MHZ;
break;
case CHAN_WIDTH_160:
- cw = CHANWIDTH_160MHZ;
+ cw = CONF_OPER_CHWIDTH_160MHZ;
break;
case CHAN_WIDTH_2160:
- cw = CHANWIDTH_2160MHZ;
+ cw = CONF_OPER_CHWIDTH_2160MHZ;
break;
case CHAN_WIDTH_4320:
- cw = CHANWIDTH_4320MHZ;
+ cw = CONF_OPER_CHWIDTH_4320MHZ;
break;
case CHAN_WIDTH_6480:
- cw = CHANWIDTH_6480MHZ;
+ cw = CONF_OPER_CHWIDTH_6480MHZ;
break;
case CHAN_WIDTH_8640:
- cw = CHANWIDTH_8640MHZ;
+ cw = CONF_OPER_CHWIDTH_8640MHZ;
+ break;
+ case CHAN_WIDTH_320:
+ cw = CONF_OPER_CHWIDTH_320MHZ;
break;
}
@@ -1263,8 +1741,9 @@ static int ieee80211_chan_to_freq_us(u8 op_class, u8 chan)
if (chan < 25 || chan > 29)
return -1;
return 56160 + 2160 * (chan - 24);
+ default:
+ return -1;
}
- return -1;
}
@@ -1313,8 +1792,9 @@ static int ieee80211_chan_to_freq_eu(u8 op_class, u8 chan)
if (chan != 25)
return -1;
return 56160 + 2160 * (chan - 24);
+ default:
+ return -1;
}
- return -1;
}
@@ -1369,8 +1849,9 @@ static int ieee80211_chan_to_freq_jp(u8 op_class, u8 chan)
if (chan != 25)
return -1;
return 56160 + 2160 * (chan - 24);
+ default:
+ return -1;
}
- return -1;
}
@@ -1395,8 +1876,9 @@ static int ieee80211_chan_to_freq_cn(u8 op_class, u8 chan)
if (chan < 149 || chan > 165)
return -1;
return 5000 + 5 * chan;
+ default:
+ return -1;
}
- return -1;
}
@@ -1458,6 +1940,7 @@ static int ieee80211_chan_to_freq_global(u8 op_class, u8 chan)
case 133: /* UHB channels, 80 MHz: 7, 23, 39.. */
case 134: /* UHB channels, 160 MHz: 15, 47, 79.. */
case 135: /* UHB channels, 80+80 MHz: 7, 23, 39.. */
+ case 137: /* UHB channels, 320 MHz: 31, 63, 95, 127, 159, 191 */
if (chan < 1 || chan > 233)
return -1;
return 5950 + chan * 5;
@@ -1481,8 +1964,9 @@ static int ieee80211_chan_to_freq_global(u8 op_class, u8 chan)
if (chan < 25 || chan > 29)
return -1;
return 56160 + 2160 * (chan - 24);
+ default:
+ return -1;
}
- return -1;
}
/**
@@ -1822,6 +2306,9 @@ const char * status2str(u16 status)
S2S(DENIED_HE_NOT_SUPPORTED)
S2S(SAE_HASH_TO_ELEMENT)
S2S(SAE_PK)
+ S2S(INVALID_PUBLIC_KEY)
+ S2S(PASN_BASE_AKMP_FAILED)
+ S2S(OCI_MISMATCH)
}
return "UNKNOWN";
#undef S2S
@@ -1911,11 +2398,14 @@ const struct oper_class_map global_op_class[] = {
/*
* IEEE Std 802.11ax-2021, Table E-4 actually talks about channel center
- * frequency index 42, 58, 106, 122, 138, 155, 171 with channel spacing
- * of 80 MHz, but currently use the following definition for simplicity
+ * frequency index for operation classes 128, 129, 130, 132, 133, 134,
+ * and 135, but currently use the lowest 20 MHz channel for simplicity
* (these center frequencies are not actual channels, which makes
- * wpas_p2p_verify_channel() fail). wpas_p2p_verify_80mhz() should take
- * care of removing invalid channels.
+ * wpas_p2p_verify_channel() fail).
+ * Specially for the operation class 136, it is also defined to use the
+ * channel center frequency index value, but it happens to be a 20 MHz
+ * channel and the channel number in the channel set would match the
+ * value in for the frequency center.
*/
{ HOSTAPD_MODE_IEEE80211A, 128, 36, 177, 4, BW80, P2P_SUPP },
{ HOSTAPD_MODE_IEEE80211A, 129, 36, 177, 4, BW160, P2P_SUPP },
@@ -2272,6 +2762,9 @@ int center_idx_to_bw_6ghz(u8 idx)
/* channels 15, 47, 79...*/
if ((idx & 0x1f) == 0xf)
return 3; /* 160 MHz */
+ /* channels 31, 63, 95, 127, 159, 191 */
+ if ((idx & 0x1f) == 0x1f && idx < 192)
+ return 4; /* 320 MHz */
return -1;
}
@@ -2294,7 +2787,7 @@ bool is_6ghz_freq(int freq)
bool is_6ghz_op_class(u8 op_class)
{
- return op_class >= 131 && op_class <= 136;
+ return op_class >= 131 && op_class <= 137;
}
@@ -2600,6 +3093,8 @@ int op_class_to_bandwidth(u8 op_class)
return 160;
case 136: /* UHB channels, 20 MHz: 2 */
return 20;
+ case 137: /* UHB channels, 320 MHz: 31, 63, 95, 127, 159, 191 */
+ return 320;
case 180: /* 60 GHz band, channels 1..8 */
return 2160;
case 181: /* 60 GHz band, EDMG CB2, channels 9..15 */
@@ -2608,103 +3103,111 @@ int op_class_to_bandwidth(u8 op_class)
return 6480;
case 183: /* 60 GHz band, EDMG CB4, channel 25..29 */
return 8640;
+ default:
+ return 20;
}
-
- return 20;
}
-int op_class_to_ch_width(u8 op_class)
+enum oper_chan_width op_class_to_ch_width(u8 op_class)
{
switch (op_class) {
case 81:
case 82:
- return CHANWIDTH_USE_HT;
+ return CONF_OPER_CHWIDTH_USE_HT;
case 83: /* channels 1..9; 40 MHz */
case 84: /* channels 5..13; 40 MHz */
- return CHANWIDTH_USE_HT;
+ return CONF_OPER_CHWIDTH_USE_HT;
case 115: /* channels 36,40,44,48; indoor only */
- return CHANWIDTH_USE_HT;
+ return CONF_OPER_CHWIDTH_USE_HT;
case 116: /* channels 36,44; 40 MHz; indoor only */
case 117: /* channels 40,48; 40 MHz; indoor only */
- return CHANWIDTH_USE_HT;
+ return CONF_OPER_CHWIDTH_USE_HT;
case 118: /* channels 52,56,60,64; dfs */
- return CHANWIDTH_USE_HT;
+ return CONF_OPER_CHWIDTH_USE_HT;
case 119: /* channels 52,60; 40 MHz; dfs */
case 120: /* channels 56,64; 40 MHz; dfs */
- return CHANWIDTH_USE_HT;
+ return CONF_OPER_CHWIDTH_USE_HT;
case 121: /* channels 100-140 */
- return CHANWIDTH_USE_HT;
+ return CONF_OPER_CHWIDTH_USE_HT;
case 122: /* channels 100-142; 40 MHz */
case 123: /* channels 104-136; 40 MHz */
- return CHANWIDTH_USE_HT;
+ return CONF_OPER_CHWIDTH_USE_HT;
case 124: /* channels 149,153,157,161 */
case 125: /* channels 149,153,157,161,165,169,171 */
- return CHANWIDTH_USE_HT;
+ return CONF_OPER_CHWIDTH_USE_HT;
case 126: /* channels 149,157,165, 173; 40 MHz */
case 127: /* channels 153,161,169,177; 40 MHz */
- return CHANWIDTH_USE_HT;
+ return CONF_OPER_CHWIDTH_USE_HT;
case 128: /* center freqs 42, 58, 106, 122, 138, 155, 171; 80 MHz */
- return CHANWIDTH_80MHZ;
+ return CONF_OPER_CHWIDTH_80MHZ;
case 129: /* center freqs 50, 114, 163; 160 MHz */
- return CHANWIDTH_160MHZ;
+ return CONF_OPER_CHWIDTH_160MHZ;
case 130: /* center freqs 42, 58, 106, 122, 138, 155, 171; 80+80 MHz */
- return CHANWIDTH_80P80MHZ;
+ return CONF_OPER_CHWIDTH_80P80MHZ;
case 131: /* UHB channels, 20 MHz: 1, 5, 9.. */
- return CHANWIDTH_USE_HT;
+ return CONF_OPER_CHWIDTH_USE_HT;
case 132: /* UHB channels, 40 MHz: 3, 11, 19.. */
- return CHANWIDTH_USE_HT;
+ return CONF_OPER_CHWIDTH_USE_HT;
case 133: /* UHB channels, 80 MHz: 7, 23, 39.. */
- return CHANWIDTH_80MHZ;
+ return CONF_OPER_CHWIDTH_80MHZ;
case 134: /* UHB channels, 160 MHz: 15, 47, 79.. */
- return CHANWIDTH_160MHZ;
+ return CONF_OPER_CHWIDTH_160MHZ;
case 135: /* UHB channels, 80+80 MHz: 7, 23, 39.. */
- return CHANWIDTH_80P80MHZ;
+ return CONF_OPER_CHWIDTH_80P80MHZ;
case 136: /* UHB channels, 20 MHz: 2 */
- return CHANWIDTH_USE_HT;
+ return CONF_OPER_CHWIDTH_USE_HT;
+ case 137: /* UHB channels, 320 MHz: 31, 63, 95, 127, 159, 191 */
+ return CONF_OPER_CHWIDTH_320MHZ;
case 180: /* 60 GHz band, channels 1..8 */
- return CHANWIDTH_2160MHZ;
+ return CONF_OPER_CHWIDTH_2160MHZ;
case 181: /* 60 GHz band, EDMG CB2, channels 9..15 */
- return CHANWIDTH_4320MHZ;
+ return CONF_OPER_CHWIDTH_4320MHZ;
case 182: /* 60 GHz band, EDMG CB3, channels 17..22 */
- return CHANWIDTH_6480MHZ;
+ return CONF_OPER_CHWIDTH_6480MHZ;
case 183: /* 60 GHz band, EDMG CB4, channel 25..29 */
- return CHANWIDTH_8640MHZ;
+ return CONF_OPER_CHWIDTH_8640MHZ;
+ default:
+ return CONF_OPER_CHWIDTH_USE_HT;
}
- return CHANWIDTH_USE_HT;
}
-struct wpabuf * ieee802_11_defrag_data(struct ieee802_11_elems *elems,
- u8 eid, u8 eid_ext,
- const u8 *data, u8 len)
+
+struct wpabuf * ieee802_11_defrag_data(const u8 *data, size_t len,
+ bool ext_elem)
{
- struct frag_ies_info *frag_ies = &elems->frag_ies;
struct wpabuf *buf;
- unsigned int i;
+ const u8 *pos, *end = data + len;
+ size_t min_defrag_len = ext_elem ? 255 : 256;
- if (!elems || !data || !len)
+ if (!data || !len)
return NULL;
- buf = wpabuf_alloc_copy(data, len);
+ if (len < min_defrag_len)
+ return wpabuf_alloc_copy(data, len);
+
+ buf = wpabuf_alloc_copy(data, min_defrag_len - 1);
if (!buf)
return NULL;
- for (i = 0; i < frag_ies->n_frags; i++) {
+ pos = &data[min_defrag_len - 1];
+ len -= min_defrag_len - 1;
+ while (len > 2 && pos[0] == WLAN_EID_FRAGMENT && pos[1]) {
int ret;
+ size_t elen = 2 + pos[1];
- if (frag_ies->frags[i].eid != eid ||
- frag_ies->frags[i].eid_ext != eid_ext)
- continue;
-
- ret = wpabuf_resize(&buf, frag_ies->frags[i].ie_len);
+ if (elen > (size_t) (end - pos) || elen > len)
+ break;
+ ret = wpabuf_resize(&buf, pos[1]);
if (ret < 0) {
wpabuf_free(buf);
return NULL;
}
/* Copy only the fragment data (without the EID and length) */
- wpabuf_put_data(buf, frag_ies->frags[i].ie,
- frag_ies->frags[i].ie_len);
+ wpabuf_put_data(buf, &pos[2], pos[1]);
+ pos += elen;
+ len -= elen;
}
return buf;
@@ -2715,7 +3218,7 @@ struct wpabuf * ieee802_11_defrag(struct ieee802_11_elems *elems,
u8 eid, u8 eid_ext)
{
const u8 *data;
- u8 len;
+ size_t len;
/*
* TODO: Defragmentation mechanism can be supported for all IEs. For now
@@ -2745,7 +3248,7 @@ struct wpabuf * ieee802_11_defrag(struct ieee802_11_elems *elems,
return NULL;
}
- return ieee802_11_defrag_data(elems, eid, eid_ext, data, len);
+ return ieee802_11_defrag_data(data, len, true);
}
/* Parse HT capabilities to get maximum number of supported spatial streams */
@@ -2837,6 +3340,7 @@ struct supported_chan_width get_supported_channel_width(
struct supported_chan_width supported_width;
supported_width.is_160_supported = 0;
supported_width.is_80p80_supported = 0;
+ supported_width.is_320_supported = 0;
if (elems == NULL)
return supported_width;
@@ -2844,6 +3348,8 @@ struct supported_chan_width get_supported_channel_width(
(struct ieee80211_vht_capabilities *) elems->vht_capabilities;
struct ieee80211_he_capabilities *hecaps =
(struct ieee80211_he_capabilities *) elems->he_capabilities;
+ struct ieee80211_eht_capabilities *ehtcaps =
+ (struct ieee80211_eht_capabilities *) elems->eht_capabilities;
if (vhtcaps) {
le32 vht_capabilities_info =
@@ -2861,8 +3367,16 @@ struct supported_chan_width get_supported_channel_width(
if (channel_width_set & HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G)
supported_width.is_80p80_supported = 1;
}
- wpa_printf(MSG_DEBUG, " IE indicate 160 supported: %u, 80+80 supported: %u",
- supported_width.is_160_supported, supported_width.is_80p80_supported);
+ if (ehtcaps) {
+ if (ehtcaps->phy_cap[EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_IDX] &
+ EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_MASK)
+ supported_width.is_320_supported = 1;
+ }
+ wpa_printf(MSG_DEBUG,
+ " IE indicates 320 supported: %u, 160 supported: %u, 80+80 supported: %u",
+ supported_width.is_320_supported,
+ supported_width.is_160_supported,
+ supported_width.is_80p80_supported);
return supported_width;
}
@@ -2972,6 +3486,39 @@ static enum chan_width get_he_operation_channel_width(
return channel_width;
}
+/* Parse EHT operation IE to get EHT operation channel width */
+static enum chan_width get_eht_operation_channel_width(
+ struct ieee80211_eht_operation *eht_oper,
+ int eht_oper_len)
+{
+ enum chan_width channel_width = CHAN_WIDTH_UNKNOWN;
+ if (!(eht_oper->oper_params & EHT_OPER_INFO_PRESENT) ||
+ eht_oper_len < (EHT_OPERATION_IE_MIN_LEN + EHT_OPER_INFO_MIN_LEN))
+ return channel_width;
+
+ switch (eht_oper->oper_info.control & EHT_OPER_CHANNEL_WIDTH_MASK) {
+ case EHT_OPER_CHANNEL_WIDTH_20MHZ:
+ channel_width = CHAN_WIDTH_20;
+ break;
+ case EHT_OPER_CHANNEL_WIDTH_40MHZ:
+ channel_width = CHAN_WIDTH_40;
+ break;
+ case EHT_OPER_CHANNEL_WIDTH_80MHZ:
+ channel_width = CHAN_WIDTH_80;
+ break;
+ case EHT_OPER_CHANNEL_WIDTH_160MHZ:
+ channel_width = CHAN_WIDTH_160;
+ break;
+ case EHT_OPER_CHANNEL_WIDTH_320MHZ:
+ channel_width = CHAN_WIDTH_320;
+ break;
+ default:
+ break;
+ }
+ wpa_printf(MSG_DEBUG, " EHT operation CBW: %u", channel_width);
+ return channel_width;
+}
+
/* Parse HT/VHT/HE operation IEs to get operation channel width */
enum chan_width get_operation_channel_width(struct ieee802_11_elems *elems)
{
@@ -2985,7 +3532,14 @@ enum chan_width get_operation_channel_width(struct ieee802_11_elems *elems)
(struct ieee80211_vht_operation_info *) elems->vht_operation;
struct ieee80211_he_operation *he_oper =
(struct ieee80211_he_operation *) elems->he_operation;
- if (he_oper)
+ struct ieee80211_eht_operation *eht_oper =
+ (struct ieee80211_eht_operation *) elems->eht_operation;
+
+ if (eht_oper)
+ channel_width = get_eht_operation_channel_width(
+ eht_oper, elems->eht_operation_len);
+
+ if (channel_width == CHAN_WIDTH_UNKNOWN && he_oper)
channel_width = get_he_operation_channel_width(
he_oper, elems->he_operation_len);
@@ -3009,7 +3563,11 @@ enum chan_width get_sta_operation_chan_width(
enum chan_width ap_operation_chan_width,
struct supported_chan_width sta_supported_chan_width)
{
- if (ap_operation_chan_width == CHAN_WIDTH_160)
+ if (ap_operation_chan_width == CHAN_WIDTH_320 &&
+ sta_supported_chan_width.is_320_supported)
+ return CHAN_WIDTH_320;
+ if (ap_operation_chan_width == CHAN_WIDTH_160 ||
+ ap_operation_chan_width == CHAN_WIDTH_320)
return (sta_supported_chan_width.is_160_supported)
? CHAN_WIDTH_160 : CHAN_WIDTH_80;
if (ap_operation_chan_width == CHAN_WIDTH_80P80)
@@ -3017,3 +3575,75 @@ enum chan_width get_sta_operation_chan_width(
? CHAN_WIDTH_80P80 : CHAN_WIDTH_80;
return ap_operation_chan_width;
}
+
+const u8 * get_ml_ie(const u8 *ies, size_t len, u8 type)
+{
+ const struct element *elem;
+
+ if (!ies)
+ return NULL;
+
+ for_each_element_extid(elem, WLAN_EID_EXT_MULTI_LINK, ies, len) {
+ if (elem->datalen >= 2 &&
+ (elem->data[1] & MULTI_LINK_CONTROL_TYPE_MASK) == type)
+ return &elem->id;
+ }
+
+ return NULL;
+}
+
+
+const u8 * get_basic_mle_mld_addr(const u8 *buf, size_t len)
+{
+ const size_t mld_addr_pos =
+ 2 /* Control field */ +
+ 1 /* Common Info Length field */;
+ const size_t fixed_len = mld_addr_pos +
+ ETH_ALEN /* MLD MAC Address field */;
+
+ if (len < fixed_len)
+ return NULL;
+
+ if ((buf[0] & MULTI_LINK_CONTROL_TYPE_MASK) !=
+ MULTI_LINK_CONTROL_TYPE_BASIC)
+ return NULL;
+
+ return &buf[mld_addr_pos];
+}
+
+
+struct wpabuf * ieee802_11_defrag_mle(struct ieee802_11_elems *elems, u8 type)
+{
+ const u8 *data;
+ size_t len;
+
+ switch (type) {
+ case MULTI_LINK_CONTROL_TYPE_BASIC:
+ data = elems->basic_mle;
+ len = elems->basic_mle_len;
+ break;
+ case MULTI_LINK_CONTROL_TYPE_PROBE_REQ:
+ data = elems->probe_req_mle;
+ len = elems->probe_req_mle_len;
+ break;
+ case MULTI_LINK_CONTROL_TYPE_RECONF:
+ data = elems->reconf_mle;
+ len = elems->reconf_mle_len;
+ break;
+ case MULTI_LINK_CONTROL_TYPE_TDLS:
+ data = elems->tdls_mle;
+ len = elems->tdls_mle_len;
+ break;
+ case MULTI_LINK_CONTROL_TYPE_PRIOR_ACCESS:
+ data = elems->prior_access_mle;
+ len = elems->prior_access_mle_len;
+ break;
+ default:
+ wpa_printf(MSG_DEBUG,
+ "Defragmentation not supported for Multi-Link element type=%u",
+ type);
+ return NULL;
+ }
+
+ return ieee802_11_defrag_data(data, len, true);
+}