// SPDX-License-Identifier: GPL-2.0 /****************************************************************************** * * Copyright (C) 2020 SeekWave Technology Co.,Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation; * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * ******************************************************************************/ #include #include #include #include "skw_core.h" #include "skw_log.h" #include "skw_mbssid.h" #include "skw_cfg80211.h" #include "skw_compat.h" #define SKW_GENMASK_ULL(h, l) (((~0ULL) - (1ULL << (l)) + 1) & \ (~0ULL >> (BITS_PER_LONG_LONG - 1 - (h)))) static __always_inline u16 skw_get_unaligned_le16(const void *p) { return le16_to_cpup((__le16 *)p); } static struct skw_element *skw_find_elem(u8 eid, const u8 *ies, int len, const u8 *match, unsigned int match_len, unsigned int match_offset) { struct skw_element *elem; skw_foreach_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; } static const struct skw_element * skw_get_profile_continuation(const u8 *ie, size_t ie_len, const struct skw_element *mbssid_elem, const struct skw_element *sub_elem) { const u8 *mbssid_end = mbssid_elem->data + mbssid_elem->datalen; const struct skw_element *next_mbssid; const struct skw_element *sub; next_mbssid = skw_find_elem(WLAN_EID_MULTIPLE_BSSID, mbssid_end, ie_len - (mbssid_end - ie), NULL, 0, 0); if (!next_mbssid || (sub_elem->data + sub_elem->datalen < mbssid_end - 1)) return NULL; if (next_mbssid->datalen < 4) return NULL; sub = (void *)&next_mbssid->data[1]; if (next_mbssid->data + next_mbssid->datalen < sub->data + sub->datalen) return NULL; if (sub->id != 0 || sub->datalen < 2) return NULL; return sub->data[0] == WLAN_EID_NON_TX_BSSID_CAP ? NULL : next_mbssid; } static size_t skw_merge_profile(const u8 *ie, size_t ie_len, const struct skw_element *mbssid_elem, const struct skw_element *sub_elem, u8 *merged_ie, size_t max_copy_len) { size_t copied_len = sub_elem->datalen; const struct skw_element *next_mbssid; if (sub_elem->datalen > max_copy_len) return 0; memcpy(merged_ie, sub_elem->data, sub_elem->datalen); while ((next_mbssid = skw_get_profile_continuation(ie, ie_len, mbssid_elem, sub_elem))) { const struct skw_element *next = (void *)&next_mbssid->data[1]; if (copied_len + next->datalen > max_copy_len) break; memcpy(merged_ie + copied_len, next->data, next->datalen); copied_len += next->datalen; } return copied_len; } static inline void skw_gen_new_bssid(const u8 *bssid, u8 max_bssid, u8 mbssid_index, u8 *new_bssid) { u64 bssid_u64 = skw_mac_to_u64(bssid); u64 mask = SKW_GENMASK_ULL(max_bssid - 1, 0); u64 new_bssid_u64; new_bssid_u64 = bssid_u64 & ~mask; new_bssid_u64 |= ((bssid_u64 & mask) + mbssid_index) & mask; skw_u64_to_mac(new_bssid_u64, new_bssid); } static bool is_skw_element_inherited(const struct skw_element *elem, const struct skw_element *non_inherit_elem) { u8 id_len, ext_id_len, i, loop_len, id; const u8 *list; if (elem->id == WLAN_EID_MULTIPLE_BSSID) return false; if (!non_inherit_elem || non_inherit_elem->datalen < 2) return true; id_len = non_inherit_elem->data[1]; if (non_inherit_elem->datalen < 3 + id_len) return true; ext_id_len = non_inherit_elem->data[2 + id_len]; if (non_inherit_elem->datalen < 3 + id_len + ext_id_len) return true; if (elem->id == SKW_WLAN_EID_EXTENSION) { if (!ext_id_len) return true; loop_len = ext_id_len; list = &non_inherit_elem->data[3 + id_len]; id = elem->data[0]; } else { if (!id_len) return true; loop_len = id_len; list = &non_inherit_elem->data[2]; id = elem->id; } for (i = 0; i < loop_len; i++) { if (list[i] == id) return false; } return true; } static size_t skw_gen_new_ie(const u8 *ie, size_t ielen, const u8 *subelement, size_t subie_len, u8 *new_ie, gfp_t gfp) { u8 eid; u8 *pos, *tmp; const u8 *tmp_old, *tmp_new; const struct skw_element *non_inherit; u8 *sub_copy; sub_copy = SKW_KMEMDUP(subelement, subie_len, gfp); if (!sub_copy) return 0; 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); } /* get non inheritance list if exists */ eid = SKW_EID_EXT_NON_INHERITANCE; non_inherit = skw_find_elem(SKW_WLAN_EID_EXTENSION, sub_copy, subie_len, &eid, 1, 0); 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; } if (tmp_old[0] == SKW_WLAN_EID_EXTENSION) { tmp = (u8 *)skw_find_elem(SKW_WLAN_EID_EXTENSION, sub_copy, subie_len, &tmp_old[2], 1, 2); } else { tmp = (u8 *)cfg80211_find_ie(tmp_old[0], sub_copy, subie_len); } if (!tmp) { const struct skw_element *old_elem = (void *)tmp_old; if (is_skw_element_inherited(old_elem, non_inherit)) { memcpy(pos, tmp_old, tmp_old[1] + 2); pos += tmp_old[1] + 2; } } else { if (tmp_old[0] == WLAN_EID_VENDOR_SPECIFIC) { if (!memcmp(tmp_old + 2, tmp + 2, 5)) { memcpy(pos, tmp, tmp[1] + 2); pos += tmp[1] + 2; tmp[0] = WLAN_EID_SSID; } else { memcpy(pos, tmp_old, tmp_old[1] + 2); pos += tmp_old[1] + 2; } } else { memcpy(pos, tmp, tmp[1] + 2); pos += tmp[1] + 2; tmp[0] = WLAN_EID_SSID; } } if (tmp_old + tmp_old[1] + 2 - ie == ielen) break; tmp_old += tmp_old[1] + 2; } 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)) { 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; } SKW_KFREE(sub_copy); return pos - new_ie; } static void skw_parse_mbssid_data(struct wiphy *wiphy, struct ieee80211_channel *rx_channel, s32 signal, #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) enum cfg80211_bss_frame_type ftype, #endif const u8 *bssid, u64 tsf, u16 beacon_interval, const u8 *ie, size_t ie_len, gfp_t gfp) { const u8 *idx_ie; const struct skw_element *elem, *sub; size_t new_ie_len; u8 bssid_index; u8 max_indicator; u8 new_bssid[ETH_ALEN]; u8 *new_ie, *profile; u64 seen_indices = 0; u16 capability; struct cfg80211_bss *bss; new_ie = SKW_ZALLOC(IEEE80211_MAX_DATA_LEN, gfp); if (!new_ie) return; profile = SKW_ZALLOC(ie_len, gfp); if (!profile) goto out; skw_foreach_element_id(elem, WLAN_EID_MULTIPLE_BSSID, ie, ie_len) { if (elem->datalen < 4) continue; skw_foreach_element(sub, elem->data + 1, elem->datalen - 1) { u8 profile_len; if (sub->id != 0 || sub->datalen < 4) continue; if (sub->data[0] != WLAN_EID_NON_TX_BSSID_CAP || sub->data[1] != 2) { continue; } memset(profile, 0, ie_len); profile_len = skw_merge_profile(ie, ie_len, elem, sub, profile, ie_len); idx_ie = cfg80211_find_ie(SKW_WLAN_EID_MULTI_BSSID_IDX, profile, profile_len); if (!idx_ie || idx_ie[1] < 1 || idx_ie[2] == 0 || idx_ie[2] > 46) { /* No valid Multiple BSSID-Index element */ continue; } if (seen_indices & (1ULL << (idx_ie[2]))) net_dbg_ratelimited("Partial info for BSSID index %d\n", idx_ie[2]); seen_indices |= (1ULL << (idx_ie[2])); bssid_index = idx_ie[2]; max_indicator = elem->data[0]; skw_gen_new_bssid(bssid, max_indicator, bssid_index, new_bssid); memset(new_ie, 0, IEEE80211_MAX_DATA_LEN); new_ie_len = skw_gen_new_ie(ie, ie_len, profile, profile_len, new_ie, GFP_KERNEL); if (!new_ie_len) continue; capability = skw_get_unaligned_le16(profile + 2); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) bss = cfg80211_inform_bss(wiphy, rx_channel, ftype, new_bssid, tsf, capability, beacon_interval, new_ie, new_ie_len, signal, gfp); #else bss = cfg80211_inform_bss(wiphy, rx_channel, new_bssid, tsf, capability, beacon_interval, new_ie, new_ie_len, signal, gfp); #endif if (!bss) break; skw_bss_priv(bss)->bssid_index = bssid_index; skw_bss_priv(bss)->max_bssid_indicator = max_indicator; cfg80211_put_bss(wiphy, bss); } } SKW_KFREE(profile); out: SKW_KFREE(new_ie); } void skw_mbssid_data_parser(struct wiphy *wiphy, bool beacon, struct ieee80211_channel *chan, s32 signal, struct ieee80211_mgmt *mgmt, int mgmt_len) { const u8 *ie = mgmt->u.probe_resp.variable; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) enum cfg80211_bss_frame_type ftype = CFG80211_BSS_FTYPE_PRESP; #endif size_t len = offsetof(struct ieee80211_mgmt, u.probe_resp.variable); if (!cfg80211_find_ie(WLAN_EID_MULTIPLE_BSSID, ie, mgmt_len - len)) return; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) if (beacon) ftype = CFG80211_BSS_FTYPE_BEACON; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) skw_parse_mbssid_data(wiphy, chan, signal, ftype, mgmt->bssid, le64_to_cpu(mgmt->u.probe_resp.timestamp), le16_to_cpu(mgmt->u.probe_resp.beacon_int), ie, mgmt_len - len, GFP_KERNEL); #else skw_parse_mbssid_data(wiphy, chan, signal, mgmt->bssid, le64_to_cpu(mgmt->u.probe_resp.timestamp), le16_to_cpu(mgmt->u.probe_resp.beacon_int), ie, mgmt_len - len, GFP_KERNEL); #endif }