Loading include/linux/ieee80211.h +53 −0 Original line number Diff line number Diff line Loading @@ -3185,4 +3185,57 @@ static inline bool ieee80211_action_contains_tpc(struct sk_buff *skb) return true; } struct element { u8 id; u8 datalen; u8 data[]; }; /* element iteration helpers */ #define for_each_element(element, _data, _datalen) \ for (element = (void *)(_data); \ (u8 *)(_data) + (_datalen) - (u8 *)element >= \ sizeof(*element) && \ (u8 *)(_data) + (_datalen) - (u8 *)element >= \ sizeof(*element) + element->datalen; \ element = (void *)(element->data + element->datalen)) #define for_each_element_id(element, _id, data, datalen) \ for_each_element(element, data, datalen) \ if (element->id == (_id)) #define for_each_element_extid(element, extid, data, datalen) \ for_each_element(element, data, datalen) \ if (element->id == WLAN_EID_EXTENSION && \ element->datalen > 0 && \ element->data[0] == (extid)) #define for_each_subelement(sub, element) \ for_each_element(sub, (element)->data, (element)->datalen) #define for_each_subelement_id(sub, id, element) \ for_each_element_id(sub, id, (element)->data, (element)->datalen) #define for_each_subelement_extid(sub, extid, element) \ for_each_element_extid(sub, extid, (element)->data, (element)->datalen) /** * for_each_element_completed - determine if element parsing consumed all data * @element: element pointer after for_each_element() or friends * @data: same data pointer as passed to for_each_element() or friends * @datalen: same data length as passed to for_each_element() or friends * * This function returns %true if all the data was parsed or considered * while walking the elements. Only use this if your for_each_element() * loop cannot be broken out of, otherwise it always returns %false. * * If some data was malformed, this returns %false since the last parsed * element will not fill the whole remaining data. */ static inline bool for_each_element_completed(const struct element *element, const void *data, size_t datalen) { return (u8 *)element == (u8 *)data + datalen; } #endif /* LINUX_IEEE80211_H */ net/wireless/core.h +2 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ * Wireless configuration interface internals. * * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net> * Copyright (C) 2018-2019 Intel Corporation */ #ifndef __NET_WIRELESS_CORE_H #define __NET_WIRELESS_CORE_H Loading Loading @@ -140,6 +141,7 @@ extern int cfg80211_rdev_list_generation; struct cfg80211_internal_bss { struct list_head list; struct list_head hidden_list; struct list_head nontrans_list; struct rb_node rbn; u64 ts_boottime; unsigned long ts; Loading net/wireless/scan.c +459 −46 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ * Copyright 2008 Johannes Berg <johannes@sipsolutions.net> * Copyright 2013-2014 Intel Mobile Communications GmbH * Copyright 2016 Intel Deutschland GmbH * Copyright (C) 2018-2019 Intel Corporation */ #include <linux/kernel.h> #include <linux/slab.h> Loading Loading @@ -150,6 +151,7 @@ static bool __cfg80211_unlink_bss(struct cfg80211_registered_device *rdev, } list_del_init(&bss->list); list_del_init(&bss->nontrans_list); rb_erase(&bss->rbn, &rdev->bss_tree); rdev->bss_entries--; WARN_ONCE((rdev->bss_entries == 0) ^ list_empty(&rdev->bss_list), Loading @@ -159,6 +161,172 @@ static bool __cfg80211_unlink_bss(struct cfg80211_registered_device *rdev, return true; } static void cfg80211_gen_new_bssid(const u8 *bssid, u8 max_bssid, u8 mbssid_index, u8 *new_bssid_addr) { u64 bssid_tmp, new_bssid = 0; u64 lsb_n; bssid_tmp = ether_addr_to_u64(bssid); lsb_n = bssid_tmp & ((1 << max_bssid) - 1); new_bssid = bssid_tmp; new_bssid &= ~((1 << max_bssid) - 1); new_bssid |= (lsb_n + mbssid_index) % (1 << max_bssid); u64_to_ether_addr(new_bssid, new_bssid_addr); } static size_t cfg80211_gen_new_ie(const u8 *ie, size_t ielen, const u8 *subelement, size_t subie_len, u8 *new_ie, gfp_t gfp) { u8 *pos, *tmp; const u8 *tmp_old, *tmp_new; u8 *sub_copy; /* copy subelement as we need to change its content to * mark an ie after it is processed. */ sub_copy = kmalloc(subie_len, gfp); if (!sub_copy) return 0; memcpy(sub_copy, subelement, subie_len); pos = &new_ie[0]; /* set new ssid */ tmp_new = cfg80211_find_ie(WLAN_EID_SSID, sub_copy, subie_len); if (tmp_new) { memcpy(pos, tmp_new, tmp_new[1] + 2); pos += (tmp_new[1] + 2); } /* go through IEs in ie (skip SSID) and subelement, * merge them into new_ie */ tmp_old = cfg80211_find_ie(WLAN_EID_SSID, ie, ielen); tmp_old = (tmp_old) ? tmp_old + tmp_old[1] + 2 : ie; while (tmp_old + tmp_old[1] + 2 - ie <= ielen) { if (tmp_old[0] == 0) { tmp_old++; continue; } tmp = (u8 *)cfg80211_find_ie(tmp_old[0], sub_copy, subie_len); if (!tmp) { /* ie in old ie but not in subelement */ if (tmp_old[0] != WLAN_EID_MULTIPLE_BSSID) { memcpy(pos, tmp_old, tmp_old[1] + 2); pos += tmp_old[1] + 2; } } else { /* ie in transmitting ie also in subelement, * copy from subelement and flag the ie in subelement * as copied (by setting eid field to 0xff). For * vendor ie, compare OUI + type + subType to * determine if they are the same ie. */ if (tmp_old[0] == WLAN_EID_VENDOR_SPECIFIC) { if (!memcmp(tmp_old + 2, tmp + 2, 5)) { /* same vendor ie, copy from * subelement */ memcpy(pos, tmp, tmp[1] + 2); pos += tmp[1] + 2; tmp[0] = 0xff; } else { memcpy(pos, tmp_old, tmp_old[1] + 2); pos += tmp_old[1] + 2; } } else { /* copy ie from subelement into new ie */ memcpy(pos, tmp, tmp[1] + 2); pos += tmp[1] + 2; tmp[0] = 0xff; } } if (tmp_old + tmp_old[1] + 2 - ie == ielen) break; tmp_old += tmp_old[1] + 2; } /* go through subelement again to check if there is any ie not * copied to new ie, skip ssid, capability, bssid-index ie */ tmp_new = sub_copy; while (tmp_new + tmp_new[1] + 2 - sub_copy <= subie_len) { if (!(tmp_new[0] == WLAN_EID_NON_TX_BSSID_CAP || tmp_new[0] == WLAN_EID_SSID || tmp_new[0] == WLAN_EID_MULTI_BSSID_IDX || tmp_new[0] == 0xff)) { memcpy(pos, tmp_new, tmp_new[1] + 2); pos += tmp_new[1] + 2; } if (tmp_new + tmp_new[1] + 2 - sub_copy == subie_len) break; tmp_new += tmp_new[1] + 2; } kfree(sub_copy); return pos - new_ie; } static bool is_bss(struct cfg80211_bss *a, const u8 *bssid, const u8 *ssid, size_t ssid_len) { const struct cfg80211_bss_ies *ies; const u8 *ssidie; if (bssid && !ether_addr_equal(a->bssid, bssid)) return false; if (!ssid) return true; ies = rcu_access_pointer(a->ies); if (!ies) return false; ssidie = cfg80211_find_ie(WLAN_EID_SSID, ies->data, ies->len); if (!ssidie) return false; if (ssidie[1] != ssid_len) return false; return memcmp(ssidie + 2, ssid, ssid_len) == 0; } static int cfg80211_add_nontrans_list(struct cfg80211_internal_bss *trans_bss, struct cfg80211_internal_bss *nontrans_bss) { const u8 *ssid; size_t ssid_len; struct cfg80211_internal_bss *bss = NULL; rcu_read_lock(); ssid = ieee80211_bss_get_ie(&nontrans_bss->pub, WLAN_EID_SSID); if (!ssid) { rcu_read_unlock(); return -EINVAL; } ssid_len = ssid[1]; ssid = ssid + 2; rcu_read_unlock(); /* check if nontrans_bss is in the list */ list_for_each_entry(bss, &trans_bss->nontrans_list, nontrans_list) { if (is_bss(&bss->pub, nontrans_bss->pub.bssid, ssid, ssid_len)) return 0; } /* add to the list */ list_add_tail(&nontrans_bss->nontrans_list, &trans_bss->nontrans_list); return 0; } static void __cfg80211_bss_expire(struct cfg80211_registered_device *rdev, unsigned long expire_time) { Loading Loading @@ -484,6 +652,8 @@ const u8 *cfg80211_find_ie_match(u8 eid, const u8 *ies, int len, const u8 *match, int match_len, int match_offset) { const struct element *elem; /* match_offset can't be smaller than 2, unless match_len is * zero, in which case match_offset must be zero as well. */ Loading @@ -491,14 +661,10 @@ const u8 *cfg80211_find_ie_match(u8 eid, const u8 *ies, int len, (!match_len && match_offset))) return NULL; while (len >= 2 && len >= ies[1] + 2) { if ((ies[0] == eid) && (ies[1] + 2 >= match_offset + match_len) && !memcmp(ies + match_offset, match, match_len)) return ies; len -= ies[1] + 2; ies += ies[1] + 2; for_each_element_id(elem, eid, ies, len) { if (elem->datalen >= match_offset - 2 + match_len && !memcmp(elem->data + match_offset - 2, match, match_len)) return (void *)elem; } return NULL; Loading @@ -525,29 +691,6 @@ const u8 *cfg80211_find_vendor_ie(unsigned int oui, int oui_type, } EXPORT_SYMBOL(cfg80211_find_vendor_ie); static bool is_bss(struct cfg80211_bss *a, const u8 *bssid, const u8 *ssid, size_t ssid_len) { const struct cfg80211_bss_ies *ies; const u8 *ssidie; if (bssid && !ether_addr_equal(a->bssid, bssid)) return false; if (!ssid) return true; ies = rcu_access_pointer(a->ies); if (!ies) return false; ssidie = cfg80211_find_ie(WLAN_EID_SSID, ies->data, ies->len); if (!ssidie) return false; if (ssidie[1] != ssid_len) return false; return memcmp(ssidie + 2, ssid, ssid_len) == 0; } /** * enum bss_compare_mode - BSS compare mode * @BSS_CMP_REGULAR: regular compare mode (for insertion and normal find) Loading Loading @@ -1009,6 +1152,7 @@ cfg80211_bss_update(struct cfg80211_registered_device *rdev, memcpy(new, tmp, sizeof(*new)); new->refcount = 1; INIT_LIST_HEAD(&new->hidden_list); INIT_LIST_HEAD(&new->nontrans_list); if (rcu_access_pointer(tmp->pub.proberesp_ies)) { hidden = rb_find_bss(rdev, tmp, BSS_CMP_HIDE_ZLEN); Loading Loading @@ -1130,17 +1274,19 @@ cfg80211_get_bss_channel(struct wiphy *wiphy, const u8 *ie, size_t ielen, } /* Returned bss is reference counted and must be cleaned up appropriately. */ struct cfg80211_bss * cfg80211_inform_bss_data(struct wiphy *wiphy, static struct cfg80211_bss * cfg80211_inform_single_bss_data(struct wiphy *wiphy, struct cfg80211_inform_bss *data, enum cfg80211_bss_frame_type ftype, const u8 *bssid, u64 tsf, u16 capability, u16 beacon_interval, const u8 *ie, size_t ielen, struct cfg80211_bss *trans_bss, gfp_t gfp) { struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); struct cfg80211_bss_ies *ies; struct ieee80211_channel *channel; struct cfg80211_internal_bss tmp = {}, *res; struct cfg80211_internal_bss tmp = {}, *res, *trans_internal; int bss_type; bool signal_valid; Loading Loading @@ -1209,19 +1355,235 @@ cfg80211_inform_bss_data(struct wiphy *wiphy, regulatory_hint_found_beacon(wiphy, channel, gfp); } if (trans_bss) { /* this is a nontransmitting bss, we need to add it to * transmitting bss' list if it is not there */ trans_internal = container_of(trans_bss, struct cfg80211_internal_bss, pub); if (cfg80211_add_nontrans_list(trans_internal, res)) { if (__cfg80211_unlink_bss(rdev, res)) rdev->bss_generation++; } } trace_cfg80211_return_bss(&res->pub); /* cfg80211_bss_update gives us a referenced result */ return &res->pub; } EXPORT_SYMBOL(cfg80211_inform_bss_data); /* cfg80211_inform_bss_width_frame helper */ static void cfg80211_parse_mbssid_data(struct wiphy *wiphy, struct cfg80211_inform_bss *data, enum cfg80211_bss_frame_type ftype, const u8 *bssid, u64 tsf, u16 beacon_interval, const u8 *ie, size_t ielen, struct cfg80211_bss *trans_bss, gfp_t gfp) { const u8 *mbssid_index_ie; const struct element *elem, *sub; size_t new_ie_len; u8 new_bssid[ETH_ALEN]; u8 *new_ie; u16 capability; struct cfg80211_bss *bss; if (!trans_bss) return; if (!cfg80211_find_ie(WLAN_EID_MULTIPLE_BSSID, ie, ielen)) return; new_ie = kmalloc(IEEE80211_MAX_DATA_LEN, gfp); if (!new_ie) return; for_each_element_id(elem, WLAN_EID_MULTIPLE_BSSID, ie, ielen) { if (elem->datalen < 4) continue; for_each_element(sub, elem->data + 1, elem->datalen - 1) { if (sub->id != 0 || sub->datalen < 4) { /* not a valid BSS profile */ continue; } if (sub->data[0] != WLAN_EID_NON_TX_BSSID_CAP || sub->data[1] != 2) { /* The first element within the Nontransmitted * BSSID Profile is not the Nontransmitted * BSSID Capability element. */ continue; } /* found a Nontransmitted BSSID Profile */ mbssid_index_ie = cfg80211_find_ie (WLAN_EID_MULTI_BSSID_IDX, sub->data, sub->datalen); if (!mbssid_index_ie || mbssid_index_ie[1] < 1 || mbssid_index_ie[2] == 0) { /* No valid Multiple BSSID-Index element */ continue; } cfg80211_gen_new_bssid(bssid, elem->data[0], mbssid_index_ie[2], new_bssid); memset(new_ie, 0, IEEE80211_MAX_DATA_LEN); new_ie_len = cfg80211_gen_new_ie(ie, ielen, sub->data, sub->datalen, new_ie, gfp); if (!new_ie_len) continue; capability = get_unaligned_le16(sub->data + 2); bss = cfg80211_inform_single_bss_data(wiphy, data, ftype, new_bssid, tsf, capability, beacon_interval, new_ie, new_ie_len, trans_bss, gfp); if (!bss) break; cfg80211_put_bss(wiphy, bss); } } kfree(new_ie); } struct cfg80211_bss * cfg80211_inform_bss_frame_data(struct wiphy *wiphy, cfg80211_inform_bss_data(struct wiphy *wiphy, struct cfg80211_inform_bss *data, enum cfg80211_bss_frame_type ftype, const u8 *bssid, u64 tsf, u16 capability, u16 beacon_interval, const u8 *ie, size_t ielen, gfp_t gfp) { struct cfg80211_bss *res; res = cfg80211_inform_single_bss_data(wiphy, data, ftype, bssid, tsf, capability, beacon_interval, ie, ielen, NULL, gfp); cfg80211_parse_mbssid_data(wiphy, data, ftype, bssid, tsf, beacon_interval, ie, ielen, res, gfp); return res; } EXPORT_SYMBOL(cfg80211_inform_bss_data); static void cfg80211_parse_mbssid_frame_data(struct wiphy *wiphy, struct cfg80211_inform_bss *data, struct ieee80211_mgmt *mgmt, size_t len, struct cfg80211_bss *trans_bss, gfp_t gfp) { enum cfg80211_bss_frame_type ftype; const u8 *ie = mgmt->u.probe_resp.variable; size_t ielen = len - offsetof(struct ieee80211_mgmt, u.probe_resp.variable); ftype = ieee80211_is_beacon(mgmt->frame_control) ? CFG80211_BSS_FTYPE_BEACON : CFG80211_BSS_FTYPE_PRESP; cfg80211_parse_mbssid_data(wiphy, data, ftype, mgmt->bssid, le64_to_cpu(mgmt->u.probe_resp.timestamp), le16_to_cpu(mgmt->u.probe_resp.beacon_int), ie, ielen, trans_bss, gfp); } static void cfg80211_update_notlisted_nontrans(struct wiphy *wiphy, struct cfg80211_internal_bss *nontrans_bss, struct ieee80211_mgmt *mgmt, size_t len, gfp_t gfp) { u8 *ie, *new_ie, *pos; const u8 *nontrans_ssid, *trans_ssid, *mbssid; size_t ielen = len - offsetof(struct ieee80211_mgmt, u.probe_resp.variable); size_t new_ie_len; struct cfg80211_bss_ies *new_ies; const struct cfg80211_bss_ies *old; u8 cpy_len; ie = mgmt->u.probe_resp.variable; new_ie_len = ielen; trans_ssid = cfg80211_find_ie(WLAN_EID_SSID, ie, ielen); if (!trans_ssid) return; new_ie_len -= trans_ssid[1]; mbssid = cfg80211_find_ie(WLAN_EID_MULTIPLE_BSSID, ie, ielen); if (!mbssid) return; new_ie_len -= mbssid[1]; rcu_read_lock(); nontrans_ssid = ieee80211_bss_get_ie(&nontrans_bss->pub, WLAN_EID_SSID); if (!nontrans_ssid) { rcu_read_unlock(); return; } new_ie_len += nontrans_ssid[1]; rcu_read_unlock(); /* generate new ie for nontrans BSS * 1. replace SSID with nontrans BSS' SSID * 2. skip MBSSID IE */ new_ie = kzalloc(new_ie_len, gfp); if (!new_ie) return; new_ies = kzalloc(sizeof(*new_ies) + new_ie_len, gfp); if (!new_ies) { kfree(new_ie); return; } pos = new_ie; /* copy the nontransmitted SSID */ cpy_len = nontrans_ssid[1] + 2; memcpy(pos, nontrans_ssid, cpy_len); pos += cpy_len; /* copy the IEs between SSID and MBSSID */ cpy_len = trans_ssid[1] + 2; memcpy(pos, (trans_ssid + cpy_len), (mbssid - (trans_ssid + cpy_len))); pos += (mbssid - (trans_ssid + cpy_len)); /* copy the IEs after MBSSID */ cpy_len = mbssid[1] + 2; memcpy(pos, mbssid + cpy_len, ((ie + ielen) - (mbssid + cpy_len))); /* update ie */ new_ies->len = new_ie_len; new_ies->tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp); new_ies->from_beacon = ieee80211_is_beacon(mgmt->frame_control); memcpy(new_ies->data, new_ie, new_ie_len); if (ieee80211_is_probe_resp(mgmt->frame_control)) { old = rcu_access_pointer(nontrans_bss->pub.proberesp_ies); rcu_assign_pointer(nontrans_bss->pub.proberesp_ies, new_ies); rcu_assign_pointer(nontrans_bss->pub.ies, new_ies); if (old) kfree_rcu((struct cfg80211_bss_ies *)old, rcu_head); } else { old = rcu_access_pointer(nontrans_bss->pub.beacon_ies); rcu_assign_pointer(nontrans_bss->pub.beacon_ies, new_ies); rcu_assign_pointer(nontrans_bss->pub.ies, new_ies); if (old) kfree_rcu((struct cfg80211_bss_ies *)old, rcu_head); } } /* cfg80211_inform_bss_width_frame helper */ static struct cfg80211_bss * cfg80211_inform_single_bss_frame_data(struct wiphy *wiphy, struct cfg80211_inform_bss *data, struct ieee80211_mgmt *mgmt, size_t len, struct cfg80211_bss *trans_bss, gfp_t gfp) { struct cfg80211_internal_bss tmp = {}, *res; struct cfg80211_bss_ies *ies; Loading Loading @@ -1300,6 +1662,50 @@ cfg80211_inform_bss_frame_data(struct wiphy *wiphy, /* cfg80211_bss_update gives us a referenced result */ return &res->pub; } struct cfg80211_bss * cfg80211_inform_bss_frame_data(struct wiphy *wiphy, struct cfg80211_inform_bss *data, struct ieee80211_mgmt *mgmt, size_t len, gfp_t gfp) { struct cfg80211_bss *res; struct cfg80211_internal_bss *trans_bss, *tmp_bss; const u8 *ie = mgmt->u.probe_resp.variable; const struct cfg80211_bss_ies *ies1, *ies2; size_t ielen = len - offsetof(struct ieee80211_mgmt, u.probe_resp.variable); res = cfg80211_inform_single_bss_frame_data(wiphy, data, mgmt, len, NULL, gfp); if (!res || !cfg80211_find_ie(WLAN_EID_MULTIPLE_BSSID, ie, ielen)) return res; /* process each non-transmitting bss */ cfg80211_parse_mbssid_frame_data(wiphy, data, mgmt, len, res, gfp); /* check if the res has other nontransmitting bss which is not * in MBSSID IE */ ies1 = rcu_access_pointer(res->ies); trans_bss = container_of(res, struct cfg80211_internal_bss, pub); if (!trans_bss) return res; /* go through nontrans_list, if the timestamp of the BSS is * earlier than the timestamp of the transmitting BSS then * update it */ list_for_each_entry(tmp_bss, &trans_bss->nontrans_list, nontrans_list) { ies2 = rcu_access_pointer(tmp_bss->pub.ies); if (ies2->tsf < ies1->tsf) cfg80211_update_notlisted_nontrans(wiphy, tmp_bss, mgmt, len, gfp); } return res; } EXPORT_SYMBOL(cfg80211_inform_bss_frame_data); void cfg80211_ref_bss(struct wiphy *wiphy, struct cfg80211_bss *pub) Loading Loading @@ -1337,7 +1743,7 @@ EXPORT_SYMBOL(cfg80211_put_bss); void cfg80211_unlink_bss(struct wiphy *wiphy, struct cfg80211_bss *pub) { struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); struct cfg80211_internal_bss *bss; struct cfg80211_internal_bss *bss, *nontrans_bss, *tmp; if (WARN_ON(!pub)) return; Loading @@ -1346,6 +1752,13 @@ void cfg80211_unlink_bss(struct wiphy *wiphy, struct cfg80211_bss *pub) spin_lock_bh(&rdev->bss_lock); if (!list_empty(&bss->list)) { list_for_each_entry_safe(nontrans_bss, tmp, &bss->nontrans_list, nontrans_list) { if (__cfg80211_unlink_bss(rdev, nontrans_bss)) rdev->bss_generation++; } if (__cfg80211_unlink_bss(rdev, bss)) rdev->bss_generation++; } Loading Loading
include/linux/ieee80211.h +53 −0 Original line number Diff line number Diff line Loading @@ -3185,4 +3185,57 @@ static inline bool ieee80211_action_contains_tpc(struct sk_buff *skb) return true; } struct element { u8 id; u8 datalen; u8 data[]; }; /* element iteration helpers */ #define for_each_element(element, _data, _datalen) \ for (element = (void *)(_data); \ (u8 *)(_data) + (_datalen) - (u8 *)element >= \ sizeof(*element) && \ (u8 *)(_data) + (_datalen) - (u8 *)element >= \ sizeof(*element) + element->datalen; \ element = (void *)(element->data + element->datalen)) #define for_each_element_id(element, _id, data, datalen) \ for_each_element(element, data, datalen) \ if (element->id == (_id)) #define for_each_element_extid(element, extid, data, datalen) \ for_each_element(element, data, datalen) \ if (element->id == WLAN_EID_EXTENSION && \ element->datalen > 0 && \ element->data[0] == (extid)) #define for_each_subelement(sub, element) \ for_each_element(sub, (element)->data, (element)->datalen) #define for_each_subelement_id(sub, id, element) \ for_each_element_id(sub, id, (element)->data, (element)->datalen) #define for_each_subelement_extid(sub, extid, element) \ for_each_element_extid(sub, extid, (element)->data, (element)->datalen) /** * for_each_element_completed - determine if element parsing consumed all data * @element: element pointer after for_each_element() or friends * @data: same data pointer as passed to for_each_element() or friends * @datalen: same data length as passed to for_each_element() or friends * * This function returns %true if all the data was parsed or considered * while walking the elements. Only use this if your for_each_element() * loop cannot be broken out of, otherwise it always returns %false. * * If some data was malformed, this returns %false since the last parsed * element will not fill the whole remaining data. */ static inline bool for_each_element_completed(const struct element *element, const void *data, size_t datalen) { return (u8 *)element == (u8 *)data + datalen; } #endif /* LINUX_IEEE80211_H */
net/wireless/core.h +2 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ * Wireless configuration interface internals. * * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net> * Copyright (C) 2018-2019 Intel Corporation */ #ifndef __NET_WIRELESS_CORE_H #define __NET_WIRELESS_CORE_H Loading Loading @@ -140,6 +141,7 @@ extern int cfg80211_rdev_list_generation; struct cfg80211_internal_bss { struct list_head list; struct list_head hidden_list; struct list_head nontrans_list; struct rb_node rbn; u64 ts_boottime; unsigned long ts; Loading
net/wireless/scan.c +459 −46 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ * Copyright 2008 Johannes Berg <johannes@sipsolutions.net> * Copyright 2013-2014 Intel Mobile Communications GmbH * Copyright 2016 Intel Deutschland GmbH * Copyright (C) 2018-2019 Intel Corporation */ #include <linux/kernel.h> #include <linux/slab.h> Loading Loading @@ -150,6 +151,7 @@ static bool __cfg80211_unlink_bss(struct cfg80211_registered_device *rdev, } list_del_init(&bss->list); list_del_init(&bss->nontrans_list); rb_erase(&bss->rbn, &rdev->bss_tree); rdev->bss_entries--; WARN_ONCE((rdev->bss_entries == 0) ^ list_empty(&rdev->bss_list), Loading @@ -159,6 +161,172 @@ static bool __cfg80211_unlink_bss(struct cfg80211_registered_device *rdev, return true; } static void cfg80211_gen_new_bssid(const u8 *bssid, u8 max_bssid, u8 mbssid_index, u8 *new_bssid_addr) { u64 bssid_tmp, new_bssid = 0; u64 lsb_n; bssid_tmp = ether_addr_to_u64(bssid); lsb_n = bssid_tmp & ((1 << max_bssid) - 1); new_bssid = bssid_tmp; new_bssid &= ~((1 << max_bssid) - 1); new_bssid |= (lsb_n + mbssid_index) % (1 << max_bssid); u64_to_ether_addr(new_bssid, new_bssid_addr); } static size_t cfg80211_gen_new_ie(const u8 *ie, size_t ielen, const u8 *subelement, size_t subie_len, u8 *new_ie, gfp_t gfp) { u8 *pos, *tmp; const u8 *tmp_old, *tmp_new; u8 *sub_copy; /* copy subelement as we need to change its content to * mark an ie after it is processed. */ sub_copy = kmalloc(subie_len, gfp); if (!sub_copy) return 0; memcpy(sub_copy, subelement, subie_len); pos = &new_ie[0]; /* set new ssid */ tmp_new = cfg80211_find_ie(WLAN_EID_SSID, sub_copy, subie_len); if (tmp_new) { memcpy(pos, tmp_new, tmp_new[1] + 2); pos += (tmp_new[1] + 2); } /* go through IEs in ie (skip SSID) and subelement, * merge them into new_ie */ tmp_old = cfg80211_find_ie(WLAN_EID_SSID, ie, ielen); tmp_old = (tmp_old) ? tmp_old + tmp_old[1] + 2 : ie; while (tmp_old + tmp_old[1] + 2 - ie <= ielen) { if (tmp_old[0] == 0) { tmp_old++; continue; } tmp = (u8 *)cfg80211_find_ie(tmp_old[0], sub_copy, subie_len); if (!tmp) { /* ie in old ie but not in subelement */ if (tmp_old[0] != WLAN_EID_MULTIPLE_BSSID) { memcpy(pos, tmp_old, tmp_old[1] + 2); pos += tmp_old[1] + 2; } } else { /* ie in transmitting ie also in subelement, * copy from subelement and flag the ie in subelement * as copied (by setting eid field to 0xff). For * vendor ie, compare OUI + type + subType to * determine if they are the same ie. */ if (tmp_old[0] == WLAN_EID_VENDOR_SPECIFIC) { if (!memcmp(tmp_old + 2, tmp + 2, 5)) { /* same vendor ie, copy from * subelement */ memcpy(pos, tmp, tmp[1] + 2); pos += tmp[1] + 2; tmp[0] = 0xff; } else { memcpy(pos, tmp_old, tmp_old[1] + 2); pos += tmp_old[1] + 2; } } else { /* copy ie from subelement into new ie */ memcpy(pos, tmp, tmp[1] + 2); pos += tmp[1] + 2; tmp[0] = 0xff; } } if (tmp_old + tmp_old[1] + 2 - ie == ielen) break; tmp_old += tmp_old[1] + 2; } /* go through subelement again to check if there is any ie not * copied to new ie, skip ssid, capability, bssid-index ie */ tmp_new = sub_copy; while (tmp_new + tmp_new[1] + 2 - sub_copy <= subie_len) { if (!(tmp_new[0] == WLAN_EID_NON_TX_BSSID_CAP || tmp_new[0] == WLAN_EID_SSID || tmp_new[0] == WLAN_EID_MULTI_BSSID_IDX || tmp_new[0] == 0xff)) { memcpy(pos, tmp_new, tmp_new[1] + 2); pos += tmp_new[1] + 2; } if (tmp_new + tmp_new[1] + 2 - sub_copy == subie_len) break; tmp_new += tmp_new[1] + 2; } kfree(sub_copy); return pos - new_ie; } static bool is_bss(struct cfg80211_bss *a, const u8 *bssid, const u8 *ssid, size_t ssid_len) { const struct cfg80211_bss_ies *ies; const u8 *ssidie; if (bssid && !ether_addr_equal(a->bssid, bssid)) return false; if (!ssid) return true; ies = rcu_access_pointer(a->ies); if (!ies) return false; ssidie = cfg80211_find_ie(WLAN_EID_SSID, ies->data, ies->len); if (!ssidie) return false; if (ssidie[1] != ssid_len) return false; return memcmp(ssidie + 2, ssid, ssid_len) == 0; } static int cfg80211_add_nontrans_list(struct cfg80211_internal_bss *trans_bss, struct cfg80211_internal_bss *nontrans_bss) { const u8 *ssid; size_t ssid_len; struct cfg80211_internal_bss *bss = NULL; rcu_read_lock(); ssid = ieee80211_bss_get_ie(&nontrans_bss->pub, WLAN_EID_SSID); if (!ssid) { rcu_read_unlock(); return -EINVAL; } ssid_len = ssid[1]; ssid = ssid + 2; rcu_read_unlock(); /* check if nontrans_bss is in the list */ list_for_each_entry(bss, &trans_bss->nontrans_list, nontrans_list) { if (is_bss(&bss->pub, nontrans_bss->pub.bssid, ssid, ssid_len)) return 0; } /* add to the list */ list_add_tail(&nontrans_bss->nontrans_list, &trans_bss->nontrans_list); return 0; } static void __cfg80211_bss_expire(struct cfg80211_registered_device *rdev, unsigned long expire_time) { Loading Loading @@ -484,6 +652,8 @@ const u8 *cfg80211_find_ie_match(u8 eid, const u8 *ies, int len, const u8 *match, int match_len, int match_offset) { const struct element *elem; /* match_offset can't be smaller than 2, unless match_len is * zero, in which case match_offset must be zero as well. */ Loading @@ -491,14 +661,10 @@ const u8 *cfg80211_find_ie_match(u8 eid, const u8 *ies, int len, (!match_len && match_offset))) return NULL; while (len >= 2 && len >= ies[1] + 2) { if ((ies[0] == eid) && (ies[1] + 2 >= match_offset + match_len) && !memcmp(ies + match_offset, match, match_len)) return ies; len -= ies[1] + 2; ies += ies[1] + 2; for_each_element_id(elem, eid, ies, len) { if (elem->datalen >= match_offset - 2 + match_len && !memcmp(elem->data + match_offset - 2, match, match_len)) return (void *)elem; } return NULL; Loading @@ -525,29 +691,6 @@ const u8 *cfg80211_find_vendor_ie(unsigned int oui, int oui_type, } EXPORT_SYMBOL(cfg80211_find_vendor_ie); static bool is_bss(struct cfg80211_bss *a, const u8 *bssid, const u8 *ssid, size_t ssid_len) { const struct cfg80211_bss_ies *ies; const u8 *ssidie; if (bssid && !ether_addr_equal(a->bssid, bssid)) return false; if (!ssid) return true; ies = rcu_access_pointer(a->ies); if (!ies) return false; ssidie = cfg80211_find_ie(WLAN_EID_SSID, ies->data, ies->len); if (!ssidie) return false; if (ssidie[1] != ssid_len) return false; return memcmp(ssidie + 2, ssid, ssid_len) == 0; } /** * enum bss_compare_mode - BSS compare mode * @BSS_CMP_REGULAR: regular compare mode (for insertion and normal find) Loading Loading @@ -1009,6 +1152,7 @@ cfg80211_bss_update(struct cfg80211_registered_device *rdev, memcpy(new, tmp, sizeof(*new)); new->refcount = 1; INIT_LIST_HEAD(&new->hidden_list); INIT_LIST_HEAD(&new->nontrans_list); if (rcu_access_pointer(tmp->pub.proberesp_ies)) { hidden = rb_find_bss(rdev, tmp, BSS_CMP_HIDE_ZLEN); Loading Loading @@ -1130,17 +1274,19 @@ cfg80211_get_bss_channel(struct wiphy *wiphy, const u8 *ie, size_t ielen, } /* Returned bss is reference counted and must be cleaned up appropriately. */ struct cfg80211_bss * cfg80211_inform_bss_data(struct wiphy *wiphy, static struct cfg80211_bss * cfg80211_inform_single_bss_data(struct wiphy *wiphy, struct cfg80211_inform_bss *data, enum cfg80211_bss_frame_type ftype, const u8 *bssid, u64 tsf, u16 capability, u16 beacon_interval, const u8 *ie, size_t ielen, struct cfg80211_bss *trans_bss, gfp_t gfp) { struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); struct cfg80211_bss_ies *ies; struct ieee80211_channel *channel; struct cfg80211_internal_bss tmp = {}, *res; struct cfg80211_internal_bss tmp = {}, *res, *trans_internal; int bss_type; bool signal_valid; Loading Loading @@ -1209,19 +1355,235 @@ cfg80211_inform_bss_data(struct wiphy *wiphy, regulatory_hint_found_beacon(wiphy, channel, gfp); } if (trans_bss) { /* this is a nontransmitting bss, we need to add it to * transmitting bss' list if it is not there */ trans_internal = container_of(trans_bss, struct cfg80211_internal_bss, pub); if (cfg80211_add_nontrans_list(trans_internal, res)) { if (__cfg80211_unlink_bss(rdev, res)) rdev->bss_generation++; } } trace_cfg80211_return_bss(&res->pub); /* cfg80211_bss_update gives us a referenced result */ return &res->pub; } EXPORT_SYMBOL(cfg80211_inform_bss_data); /* cfg80211_inform_bss_width_frame helper */ static void cfg80211_parse_mbssid_data(struct wiphy *wiphy, struct cfg80211_inform_bss *data, enum cfg80211_bss_frame_type ftype, const u8 *bssid, u64 tsf, u16 beacon_interval, const u8 *ie, size_t ielen, struct cfg80211_bss *trans_bss, gfp_t gfp) { const u8 *mbssid_index_ie; const struct element *elem, *sub; size_t new_ie_len; u8 new_bssid[ETH_ALEN]; u8 *new_ie; u16 capability; struct cfg80211_bss *bss; if (!trans_bss) return; if (!cfg80211_find_ie(WLAN_EID_MULTIPLE_BSSID, ie, ielen)) return; new_ie = kmalloc(IEEE80211_MAX_DATA_LEN, gfp); if (!new_ie) return; for_each_element_id(elem, WLAN_EID_MULTIPLE_BSSID, ie, ielen) { if (elem->datalen < 4) continue; for_each_element(sub, elem->data + 1, elem->datalen - 1) { if (sub->id != 0 || sub->datalen < 4) { /* not a valid BSS profile */ continue; } if (sub->data[0] != WLAN_EID_NON_TX_BSSID_CAP || sub->data[1] != 2) { /* The first element within the Nontransmitted * BSSID Profile is not the Nontransmitted * BSSID Capability element. */ continue; } /* found a Nontransmitted BSSID Profile */ mbssid_index_ie = cfg80211_find_ie (WLAN_EID_MULTI_BSSID_IDX, sub->data, sub->datalen); if (!mbssid_index_ie || mbssid_index_ie[1] < 1 || mbssid_index_ie[2] == 0) { /* No valid Multiple BSSID-Index element */ continue; } cfg80211_gen_new_bssid(bssid, elem->data[0], mbssid_index_ie[2], new_bssid); memset(new_ie, 0, IEEE80211_MAX_DATA_LEN); new_ie_len = cfg80211_gen_new_ie(ie, ielen, sub->data, sub->datalen, new_ie, gfp); if (!new_ie_len) continue; capability = get_unaligned_le16(sub->data + 2); bss = cfg80211_inform_single_bss_data(wiphy, data, ftype, new_bssid, tsf, capability, beacon_interval, new_ie, new_ie_len, trans_bss, gfp); if (!bss) break; cfg80211_put_bss(wiphy, bss); } } kfree(new_ie); } struct cfg80211_bss * cfg80211_inform_bss_frame_data(struct wiphy *wiphy, cfg80211_inform_bss_data(struct wiphy *wiphy, struct cfg80211_inform_bss *data, enum cfg80211_bss_frame_type ftype, const u8 *bssid, u64 tsf, u16 capability, u16 beacon_interval, const u8 *ie, size_t ielen, gfp_t gfp) { struct cfg80211_bss *res; res = cfg80211_inform_single_bss_data(wiphy, data, ftype, bssid, tsf, capability, beacon_interval, ie, ielen, NULL, gfp); cfg80211_parse_mbssid_data(wiphy, data, ftype, bssid, tsf, beacon_interval, ie, ielen, res, gfp); return res; } EXPORT_SYMBOL(cfg80211_inform_bss_data); static void cfg80211_parse_mbssid_frame_data(struct wiphy *wiphy, struct cfg80211_inform_bss *data, struct ieee80211_mgmt *mgmt, size_t len, struct cfg80211_bss *trans_bss, gfp_t gfp) { enum cfg80211_bss_frame_type ftype; const u8 *ie = mgmt->u.probe_resp.variable; size_t ielen = len - offsetof(struct ieee80211_mgmt, u.probe_resp.variable); ftype = ieee80211_is_beacon(mgmt->frame_control) ? CFG80211_BSS_FTYPE_BEACON : CFG80211_BSS_FTYPE_PRESP; cfg80211_parse_mbssid_data(wiphy, data, ftype, mgmt->bssid, le64_to_cpu(mgmt->u.probe_resp.timestamp), le16_to_cpu(mgmt->u.probe_resp.beacon_int), ie, ielen, trans_bss, gfp); } static void cfg80211_update_notlisted_nontrans(struct wiphy *wiphy, struct cfg80211_internal_bss *nontrans_bss, struct ieee80211_mgmt *mgmt, size_t len, gfp_t gfp) { u8 *ie, *new_ie, *pos; const u8 *nontrans_ssid, *trans_ssid, *mbssid; size_t ielen = len - offsetof(struct ieee80211_mgmt, u.probe_resp.variable); size_t new_ie_len; struct cfg80211_bss_ies *new_ies; const struct cfg80211_bss_ies *old; u8 cpy_len; ie = mgmt->u.probe_resp.variable; new_ie_len = ielen; trans_ssid = cfg80211_find_ie(WLAN_EID_SSID, ie, ielen); if (!trans_ssid) return; new_ie_len -= trans_ssid[1]; mbssid = cfg80211_find_ie(WLAN_EID_MULTIPLE_BSSID, ie, ielen); if (!mbssid) return; new_ie_len -= mbssid[1]; rcu_read_lock(); nontrans_ssid = ieee80211_bss_get_ie(&nontrans_bss->pub, WLAN_EID_SSID); if (!nontrans_ssid) { rcu_read_unlock(); return; } new_ie_len += nontrans_ssid[1]; rcu_read_unlock(); /* generate new ie for nontrans BSS * 1. replace SSID with nontrans BSS' SSID * 2. skip MBSSID IE */ new_ie = kzalloc(new_ie_len, gfp); if (!new_ie) return; new_ies = kzalloc(sizeof(*new_ies) + new_ie_len, gfp); if (!new_ies) { kfree(new_ie); return; } pos = new_ie; /* copy the nontransmitted SSID */ cpy_len = nontrans_ssid[1] + 2; memcpy(pos, nontrans_ssid, cpy_len); pos += cpy_len; /* copy the IEs between SSID and MBSSID */ cpy_len = trans_ssid[1] + 2; memcpy(pos, (trans_ssid + cpy_len), (mbssid - (trans_ssid + cpy_len))); pos += (mbssid - (trans_ssid + cpy_len)); /* copy the IEs after MBSSID */ cpy_len = mbssid[1] + 2; memcpy(pos, mbssid + cpy_len, ((ie + ielen) - (mbssid + cpy_len))); /* update ie */ new_ies->len = new_ie_len; new_ies->tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp); new_ies->from_beacon = ieee80211_is_beacon(mgmt->frame_control); memcpy(new_ies->data, new_ie, new_ie_len); if (ieee80211_is_probe_resp(mgmt->frame_control)) { old = rcu_access_pointer(nontrans_bss->pub.proberesp_ies); rcu_assign_pointer(nontrans_bss->pub.proberesp_ies, new_ies); rcu_assign_pointer(nontrans_bss->pub.ies, new_ies); if (old) kfree_rcu((struct cfg80211_bss_ies *)old, rcu_head); } else { old = rcu_access_pointer(nontrans_bss->pub.beacon_ies); rcu_assign_pointer(nontrans_bss->pub.beacon_ies, new_ies); rcu_assign_pointer(nontrans_bss->pub.ies, new_ies); if (old) kfree_rcu((struct cfg80211_bss_ies *)old, rcu_head); } } /* cfg80211_inform_bss_width_frame helper */ static struct cfg80211_bss * cfg80211_inform_single_bss_frame_data(struct wiphy *wiphy, struct cfg80211_inform_bss *data, struct ieee80211_mgmt *mgmt, size_t len, struct cfg80211_bss *trans_bss, gfp_t gfp) { struct cfg80211_internal_bss tmp = {}, *res; struct cfg80211_bss_ies *ies; Loading Loading @@ -1300,6 +1662,50 @@ cfg80211_inform_bss_frame_data(struct wiphy *wiphy, /* cfg80211_bss_update gives us a referenced result */ return &res->pub; } struct cfg80211_bss * cfg80211_inform_bss_frame_data(struct wiphy *wiphy, struct cfg80211_inform_bss *data, struct ieee80211_mgmt *mgmt, size_t len, gfp_t gfp) { struct cfg80211_bss *res; struct cfg80211_internal_bss *trans_bss, *tmp_bss; const u8 *ie = mgmt->u.probe_resp.variable; const struct cfg80211_bss_ies *ies1, *ies2; size_t ielen = len - offsetof(struct ieee80211_mgmt, u.probe_resp.variable); res = cfg80211_inform_single_bss_frame_data(wiphy, data, mgmt, len, NULL, gfp); if (!res || !cfg80211_find_ie(WLAN_EID_MULTIPLE_BSSID, ie, ielen)) return res; /* process each non-transmitting bss */ cfg80211_parse_mbssid_frame_data(wiphy, data, mgmt, len, res, gfp); /* check if the res has other nontransmitting bss which is not * in MBSSID IE */ ies1 = rcu_access_pointer(res->ies); trans_bss = container_of(res, struct cfg80211_internal_bss, pub); if (!trans_bss) return res; /* go through nontrans_list, if the timestamp of the BSS is * earlier than the timestamp of the transmitting BSS then * update it */ list_for_each_entry(tmp_bss, &trans_bss->nontrans_list, nontrans_list) { ies2 = rcu_access_pointer(tmp_bss->pub.ies); if (ies2->tsf < ies1->tsf) cfg80211_update_notlisted_nontrans(wiphy, tmp_bss, mgmt, len, gfp); } return res; } EXPORT_SYMBOL(cfg80211_inform_bss_frame_data); void cfg80211_ref_bss(struct wiphy *wiphy, struct cfg80211_bss *pub) Loading Loading @@ -1337,7 +1743,7 @@ EXPORT_SYMBOL(cfg80211_put_bss); void cfg80211_unlink_bss(struct wiphy *wiphy, struct cfg80211_bss *pub) { struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); struct cfg80211_internal_bss *bss; struct cfg80211_internal_bss *bss, *nontrans_bss, *tmp; if (WARN_ON(!pub)) return; Loading @@ -1346,6 +1752,13 @@ void cfg80211_unlink_bss(struct wiphy *wiphy, struct cfg80211_bss *pub) spin_lock_bh(&rdev->bss_lock); if (!list_empty(&bss->list)) { list_for_each_entry_safe(nontrans_bss, tmp, &bss->nontrans_list, nontrans_list) { if (__cfg80211_unlink_bss(rdev, nontrans_bss)) rdev->bss_generation++; } if (__cfg80211_unlink_bss(rdev, bss)) rdev->bss_generation++; } Loading