wifi: mac80211: Parse station profile from association response

When processing an association response frame for a Multi-Link
connection, extract the per station profile for each additional
link, and use it for parsing the link elements.

As the Multi-Link element might be fragmented, add support for
reassembling a fragmented element. To simplify memory management
logic, extend 'struct ieee802_11_elems' to hold a scratch buffer,
which is used for the defragmentation. Once an element is
reconstructed in the scratch area, point the corresponding element
pointer to it. Currently only defragmentation of Multi-Link element
and the contained per-STA profile subelement is supported.

Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
Ilan Peer 2022-09-06 11:48:56 +03:00 committed by Johannes Berg
parent fb99c7d4d6
commit 45ebac4f05
4 changed files with 188 additions and 6 deletions

View File

@ -4666,6 +4666,7 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len)
enum ieee80211_mle_subelems {
IEEE80211_MLE_SUBELEM_PER_STA_PROFILE = 0,
IEEE80211_MLE_SUBELEM_FRAGMENT = 254,
};
#define IEEE80211_MLE_STA_CONTROL_LINK_ID 0x000f

View File

@ -1707,8 +1707,27 @@ struct ieee802_11_elems {
u8 tx_pwr_env_num;
u8 eht_cap_len;
/* mult-link element can be de-fragmented and thus u8 is not sufficient */
size_t multi_link_len;
/*
* store the per station profile pointer and length in case that the
* parsing also handled Multi-Link element parsing for a specific link
* ID.
*/
struct ieee80211_mle_per_sta_profile *prof;
size_t sta_prof_len;
/* whether a parse error occurred while retrieving these elements */
bool parse_error;
/*
* scratch buffer that can be used for various element parsing related
* tasks, e.g., element de-fragmentation etc.
*/
size_t scratch_len;
u8 *scratch_pos;
u8 scratch[];
};
static inline struct ieee80211_local *hw_to_local(
@ -2197,9 +2216,13 @@ static inline void ieee80211_tx_skb(struct ieee80211_sub_if_data *sdata,
* represent a non-transmitting BSS in which case the data
* for that non-transmitting BSS is returned
* @link_id: the link ID to parse elements for, if a STA profile
* is present in the multi-link element, or -1 to ignore
* is present in the multi-link element, or -1 to ignore;
* note that the code currently assumes parsing an association
* (or re-association) response frame if this is given
* @from_ap: frame is received from an AP (currently used only
* for EHT capabilities parsing)
* @scratch_len: if non zero, specifies the requested length of the scratch
* buffer; otherwise, 'len' is used.
*/
struct ieee80211_elems_parse_params {
const u8 *start;
@ -2210,6 +2233,7 @@ struct ieee80211_elems_parse_params {
struct cfg80211_bss *bss;
int link_id;
bool from_ap;
size_t scratch_len;
};
struct ieee802_11_elems *

View File

@ -3923,11 +3923,12 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
struct ieee80211_mgd_assoc_data *assoc_data = sdata->u.mgd.assoc_data;
struct ieee80211_bss_conf *bss_conf = link->conf;
struct ieee80211_local *local = sdata->local;
unsigned int link_id = link->link_id;
struct ieee80211_elems_parse_params parse_params = {
.start = elem_start,
.len = elem_len,
.bss = cbss,
.link_id = link == &sdata->deflink ? -1 : link->link_id,
.link_id = link_id == assoc_data->assoc_link_id ? -1 : link_id,
.from_ap = true,
};
bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ;
@ -3942,8 +3943,18 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
if (!elems)
return false;
/* FIXME: use from STA profile element after parsing that */
capab_info = le16_to_cpu(mgmt->u.assoc_resp.capab_info);
if (link_id == assoc_data->assoc_link_id) {
capab_info = le16_to_cpu(mgmt->u.assoc_resp.capab_info);
} else if (!elems->prof) {
ret = false;
goto out;
} else {
const u8 *ptr = elems->prof->variable +
elems->prof->sta_info_len - 1;
/* FIXME: need to also handle the status code */
capab_info = get_unaligned_le16(ptr);
}
if (!is_s1g && !elems->supp_rates) {
sdata_info(sdata, "no SuppRates element in AssocResp\n");

View File

@ -1026,8 +1026,10 @@ ieee80211_parse_extension_element(u32 *crc,
elems->eht_operation = data;
break;
case WLAN_EID_EXT_EHT_MULTI_LINK:
if (ieee80211_mle_size_ok(data, len))
if (ieee80211_mle_size_ok(data, len)) {
elems->multi_link = (void *)data;
elems->multi_link_len = len;
}
break;
}
}
@ -1497,6 +1499,145 @@ static size_t ieee802_11_find_bssid_profile(const u8 *start, size_t len,
return found ? profile_len : 0;
}
static void ieee80211_defragment_element(struct ieee802_11_elems *elems,
void **elem_ptr, size_t *len,
size_t total_len, u8 frag_id)
{
u8 *data = *elem_ptr, *pos, *start;
const struct element *elem;
/*
* Since 'data' points to the data of the element, not the element
* itself, allow 254 in case it was an extended element where the
* extended ID isn't part of the data we see here and thus not part of
* 'len' either.
*/
if (!data || (*len != 254 && *len != 255))
return;
start = elems->scratch_pos;
if (WARN_ON(*len > (elems->scratch + elems->scratch_len -
elems->scratch_pos)))
return;
memcpy(elems->scratch_pos, data, *len);
elems->scratch_pos += *len;
pos = data + *len;
total_len -= *len;
for_each_element(elem, pos, total_len) {
if (elem->id != frag_id)
break;
if (WARN_ON(elem->datalen >
(elems->scratch + elems->scratch_len -
elems->scratch_pos)))
return;
memcpy(elems->scratch_pos, elem->data, elem->datalen);
elems->scratch_pos += elem->datalen;
*len += elem->datalen;
}
*elem_ptr = start;
}
static void ieee80211_mle_get_sta_prof(struct ieee802_11_elems *elems,
u8 link_id)
{
const struct ieee80211_multi_link_elem *ml = elems->multi_link;
size_t ml_len = elems->multi_link_len;
const struct element *sub;
if (!ml || !ml_len)
return;
if (le16_get_bits(ml->control, IEEE80211_ML_CONTROL_TYPE) !=
IEEE80211_ML_CONTROL_TYPE_BASIC)
return;
for_each_mle_subelement(sub, (u8 *)ml, ml_len) {
struct ieee80211_mle_per_sta_profile *prof = (void *)sub->data;
u16 control;
if (sub->id != IEEE80211_MLE_SUBELEM_PER_STA_PROFILE)
continue;
if (!ieee80211_mle_sta_prof_size_ok(sub->data, sub->datalen))
return;
control = le16_to_cpu(prof->control);
if (link_id != u16_get_bits(control,
IEEE80211_MLE_STA_CONTROL_LINK_ID))
continue;
if (!(control & IEEE80211_MLE_STA_CONTROL_COMPLETE_PROFILE))
return;
elems->prof = prof;
elems->sta_prof_len = sub->datalen;
/* the sub element can be fragmented */
ieee80211_defragment_element(elems, (void **)&elems->prof,
&elems->sta_prof_len,
ml_len - (sub->data - (u8 *)ml),
IEEE80211_MLE_SUBELEM_FRAGMENT);
return;
}
}
static void ieee80211_mle_parse_link(struct ieee802_11_elems *elems,
struct ieee80211_elems_parse_params *params)
{
struct ieee80211_mle_per_sta_profile *prof;
struct ieee80211_elems_parse_params sub = {
.action = params->action,
.from_ap = params->from_ap,
.link_id = -1,
};
const struct element *non_inherit = NULL;
const u8 *end;
if (params->link_id == -1)
return;
ieee80211_defragment_element(elems, (void **)&elems->multi_link,
&elems->multi_link_len,
elems->total_len - ((u8 *)elems->multi_link -
elems->ie_start),
WLAN_EID_FRAGMENT);
ieee80211_mle_get_sta_prof(elems, params->link_id);
prof = elems->prof;
if (!prof)
return;
/* check if we have the 4 bytes for the fixed part in assoc response */
if (elems->sta_prof_len < sizeof(*prof) + prof->sta_info_len - 1 + 4) {
elems->prof = NULL;
elems->sta_prof_len = 0;
return;
}
/*
* Skip the capability information and the status code that are expected
* as part of the station profile in association response frames. Note
* the -1 is because the 'sta_info_len' is accounted to as part of the
* per-STA profile, but not part of the 'u8 variable[]' portion.
*/
sub.start = prof->variable + prof->sta_info_len - 1 + 4;
end = (const u8 *)prof + elems->sta_prof_len;
sub.len = end - sub.start;
non_inherit = cfg80211_find_ext_elem(WLAN_EID_EXT_NON_INHERITANCE,
sub.start, sub.len);
_ieee802_11_parse_elems_full(&sub, elems, non_inherit);
}
struct ieee802_11_elems *
ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
{
@ -1504,12 +1645,15 @@ ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
const struct element *non_inherit = NULL;
u8 *nontransmitted_profile;
int nontransmitted_profile_len = 0;
size_t scratch_len = params->scratch_len ?: 2 * params->len;
elems = kzalloc(sizeof(*elems), GFP_ATOMIC);
elems = kzalloc(sizeof(*elems) + scratch_len, GFP_ATOMIC);
if (!elems)
return NULL;
elems->ie_start = params->start;
elems->total_len = params->len;
elems->scratch_len = scratch_len;
elems->scratch_pos = elems->scratch;
nontransmitted_profile = kmalloc(params->len, GFP_ATOMIC);
if (nontransmitted_profile) {
@ -1537,6 +1681,8 @@ ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
_ieee802_11_parse_elems_full(&sub, elems, NULL);
}
ieee80211_mle_parse_link(elems, params);
if (elems->tim && !elems->parse_error) {
const struct ieee80211_tim_ie *tim_ie = elems->tim;