| /*- |
| * Copyright (c) 2001 Atsushi Onoe |
| * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * Alternatively, this software may be distributed under the terms of the |
| * GNU General Public License ("GPL") version 2 as published by the Free |
| * Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * $Id: ieee80211_input.c 2610 2007-07-25 15:26:38Z mrenzmann $ |
| */ |
| #ifndef EXPORT_SYMTAB |
| #define EXPORT_SYMTAB |
| #endif |
| |
| /* |
| * IEEE 802.11 input handling. |
| */ |
| #ifndef AUTOCONF_INCLUDED |
| #include <linux/config.h> |
| #endif |
| #include <linux/version.h> |
| #include <linux/module.h> |
| #include <linux/skbuff.h> |
| #include <linux/netdevice.h> |
| #include <linux/etherdevice.h> |
| #include <linux/random.h> |
| #include <linux/if_vlan.h> |
| #include <net/iw_handler.h> /* wireless_send_event(..) */ |
| #include <linux/wireless.h> /* SIOCGIWTHRSPY */ |
| #include <linux/if_arp.h> /* ARPHRD_ETHER */ |
| #include <linux/jiffies.h> |
| |
| #include "net80211/if_llc.h" |
| #include "net80211/if_ethersubr.h" |
| #include "net80211/if_media.h" |
| |
| #include "net80211/ieee80211_var.h" |
| #include "net80211/ieee80211_linux.h" |
| #include "net80211/ieee80211_dot11_msg.h" |
| #include "net80211/ieee80211_tpc.h" |
| #include "net80211/ieee80211_tdls.h" |
| #include "net80211/ieee80211_mlme_statistics.h" |
| |
| #include "qtn/wlan_ioctl.h" |
| |
| #include "qtn/qtn_global.h" |
| #include "qtn_logging.h" |
| |
| #include "../qdrv/qdrv_debug.h" |
| #include <qtn/shared_params.h> |
| #include <qtn/hardware_revision.h> |
| |
| #if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) |
| #include <linux/if_bridge.h> |
| #include "../../net/bridge/br_public.h" |
| #endif |
| |
| #if defined(CONFIG_QTN_BSA_SUPPORT) |
| #include "net80211/ieee80211_bsa.h" |
| #endif |
| |
| extern u_int16_t ht_rate_table_20MHz_800[]; |
| extern u_int16_t ht_rate_table_40MHz_800[]; |
| |
| #ifdef IEEE80211_DEBUG |
| /* |
| * Decide if a received management frame should be |
| * printed when debugging is enabled. This filters some |
| * of the less interesting frames that come frequently |
| * (e.g. beacons). |
| */ |
| static __inline int |
| doprint(struct ieee80211vap *vap, int subtype) |
| { |
| switch (subtype) { |
| case IEEE80211_FC0_SUBTYPE_BEACON: |
| return (vap->iv_ic->ic_flags & IEEE80211_F_SCAN); |
| case IEEE80211_FC0_SUBTYPE_PROBE_REQ: |
| return (vap->iv_opmode == IEEE80211_M_IBSS); |
| } |
| return 1; |
| } |
| |
| /* |
| * Emit a debug message about discarding a frame or information |
| * element. One format is for extracting the mac address from |
| * the frame header; the other is for when a header is not |
| * available or otherwise appropriate. |
| */ |
| #define IEEE80211_DISCARD(_vap, _m, _wh, _type, _fmt, ...) do { \ |
| if ((_vap)->iv_debug & (_m)) \ |
| ieee80211_discard_frame(_vap, _wh, _type, _fmt, __VA_ARGS__);\ |
| } while (0) |
| #define IEEE80211_DISCARD_IE(_vap, _m, _wh, _type, _fmt, ...) do { \ |
| if ((_vap)->iv_debug & (_m)) \ |
| ieee80211_discard_ie(_vap, _wh, _type, _fmt, __VA_ARGS__);\ |
| } while (0) |
| #define IEEE80211_DISCARD_MAC(_vap, _m, _mac, _type, _fmt, ...) do { \ |
| if ((_vap)->iv_debug & (_m)) \ |
| ieee80211_discard_mac(_vap, _mac, _type, _fmt, __VA_ARGS__);\ |
| } while (0) |
| |
| static const u_int8_t *ieee80211_getbssid(struct ieee80211vap *, |
| const struct ieee80211_frame *); |
| static void ieee80211_discard_frame(struct ieee80211vap *, |
| const struct ieee80211_frame *, const char *, const char *, ...); |
| static void ieee80211_discard_ie(struct ieee80211vap *, |
| const struct ieee80211_frame *, const char *, const char *, ...); |
| static void ieee80211_discard_mac(struct ieee80211vap *, |
| const u_int8_t mac[IEEE80211_ADDR_LEN], const char *, |
| const char *, ...); |
| #else |
| #define IEEE80211_DISCARD(_vap, _m, _wh, _type, _fmt, ...) |
| #define IEEE80211_DISCARD_IE(_vap, _m, _wh, _type, _fmt, ...) |
| #define IEEE80211_DISCARD_MAC(_vap, _m, _mac, _type, _fmt, ...) |
| #endif /* IEEE80211_DEBUG */ |
| |
| static struct sk_buff *ieee80211_defrag(struct ieee80211_node *, |
| struct sk_buff *, int); |
| static void ieee80211_deliver_data(struct ieee80211_node *, struct sk_buff *); |
| static struct sk_buff *ieee80211_decap(struct ieee80211vap *, |
| struct sk_buff *, int); |
| static void ieee80211_send_error(struct ieee80211_node *, const u_int8_t *, |
| int, int); |
| static void ieee80211_recv_pspoll(struct ieee80211_node *, struct sk_buff *); |
| static int accept_data_frame(struct ieee80211vap *, struct ieee80211_node *, |
| struct ieee80211_key *, struct sk_buff *, struct ether_header *); |
| static void forward_mgmt_to_app(struct ieee80211vap *vap, int subtype, struct sk_buff *skb, |
| struct ieee80211_frame *wh); |
| static void forward_mgmt_to_app_for_further_processing(struct ieee80211vap *vap, |
| int subtype, struct sk_buff *skb, struct ieee80211_frame *wh); |
| #ifdef USE_HEADERLEN_RESV |
| static __be16 ath_eth_type_trans(struct sk_buff *, struct net_device *); |
| #endif |
| |
| static void ieee80211_recv_action_tdls(struct ieee80211_node *ni, struct sk_buff *skb, |
| struct ieee80211_action *ia, int ieee80211_header, int rssi); |
| |
| static void ieee80211_recv_action_vht(struct ieee80211_node *ni, |
| struct ieee80211_action *ia, |
| int subtype, |
| struct ieee80211_frame *wh, |
| u_int8_t *frm, |
| u_int8_t *efrm); |
| static void ieee80211_recv_action_wnm(struct ieee80211_node *ni, |
| struct ieee80211_action *ia, |
| int subtype, |
| struct ieee80211_frame *wh, |
| u_int8_t *efrm); |
| |
| /** |
| * Given a node and the RSSI value of a just received frame from the node, this |
| * function checks if to raise an iwspy event because we iwspy the node and RSSI |
| * exceeds threshold (if active). |
| * |
| * @param vap: VAP |
| * @param ni: sender node |
| * @param rssi: RSSI value of received frame |
| */ |
| static void |
| iwspy_event(struct ieee80211vap *vap, struct ieee80211_node *ni, u_int rssi) |
| { |
| if (vap->iv_spy.thr_low && vap->iv_spy.num && ni && (rssi < |
| vap->iv_spy.thr_low || rssi > vap->iv_spy.thr_high)) { |
| int i; |
| for (i = 0; i < vap->iv_spy.num; i++) { |
| if (IEEE80211_ADDR_EQ(ni->ni_macaddr, |
| &(vap->iv_spy.mac[i * IEEE80211_ADDR_LEN]))) { |
| |
| union iwreq_data wrq; |
| struct iw_thrspy thr; |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG, |
| "%s: we spy %s, threshold is active " |
| "and rssi exceeds it -> raise an iwspy" |
| " event\n", __func__, ether_sprintf( |
| ni->ni_macaddr)); |
| memset(&wrq, 0, sizeof(wrq)); |
| wrq.data.length = 1; |
| memset(&thr, 0, sizeof(struct iw_thrspy)); |
| memcpy(thr.addr.sa_data, ni->ni_macaddr, |
| IEEE80211_ADDR_LEN); |
| thr.addr.sa_family = ARPHRD_ETHER; |
| set_quality(&thr.qual, rssi, vap->iv_ic->ic_channoise); |
| set_quality(&thr.low, vap->iv_spy.thr_low, vap->iv_ic->ic_channoise); |
| set_quality(&thr.high, vap->iv_spy.thr_high, vap->iv_ic->ic_channoise); |
| wireless_send_event(vap->iv_dev, |
| SIOCGIWTHRSPY, &wrq, (char*) &thr); |
| break; |
| } |
| } |
| } |
| } |
| |
| static inline int |
| ieee80211_tdls_status_mismatch(struct ieee80211_node *ni) |
| { |
| if (IEEE80211_NODE_IS_TDLS_INACTIVE(ni) || |
| IEEE80211_NODE_IS_TDLS_IDLE(ni)) |
| return 1; |
| |
| return 0; |
| } |
| |
| int ieee80211_tdls_tqe_path_check(struct ieee80211_node *ni, |
| struct sk_buff *skb, int rssi, uint16_t ether_type) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_action *ia; |
| uint8_t *payload_type; |
| struct ether_header *eh = (struct ether_header *) skb->data; |
| |
| if (ether_type == __constant_htons(ETHERTYPE_80211MGT)) { |
| payload_type = (uint8_t*)(eh + 1); |
| if ( *payload_type == IEEE80211_SNAP_TYPE_TDLS) { |
| if (vap->iv_opmode == IEEE80211_M_STA) { |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, |
| "TDLS %s: got 802.11 management over data, type=%u ptr=%p (%p)\n", |
| __func__, *payload_type, payload_type, eh); |
| ia = (struct ieee80211_action *)(payload_type + 1); |
| ieee80211_recv_action_tdls(ni, skb, ia, 0, rssi); |
| } |
| |
| if (vap->iv_opmode == IEEE80211_M_HOSTAP && (vap->hs20_enable || g_l2_ext_filter)) { |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, |
| "%s Dropping TDLS frame due to HS2.0 enabled\n", __func__); |
| return 1; |
| } |
| } else { |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, |
| "TDLS %s: unsupported type %u\n", |
| __func__, *payload_type); |
| vap->iv_stats.is_rx_mgtdiscard++; |
| } |
| } else if (ieee80211_tdls_status_mismatch(ni)) { |
| enum ieee80211_tdls_operation operation; |
| |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, |
| "TDLS %s: data not allowed before tdls link is ready, peer status: %u\n", |
| __func__, ni->tdls_status); |
| vap->iv_stats.is_rx_tdls_stsmismatch++; |
| operation = IEEE80211_TDLS_TEARDOWN; |
| ieee80211_tdls_send_event(ni, IEEE80211_EVENT_TDLS, &operation); |
| |
| return 1; |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(ieee80211_tdls_tqe_path_check); |
| |
| static int ieee80211_action_frame_check(struct ieee80211vap *vap, |
| struct sk_buff *skb, struct llc *llc, int min_len) |
| { |
| int ret = 0; |
| if ((vap->iv_opmode == IEEE80211_M_STA) && |
| (skb->len >= min_len) && |
| (llc->llc_dsap == LLC_SNAP_LSAP) && |
| (llc->llc_ssap == LLC_SNAP_LSAP) && |
| (llc->llc_control == LLC_UI) && |
| (llc->llc_snap.org_code[0] == 0) && |
| (llc->llc_snap.org_code[1] == 0) && |
| (llc->llc_snap.org_code[2] == 0) && |
| (llc->llc_un.type_snap.ether_type == |
| htons(ETHERTYPE_80211MGT))) { |
| ret = 1; |
| |
| } |
| return ret; |
| } |
| |
| static void ieee80211_tdls_mailbox_path_check(struct ieee80211_node *ni, |
| struct sk_buff *skb, struct llc *llc, int rssi, int min_len) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_action *ia; |
| uint8_t *payload_type; |
| |
| min_len += sizeof(*payload_type) + sizeof(*ia); |
| if (ieee80211_action_frame_check(vap, skb, llc, min_len)) { |
| if (unlikely(ieee80211_msg(vap, IEEE80211_MSG_TDLS) && |
| ieee80211_tdls_msg(vap, IEEE80211_TDLS_MSG_DBG))) { |
| ieee80211_dump_pkt(vap->iv_ic, skb->data, min_len, -1, rssi); |
| } |
| payload_type = (u_int8_t *)llc + LLC_SNAPFRAMELEN; |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, |
| "TDLS %s: got 802.11 management over data, type=%u ptr=%p (%p)\n", |
| __func__, *payload_type, payload_type, llc); |
| |
| if (*payload_type == IEEE80211_SNAP_TYPE_TDLS) { |
| ia = (struct ieee80211_action *)(payload_type + 1); |
| ieee80211_recv_action_tdls(ni, skb, ia, 1, rssi); |
| } else { |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, |
| "TDLS %s: unsupported type %u\n", |
| __func__, *payload_type); |
| vap->iv_stats.is_rx_mgtdiscard++; |
| } |
| } else if (ieee80211_tdls_status_mismatch(ni)) { |
| enum ieee80211_tdls_operation operation; |
| |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, |
| "TDLS %s: data not allowed before tdls link is ready, peer status: %u\n", |
| __func__, ni->tdls_status); |
| vap->iv_stats.is_rx_tdls_stsmismatch++; |
| operation = IEEE80211_TDLS_TEARDOWN; |
| ieee80211_tdls_send_event(ni, IEEE80211_EVENT_TDLS, &operation); |
| } |
| } |
| |
| static int |
| ieee80211_is_tdls_disc_resp(struct sk_buff *skb, int hdrlen) |
| { |
| struct ieee80211_action *ia = (struct ieee80211_action *)(skb->data + hdrlen); |
| |
| if (skb->len < (hdrlen + sizeof(struct ieee80211_action))) |
| return 0; |
| |
| if ((ia->ia_category == IEEE80211_ACTION_CAT_PUBLIC) && |
| (ia->ia_action == IEEE80211_ACTION_PUB_TDLS_DISC_RESP)) |
| return 1; |
| else |
| return 0; |
| } |
| |
| |
| static int |
| ieee80211_is_tdls_action_frame(struct sk_buff *skb, int hdrlen) |
| { |
| static const uint8_t snap_e_header_pref[] = {LLC_SNAP_LSAP, LLC_SNAP_LSAP, LLC_UI, 0x00, 0x00}; |
| uint8_t *data = &skb->data[hdrlen]; |
| uint16_t ether_type = get_unaligned((uint16_t*)&data[6]); |
| int32_t snap_encap_pref = !memcmp(data, snap_e_header_pref, sizeof(snap_e_header_pref)); |
| |
| return (snap_encap_pref && (ether_type == htons(ETHERTYPE_80211MGT))); |
| } |
| |
| static __inline int |
| ieee80211_tdls_frame_should_accept(struct sk_buff *skb, int type, int hdrlen) |
| { |
| return (type == IEEE80211_FC0_TYPE_DATA && ieee80211_is_tdls_action_frame(skb, hdrlen)) || |
| (type == IEEE80211_FC0_TYPE_MGT && ieee80211_is_tdls_disc_resp(skb, hdrlen)); |
| } |
| |
| static int ieee80211_input_should_drop(struct ieee80211_node *ni, uint8_t *bssid, |
| struct ieee80211_frame *wh, uint8_t type, |
| uint8_t subtype, struct sk_buff *skb) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| uint8_t dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; |
| |
| if (dir == IEEE80211_FC1_DIR_DSTODS) |
| return 0; |
| |
| #ifdef QTN_BG_SCAN |
| if ((ic->ic_flags_qtn & IEEE80211_QTN_BGSCAN) && |
| (type == IEEE80211_FC0_TYPE_MGT) && |
| (subtype == IEEE80211_FC0_SUBTYPE_BEACON || |
| subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP)) { |
| return 0; |
| } |
| #endif |
| |
| if (ic->ic_flags_qtn & IEEE80211_QTN_MONITOR) |
| return 0; |
| if ((type != IEEE80211_FC0_TYPE_CTL) && vap->tdls_over_qhop_en |
| && ieee80211_tdls_frame_should_accept(skb, type, ieee80211_hdrspace(ic, wh))) |
| return 0; |
| |
| /* PS-POLL frame in State 1 */ |
| if (IEEE80211_ADDR_EQ(ni->ni_bssid, vap->iv_myaddr) && |
| (subtype == IEEE80211_FC0_SUBTYPE_PS_POLL)) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, |
| bssid, NULL, "%s", "ps-poll in unauth state"); |
| |
| vap->iv_stats.is_rx_ps_unauth++; |
| |
| ieee80211_send_error(ni, wh->i_addr2, |
| IEEE80211_FC0_SUBTYPE_DEAUTH, |
| IEEE80211_REASON_NOT_AUTHED); |
| return 1; |
| } |
| |
| /* Packet from unknown source - send deauth. */ |
| if (ni == vap->iv_bss && !ieee80211_is_bcst(wh->i_addr1)) { |
| if (type == IEEE80211_FC0_TYPE_MGT && subtype == IEEE80211_FC0_SUBTYPE_DEAUTH) { |
| /* |
| * Corner case |
| * AP may have changed mode to STA but we are still unconscious. |
| * If Deauthentication frames from AP are dropped here, we have no chance |
| * to disconnect with AP. |
| */ |
| if (IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid)) |
| return 0; |
| } |
| |
| /* |
| * intended for Repeater AP but slip into STA interface |
| * sliently discard |
| */ |
| if (vap->iv_opmode != IEEE80211_M_STA || |
| type != IEEE80211_FC0_TYPE_MGT || |
| (subtype != IEEE80211_FC0_SUBTYPE_PROBE_REQ && |
| #if defined(PLATFORM_QFDR) |
| subtype != IEEE80211_FC0_SUBTYPE_AUTH && |
| subtype != IEEE80211_FC0_SUBTYPE_DEAUTH && |
| #endif |
| subtype != IEEE80211_FC0_SUBTYPE_ASSOC_REQ)) { |
| ieee80211_send_error(ni, wh->i_addr2, |
| IEEE80211_FC0_SUBTYPE_DEAUTH, |
| IEEE80211_REASON_NOT_AUTHED); |
| } |
| } |
| |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, |
| bssid, NULL, "not from bss %pM", ni->ni_bssid); |
| vap->iv_stats.is_rx_wrongbss++; |
| |
| return 1; |
| } |
| |
| void ieee80211_update_current_mode(struct ieee80211_node *ni) |
| { |
| struct ieee80211com *ic = ni->ni_vap->iv_ic; |
| |
| if (IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan)) { |
| if (IEEE80211_NODE_IS_VHT(ni)) { |
| ni->ni_wifi_mode = IEEE80211_WIFI_MODE_AC; |
| } else if (IEEE80211_NODE_IS_HT(ni)) { |
| ni->ni_wifi_mode = IEEE80211_WIFI_MODE_NA; |
| } else { |
| ni->ni_wifi_mode = IEEE80211_WIFI_MODE_A; |
| } |
| } else { |
| if (IEEE80211_NODE_IS_HT(ni)) { |
| ni->ni_wifi_mode = IEEE80211_WIFI_MODE_NG; |
| } else { |
| /* Check the last rate since the list was sorted */ |
| if ((ni->ni_rates.rs_rates[ni->ni_rates.rs_nrates - 1] |
| & IEEE80211_RATE_VAL) > IEEE80211_RATE_11MBPS) { |
| ni->ni_wifi_mode = IEEE80211_WIFI_MODE_G; |
| } else { |
| ni->ni_wifi_mode = IEEE80211_WIFI_MODE_B; |
| } |
| } |
| } |
| } |
| |
| static int ieee80211_input_pmf_should_drop(struct ieee80211vap *vap, |
| struct ieee80211_node *ni, struct ieee80211_frame *wh, |
| struct sk_buff *skb, u_int8_t subtype) |
| { |
| if (!ni->ni_associd || !RSN_IS_MFP(ni->ni_rsn.rsn_caps)) |
| return 0; |
| |
| if (wh->i_fc[1] & IEEE80211_FC1_PROT) { |
| wh->i_fc[1] &= ~IEEE80211_FC1_PROT; |
| return 0; |
| } |
| if ((vap->iv_opmode == IEEE80211_M_STA)) { |
| if (!ni->ni_sa_query_timeout && |
| (subtype == IEEE80211_FC0_SUBTYPE_DEAUTH || |
| subtype == IEEE80211_FC0_SUBTYPE_DISASSOC)) { |
| if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { |
| forward_mgmt_to_app(vap, subtype, skb, wh); |
| return 1; |
| } else { |
| ieee80211_send_sa_query(ni, IEEE80211_ACTION_W_SA_QUERY_REQ, |
| ++ni->ni_sa_query_tid); |
| return 1; |
| } |
| } |
| } |
| if ((subtype == IEEE80211_FC0_SUBTYPE_AUTH) && |
| ieee80211_node_is_authorized(ni)) { |
| ieee80211_send_sa_query(ni, IEEE80211_ACTION_W_SA_QUERY_REQ, |
| ++ni->ni_sa_query_tid); |
| return 1; |
| } |
| if (ieee80211_mgmt_is_robust(wh)) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* |
| * Process a received frame. The node associated with the sender |
| * should be supplied. If nothing was found in the node table then |
| * the caller is assumed to supply a reference to ic_bss instead. |
| * The RSSI and a timestamp are also supplied. The RSSI data is used |
| * during AP scanning to select a AP to associate with; it can have |
| * any units so long as values have consistent units and higher values |
| * mean ``better signal''. The receive timestamp is currently not used |
| * by the 802.11 layer. |
| * |
| * Context: softIRQ (tasklet) |
| */ |
| int |
| ieee80211_input(struct ieee80211_node *ni, |
| struct sk_buff *skb, int rssi, u_int32_t rstamp) |
| { |
| #define HAS_SEQ(type) ((type & 0x4) == 0) |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| struct net_device *dev = vap->iv_dev; |
| struct ieee80211_frame *wh; |
| struct ieee80211_key *key; |
| struct ether_header *eh; |
| struct llc *llc; |
| int hdrspace; |
| u_int8_t dir, type = -1, subtype; |
| u_int8_t *bssid; |
| u_int16_t rxseq; |
| /* Variable to track whether the node inactive timer should be reset */ |
| int node_reference_held = 0; |
| struct qtn_wds_ext_event_data extender_event_data; |
| |
| KASSERT(ni != NULL, ("null node")); |
| |
| KASSERT(skb->len >= sizeof(struct ieee80211_frame_min), |
| ("frame length too short: %u", skb->len)); |
| |
| /* XXX adjust device in sk_buff? */ |
| |
| type = -1; /* undefined */ |
| /* |
| * In monitor mode, send everything directly to bpf. |
| * Also do not process frames w/o i_addr2 any further. |
| * XXX may want to include the CRC |
| */ |
| if (vap->iv_opmode == IEEE80211_M_MONITOR) |
| goto out; |
| |
| if (skb->len < sizeof(struct ieee80211_frame_min)) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, |
| ni->ni_macaddr, NULL, |
| "too short (1): len %u", skb->len); |
| vap->iv_stats.is_rx_tooshort++; |
| goto out; |
| } |
| |
| if ((vap->iv_opmode != IEEE80211_M_STA) || IEEE80211_NODE_IS_TDLS_ACTIVE(ni)) |
| ni->ni_inact = ni->ni_inact_reload; |
| |
| /* |
| * Bit of a cheat here, we use a pointer for a 3-address |
| * frame format but don't reference fields past outside |
| * ieee80211_frame_min w/o first validating the data is |
| * present. |
| */ |
| wh = (struct ieee80211_frame *)skb->data; |
| |
| if ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) != |
| IEEE80211_FC0_VERSION_0) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, |
| ni->ni_macaddr, NULL, "wrong version %x", wh->i_fc[0]); |
| vap->iv_stats.is_rx_badversion++; |
| goto err; |
| } |
| |
| dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; |
| type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; |
| subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; |
| if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { |
| switch (vap->iv_opmode) { |
| case IEEE80211_M_STA: |
| if (dir == IEEE80211_FC1_DIR_NODS) |
| bssid = wh->i_addr3; |
| else |
| bssid = wh->i_addr2; |
| if (!IEEE80211_ADDR_EQ(bssid, ni->ni_bssid)) { |
| if (ieee80211_input_should_drop(ni, bssid, wh, type, subtype, skb)) { |
| goto out; |
| } |
| } |
| iwspy_event(vap, ni, rssi); |
| break; |
| case IEEE80211_M_IBSS: |
| case IEEE80211_M_AHDEMO: |
| if (dir != IEEE80211_FC1_DIR_NODS) |
| bssid = wh->i_addr1; |
| else if (type == IEEE80211_FC0_TYPE_CTL) |
| bssid = wh->i_addr1; |
| else { |
| if (skb->len < sizeof(struct ieee80211_frame)) { |
| IEEE80211_DISCARD_MAC(vap, |
| IEEE80211_MSG_INPUT, ni->ni_macaddr, |
| NULL, "too short (2): len %u", |
| skb->len); |
| vap->iv_stats.is_rx_tooshort++; |
| goto out; |
| } |
| bssid = wh->i_addr3; |
| } |
| /* Do not try to find a node reference if the packet really did come from the BSS */ |
| if (type == IEEE80211_FC0_TYPE_DATA && ni == vap->iv_bss && |
| !IEEE80211_ADDR_EQ(vap->iv_bss->ni_macaddr, wh->i_addr2)) { |
| /* Try to find sender in local node table. */ |
| ni = ieee80211_find_node(vap->iv_bss->ni_table, wh->i_addr2); |
| if (ni == NULL) { |
| /* |
| * Fake up a node for this newly discovered |
| * member of the IBSS. This should probably |
| * done after an ACL check. |
| */ |
| ni = ieee80211_fakeup_adhoc_node(vap, |
| wh->i_addr2); |
| if (ni == NULL) { |
| /* NB: stat kept for alloc failure */ |
| goto err; |
| } |
| } |
| node_reference_held = 1; |
| } |
| iwspy_event(vap, ni, rssi); |
| break; |
| case IEEE80211_M_HOSTAP: |
| if (dir != IEEE80211_FC1_DIR_NODS) |
| bssid = wh->i_addr1; |
| else if (type == IEEE80211_FC0_TYPE_CTL) |
| bssid = wh->i_addr1; |
| else { |
| if (skb->len < sizeof(struct ieee80211_frame)) { |
| IEEE80211_DISCARD_MAC(vap, |
| IEEE80211_MSG_INPUT, ni->ni_macaddr, |
| NULL, "too short (2): len %u", |
| skb->len); |
| vap->iv_stats.is_rx_tooshort++; |
| goto out; |
| } |
| bssid = wh->i_addr3; |
| } |
| |
| /* |
| * Validate the bssid. |
| */ |
| if (!IEEE80211_ADDR_EQ(bssid, vap->iv_bss->ni_bssid) && |
| !IEEE80211_ADDR_EQ(bssid, dev->broadcast)) { |
| /* It can be a beacon from other network. Required for certification. */ |
| vap->iv_stats.is_rx_wrongbss++; |
| if (!((type == IEEE80211_FC0_TYPE_MGT) && ((subtype == IEEE80211_FC0_SUBTYPE_BEACON) |
| #ifdef QTN_BG_SCAN |
| || ((ic->ic_flags_qtn & IEEE80211_QTN_BGSCAN) |
| && (subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP)) |
| #endif /* QTN_BG_SCAN */ |
| ))) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, |
| bssid, NULL, "%s %02X %02X", "not to bss", type, subtype); |
| goto out; |
| } |
| } |
| break; |
| case IEEE80211_M_WDS: |
| if (skb->len < sizeof(struct ieee80211_frame_addr4)) { |
| IEEE80211_DISCARD_MAC(vap, |
| IEEE80211_MSG_INPUT, ni->ni_macaddr, |
| NULL, "too short (3): len %u", |
| skb->len); |
| vap->iv_stats.is_rx_tooshort++; |
| goto out; |
| } |
| bssid = wh->i_addr1; |
| if (!IEEE80211_ADDR_EQ(bssid, vap->iv_myaddr) && |
| !IEEE80211_ADDR_EQ(bssid, dev->broadcast)) { |
| /* not interested in */ |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, |
| bssid, NULL, "%s", "not to bss"); |
| vap->iv_stats.is_rx_wrongbss++; |
| goto out; |
| } |
| if (!IEEE80211_ADDR_EQ(wh->i_addr2, vap->wds_mac)) { |
| /* not interested in */ |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, |
| wh->i_addr2, NULL, "%s", "not from DS"); |
| vap->iv_stats.is_rx_wrongbss++; |
| goto out; |
| } |
| break; |
| default: |
| /* XXX catch bad values */ |
| goto out; |
| } |
| ni->ni_rstamp = rstamp; |
| ni->ni_last_rx = jiffies; |
| if (HAS_SEQ(type)) { |
| u_int8_t tid; |
| if (IEEE80211_QOS_HAS_SEQ(wh)) { |
| tid = ((struct ieee80211_qosframe *)wh)-> |
| i_qos[0] & IEEE80211_QOS_TID; |
| if (TID_TO_WME_AC(tid) >= WME_AC_VI) |
| ic->ic_wme.wme_hipri_traffic++; |
| tid++; |
| } else |
| tid = 0; |
| rxseq = le16toh(*(__le16 *)wh->i_seq); |
| if ((wh->i_fc[1] & IEEE80211_FC1_RETRY) && |
| IEEE80211_SEQ_EQ(rxseq, ni->ni_rxseqs[tid]) && |
| !((type == IEEE80211_FC0_TYPE_MGT) && |
| (subtype == IEEE80211_FC0_SUBTYPE_AUTH)) |
| #ifdef QTN_BG_SCAN |
| && !((ic->ic_flags_qtn & IEEE80211_QTN_BGSCAN) |
| && (type == IEEE80211_FC0_TYPE_MGT) |
| && (subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP || |
| subtype == IEEE80211_FC0_SUBTYPE_BEACON)) |
| #endif /* QTN_BG_SCAN */ |
| ) { |
| /* duplicate, discard */ |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, |
| bssid, "duplicate", |
| "seqno <%u,%u> fragno <%u,%u> tid %u", |
| rxseq >> IEEE80211_SEQ_SEQ_SHIFT, |
| ni->ni_rxseqs[tid] >> |
| IEEE80211_SEQ_SEQ_SHIFT, |
| rxseq & IEEE80211_SEQ_FRAG_MASK, |
| ni->ni_rxseqs[tid] & |
| IEEE80211_SEQ_FRAG_MASK, |
| tid); |
| vap->iv_stats.is_rx_dup++; |
| IEEE80211_NODE_STAT(ni, rx_dup); |
| goto out; |
| } |
| ni->ni_rxseqs[tid] = rxseq; |
| } |
| if (node_reference_held) { |
| ieee80211_free_node(ni); |
| } |
| } |
| |
| switch (type) { |
| case IEEE80211_FC0_TYPE_DATA: |
| hdrspace = ieee80211_hdrspace(ic, wh); |
| if (skb->len < hdrspace) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "data", "too short: len %u, expecting %u", |
| skb->len, hdrspace); |
| vap->iv_stats.is_rx_tooshort++; |
| goto out; /* XXX */ |
| } |
| switch (vap->iv_opmode) { |
| case IEEE80211_M_STA: |
| if ((dir != IEEE80211_FC1_DIR_FROMDS) && |
| (dir != IEEE80211_FC1_DIR_NODS) && |
| (!((vap->iv_flags_ext & IEEE80211_FEXT_WDS) && |
| (dir == IEEE80211_FC1_DIR_DSTODS)))) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "data", "invalid dir 0x%x", dir); |
| vap->iv_stats.is_rx_wrongdir++; |
| goto out; |
| } |
| if ((dev->flags & IFF_MULTICAST) && |
| IEEE80211_IS_MULTICAST(wh->i_addr1)) { |
| if (IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_myaddr)) { |
| /* |
| * In IEEE802.11 network, multicast packet |
| * sent from me is broadcasted from AP. |
| * It should be silently discarded for |
| * SIMPLEX interface. |
| * |
| * NB: Linux has no IFF_ flag to indicate |
| * if an interface is SIMPLEX or not; |
| * so we always assume it to be true. |
| */ |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, NULL, "%s", "multicast echo"); |
| vap->iv_stats.is_rx_mcastecho++; |
| goto out; |
| } |
| /* |
| * if it is broadcast by me on behalf of |
| * a station behind me, drop it. |
| */ |
| if (vap->iv_flags_ext & IEEE80211_FEXT_WDS) { |
| struct ieee80211_node_table *nt; |
| struct ieee80211_node *ni_wds; |
| nt = &ic->ic_sta; |
| ni_wds = ieee80211_find_wds_node(nt, wh->i_addr3); |
| if (ni_wds) { |
| ieee80211_free_node(ni_wds); /* Decr ref count */ |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, NULL, "%s", |
| "multicast echo originated from node behind me"); |
| vap->iv_stats.is_rx_mcastecho++; |
| goto out; |
| } |
| } |
| } |
| break; |
| case IEEE80211_M_IBSS: |
| case IEEE80211_M_AHDEMO: |
| if (dir != IEEE80211_FC1_DIR_NODS) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "data", "invalid dir 0x%x", dir); |
| vap->iv_stats.is_rx_wrongdir++; |
| goto out; |
| } |
| /* XXX no power-save support */ |
| break; |
| case IEEE80211_M_HOSTAP: |
| /* |
| * FIXME - QOS Null check added because Quantenna image |
| * currently doesn't set the to/from DS bits. |
| */ |
| if ((dir != IEEE80211_FC1_DIR_TODS) && |
| (dir != IEEE80211_FC1_DIR_DSTODS) && |
| (subtype != IEEE80211_FC0_SUBTYPE_QOS_NULL)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "data", "invalid dir 0x%x", dir); |
| vap->iv_stats.is_rx_wrongdir++; |
| goto out; |
| } |
| /* check if source STA is associated */ |
| if (ni == vap->iv_bss) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "data", "%s", "unknown src"); |
| /* NB: caller deals with reference */ |
| if (vap->iv_state == IEEE80211_S_RUN) { |
| if ((dir == IEEE80211_FC1_DIR_DSTODS) && |
| (IEEE80211_IS_MULTICAST(wh->i_addr1))) { |
| /* |
| * Some 3rd party wds ap sends wds pkts with receiver |
| * addr as bcast/mcast which will be received by our ap |
| * and lead to a lot of deauth. But they just ignore our |
| * deauth frame. To avoid too much deauth messages, We can |
| * safely ignore them. |
| */ |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "data", "%s", "mcast wds pkt"); |
| } else { |
| ieee80211_send_error(ni, wh->i_addr2, |
| IEEE80211_FC0_SUBTYPE_DEAUTH, |
| IEEE80211_REASON_NOT_AUTHED); |
| } |
| } |
| vap->iv_stats.is_rx_notassoc++; |
| goto err; |
| } |
| if (ni->ni_associd == 0) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "data", "%s", "unassoc src"); |
| IEEE80211_SEND_MGMT(ni, |
| IEEE80211_FC0_SUBTYPE_DISASSOC, |
| IEEE80211_REASON_NOT_ASSOCED); |
| vap->iv_stats.is_rx_notassoc++; |
| goto err; |
| } |
| /* |
| * If we're a 4 address packet, make sure we have an entry in |
| * the node table for the packet source address (addr4). |
| * If not, add one. |
| */ |
| if (dir == IEEE80211_FC1_DIR_DSTODS) { |
| struct ieee80211_node_table *nt; |
| struct ieee80211_frame_addr4 *wh4; |
| struct ieee80211_node *ni_wds; |
| if (!(vap->iv_flags_ext & IEEE80211_FEXT_WDS)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "data", "%s", "4 addr not allowed"); |
| goto err; |
| } |
| wh4 = (struct ieee80211_frame_addr4 *)skb->data; |
| nt = &ic->ic_sta; |
| ni_wds = ieee80211_find_wds_node(nt, wh4->i_addr4); |
| /* Last call increments ref count if !NULL */ |
| if ((ni_wds != NULL) && (ni_wds != ni)) { |
| /* |
| * node with source address (addr4) moved |
| * to another WDS capable station. |
| */ |
| (void) ieee80211_remove_wds_addr(nt, wh4->i_addr4); |
| ieee80211_add_wds_addr(nt, ni, wh4->i_addr4, 0); |
| } |
| if (ni_wds == NULL) |
| ieee80211_add_wds_addr(nt, ni, wh4->i_addr4, 0); |
| else |
| ieee80211_free_node(ni_wds); |
| } |
| |
| /* |
| * Check for power save state change. |
| */ |
| if (!(ni->ni_flags & IEEE80211_NODE_UAPSD)) { |
| if ((wh->i_fc[1] & IEEE80211_FC1_PWR_MGT) ^ |
| (ni->ni_flags & IEEE80211_NODE_PWR_MGT)) |
| ieee80211_node_pwrsave(ni, wh->i_fc[1] & IEEE80211_FC1_PWR_MGT); |
| } else if (ni->ni_flags & IEEE80211_NODE_PS_CHANGED) { |
| int pwr_save_changed = 0; |
| IEEE80211_LOCK_IRQ(ic); |
| if ((*(__le16 *)(&wh->i_seq[0])) == ni->ni_pschangeseq) { |
| ni->ni_flags &= ~IEEE80211_NODE_PS_CHANGED; |
| pwr_save_changed = 1; |
| } |
| IEEE80211_UNLOCK_IRQ(ic); |
| if (pwr_save_changed) |
| ieee80211_node_pwrsave(ni, wh->i_fc[1] & IEEE80211_FC1_PWR_MGT); |
| } |
| break; |
| case IEEE80211_M_WDS: |
| if (dir != IEEE80211_FC1_DIR_DSTODS) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "data", "invalid dir 0x%x", dir); |
| vap->iv_stats.is_rx_wrongdir++; |
| goto out; |
| } |
| break; |
| default: |
| /* XXX here to keep compiler happy */ |
| goto out; |
| } |
| |
| /* |
| * Handle privacy requirements. Note that we |
| * must not be preempted from here until after |
| * we (potentially) call ieee80211_crypto_demic; |
| * otherwise we may violate assumptions in the |
| * crypto cipher modules used to do delayed update |
| * of replay sequence numbers. |
| */ |
| if (wh->i_fc[1] & IEEE80211_FC1_PROT) { |
| if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { |
| /* |
| * Discard encrypted frames when privacy is off. |
| */ |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "WEP", "%s", "PRIVACY off"); |
| vap->iv_stats.is_rx_noprivacy++; |
| IEEE80211_NODE_STAT(ni, rx_noprivacy); |
| goto out; |
| } |
| key = ieee80211_crypto_decap(ni, skb, hdrspace); |
| if (key == NULL) { |
| /* NB: stats+msgs handled in crypto_decap */ |
| IEEE80211_NODE_STAT(ni, rx_wepfail); |
| //FIXME: This MUST be re-enabled - it could present a security hole. |
| //Needs more thought. |
| // |
| //RK-2009-11-24: this was commented out to allow WPA2 AES fragments |
| //to pass through the slow driver path. |
| |
| //goto out; |
| } |
| wh = (struct ieee80211_frame *)skb->data; |
| wh->i_fc[1] &= ~IEEE80211_FC1_PROT; |
| } else |
| key = NULL; |
| |
| /* |
| * Next up, any fragmentation. |
| */ |
| if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { |
| skb = ieee80211_defrag(ni, skb, hdrspace); |
| if (skb == NULL) { |
| /* Fragment dropped or frame not complete yet */ |
| goto out; |
| } |
| } |
| /* |
| * Next strip any MSDU crypto bits. |
| */ |
| if (key != NULL && |
| !ieee80211_crypto_demic(vap, key, skb, hdrspace)) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, |
| ni->ni_macaddr, "data", "%s", "demic error"); |
| IEEE80211_NODE_STAT(ni, rx_demicfail); |
| goto out; |
| } |
| |
| /* TDLS data encapsulated management frame */ |
| llc = (struct llc *) (skb->data + hdrspace); |
| ieee80211_tdls_mailbox_path_check(ni, skb, llc, rssi, |
| hdrspace + LLC_SNAPFRAMELEN); |
| |
| /* |
| * Finally, strip the 802.11 header. |
| */ |
| wh = NULL; /* no longer valid, catch any uses */ |
| skb = ieee80211_decap(vap, skb, hdrspace); |
| if (skb == NULL) { |
| /* don't count Null data frames as errors */ |
| if (subtype == IEEE80211_FC0_SUBTYPE_NODATA) |
| goto out; |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, |
| ni->ni_macaddr, "data", "%s", "decap error"); |
| vap->iv_stats.is_rx_decap++; |
| IEEE80211_NODE_STAT(ni, rx_decap); |
| goto err; |
| } |
| eh = (struct ether_header *) skb->data; |
| |
| if (! accept_data_frame(vap, ni, key, skb, eh)) |
| goto out; |
| |
| vap->iv_devstats.rx_packets++; |
| vap->iv_devstats.rx_bytes += skb->len; |
| IEEE80211_NODE_STAT(ni, rx_data); |
| IEEE80211_NODE_STAT_ADD(ni, rx_bytes, skb->len); |
| ic->ic_lastdata = jiffies; |
| |
| /* if sub type is NULL DATA or QOS NULL DATA, don't send to linux protocol stack */ |
| if ((subtype == IEEE80211_FC0_SUBTYPE_NODATA) || (subtype == IEEE80211_FC0_SUBTYPE_QOS_NULL)) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_INPUT, |
| "%s: NULL or QOS NULL DATA: don't deliver to linux protocol stack\n", __func__); |
| goto out; |
| } |
| |
| ieee80211_deliver_data(ni, skb); |
| |
| return IEEE80211_FC0_TYPE_DATA; |
| |
| case IEEE80211_FC0_TYPE_MGT: |
| /* Only accept action frames and peer beacons for WDS */ |
| if (vap->iv_opmode == IEEE80211_M_WDS && |
| subtype != IEEE80211_FC0_SUBTYPE_ACTION_NOACK && |
| subtype != IEEE80211_FC0_SUBTYPE_ACTION && |
| subtype != IEEE80211_FC0_SUBTYPE_BEACON) { |
| struct ieee80211vap *pri_vap = TAILQ_FIRST(&ic->ic_vaps); |
| if ((ic->ic_extender_role == IEEE80211_EXTENDER_ROLE_MBS) && |
| ieee80211_extender_find_peer_wds_info(ic, wh->i_addr2)) { |
| IEEE80211_EXTENDER_DPRINTF(vap, IEEE80211_EXTENDER_MSG_WARN, |
| "QHop: unexpected frame 0x%x from peer %pM\n", |
| subtype, wh->i_addr2); |
| extender_event_data_prepare(ic, NULL, |
| &extender_event_data, |
| WDS_EXT_LINK_STATUS_UPDATE, |
| wh->i_addr2); |
| |
| ieee80211_extender_send_event(pri_vap, &extender_event_data, NULL); |
| ieee80211_extender_remove_peer_wds_info(ic, wh->i_addr2); |
| } |
| vap->iv_stats.is_rx_mgtdiscard++; |
| goto out; |
| } |
| IEEE80211_NODE_STAT(ni, rx_mgmt); |
| |
| if (dir != IEEE80211_FC1_DIR_NODS) { |
| vap->iv_stats.is_rx_wrongdir++; |
| goto err; |
| } |
| if (skb->len < sizeof(struct ieee80211_frame)) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, |
| ni->ni_macaddr, "mgt", "too short: len %u", |
| skb->len); |
| vap->iv_stats.is_rx_tooshort++; |
| goto out; |
| } |
| #ifdef IEEE80211_DEBUG |
| if ((ieee80211_msg_debug(vap) && doprint(vap, subtype)) || |
| ieee80211_msg_dumppkts(vap)) { |
| ieee80211_note(vap, "received %s from %s rssi %d\n", |
| ieee80211_mgt_subtype_name[subtype >> |
| IEEE80211_FC0_SUBTYPE_SHIFT], |
| ether_sprintf(wh->i_addr2), rssi); |
| } |
| #endif |
| |
| if (vap->iv_pmf) { |
| if (ieee80211_input_pmf_should_drop(vap, ni, wh, skb, subtype)) |
| goto out; |
| } |
| |
| if (wh->i_fc[1] & IEEE80211_FC1_PROT) { |
| |
| if (subtype != IEEE80211_FC0_SUBTYPE_AUTH) { |
| /* |
| * Only shared key auth frames with a challenge |
| * should be encrypted, discard all others. |
| */ |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, ieee80211_mgt_subtype_name[subtype >> |
| IEEE80211_FC0_SUBTYPE_SHIFT], |
| "%s", "WEP set but not permitted"); |
| vap->iv_stats.is_rx_mgtdiscard++; /* XXX */ |
| goto out; |
| } |
| if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { |
| /* |
| * Discard encrypted frames when privacy is off. |
| */ |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "WEP set but PRIVACY off"); |
| vap->iv_stats.is_rx_noprivacy++; |
| goto out; |
| } |
| hdrspace = ieee80211_hdrspace(ic, wh); |
| key = ieee80211_crypto_decap(ni, skb, hdrspace); |
| if (key == NULL) { |
| /* NB: stats+msgs handled in crypto_decap */ |
| goto out; |
| } |
| wh = (struct ieee80211_frame *)skb->data; |
| wh->i_fc[1] &= ~IEEE80211_FC1_PROT; |
| } |
| ic->ic_recv_mgmt(ni, skb, subtype, rssi, rstamp); |
| |
| goto out; |
| |
| case IEEE80211_FC0_TYPE_CTL: { |
| u_int8_t reason; |
| IEEE80211_NODE_STAT(ni, rx_ctrl); |
| vap->iv_stats.is_rx_ctl++; |
| if (vap->iv_opmode == IEEE80211_M_HOSTAP) |
| if (subtype == IEEE80211_FC0_SUBTYPE_PS_POLL) |
| ieee80211_recv_pspoll(ni, skb); |
| |
| /*if a sta receive a PS-POLL, a deauth should be sent*/ |
| if (vap->iv_opmode == IEEE80211_M_STA && |
| subtype == IEEE80211_FC0_SUBTYPE_PS_POLL && |
| vap->iv_state < IEEE80211_S_RUN) { |
| if (vap->iv_state <= IEEE80211_S_AUTH) { |
| reason = IEEE80211_REASON_NOT_AUTHED; |
| } else { |
| reason = IEEE80211_REASON_NOT_ASSOCED; |
| } |
| |
| IEEE80211_DISCARD(vap, |
| IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG, |
| wh, "receive ps-poll", "state-%d, send deauth", |
| ((reason == IEEE80211_REASON_NOT_AUTHED) ? 1:2)); |
| |
| vap->iv_stats.is_ps_unassoc++; |
| ieee80211_send_error(ni, wh->i_addr2, |
| IEEE80211_FC0_SUBTYPE_DEAUTH, reason); |
| } |
| |
| goto out; |
| } |
| |
| default: |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, NULL, "bad frame type 0x%x", type); |
| /* should not come here */ |
| break; |
| } |
| err: |
| vap->iv_devstats.rx_errors++; |
| out: |
| |
| if (skb != NULL) |
| dev_kfree_skb(skb); |
| return type; |
| #undef HAS_SEQ |
| } |
| EXPORT_SYMBOL(ieee80211_input); |
| |
| |
| /* |
| * Determines whether a frame should be accepted, based on information |
| * about the frame's origin and encryption, and policy for this vap. |
| */ |
| static int accept_data_frame(struct ieee80211vap *vap, |
| struct ieee80211_node *ni, struct ieee80211_key *key, |
| struct sk_buff *skb, struct ether_header *eh) |
| { |
| #define IS_EAPOL(eh) ((eh)->ether_type == __constant_htons(ETH_P_PAE)) |
| #define PAIRWISE_SET(vap) ((vap)->iv_nw_keys[0].wk_cipher != &ieee80211_cipher_none) |
| if (IS_EAPOL(eh)) { |
| /* encrypted eapol is always OK */ |
| if (key) |
| return 1; |
| /* cleartext eapol is OK if we don't have pairwise keys yet */ |
| if (! PAIRWISE_SET(vap)) |
| return 1; |
| /* cleartext eapol is OK if configured to allow it */ |
| if (! IEEE80211_VAP_DROPUNENC_EAPOL(vap)) |
| return 1; |
| /* cleartext eapol is OK if other unencrypted is OK */ |
| if (! (vap->iv_flags & IEEE80211_F_DROPUNENC)) |
| return 1; |
| /* not OK */ |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, |
| eh->ether_shost, "data", |
| "unauthorized port: ether type 0x%x len %u", |
| ntohs(eh->ether_type), skb->len); |
| vap->iv_stats.is_rx_unauth++; |
| vap->iv_devstats.rx_errors++; |
| IEEE80211_NODE_STAT(ni, rx_unauth); |
| return 0; |
| } |
| |
| if (!ieee80211_node_is_authorized(ni)) { |
| /* |
| * Deny any non-PAE frames received prior to |
| * authorization. For open/shared-key |
| * authentication the port is mark authorized |
| * after authentication completes. For 802.1x |
| * the port is not marked authorized by the |
| * authenticator until the handshake has completed. |
| */ |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, |
| eh->ether_shost, "data", |
| "unauthorized port: ether type 0x%x len %u", |
| ntohs(eh->ether_type), skb->len); |
| vap->iv_stats.is_rx_unauth++; |
| vap->iv_devstats.rx_errors++; |
| IEEE80211_NODE_STAT(ni, rx_unauth); |
| return 0; |
| } |
| |
| return 1; |
| |
| #undef IS_EAPOL |
| #undef PAIRWISE_SET |
| } |
| |
| /* |
| * Context: softIRQ (tasklet) |
| */ |
| int |
| ieee80211_input_all(struct ieee80211com *ic, |
| struct sk_buff *skb, int rssi, u_int32_t rstamp) |
| { |
| struct ieee80211vap *vap; |
| int type = -1; |
| struct sk_buff *skb1; |
| struct ieee80211_node *ni; |
| |
| TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { |
| if (vap->iv_opmode == IEEE80211_M_WDS) { |
| /* Discard input from non-peer */ |
| continue; |
| } |
| |
| if (TAILQ_NEXT(vap, iv_next) != NULL) { |
| skb1 = skb_copy(skb, GFP_ATOMIC); |
| if (skb1 == NULL) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_INPUT, |
| "%s: SKB copy failed\n", __func__); |
| continue; |
| } |
| } else { |
| skb1 = skb; |
| skb = NULL; |
| } |
| |
| ni = vap->iv_bss; |
| ieee80211_ref_node(ni); |
| type = ieee80211_input(ni, skb1, rssi, rstamp); |
| ieee80211_free_node(ni); |
| } |
| |
| /* No more vaps, reclaim skb */ |
| if (skb != NULL) |
| dev_kfree_skb(skb); |
| |
| return type; |
| } |
| EXPORT_SYMBOL(ieee80211_input_all); |
| |
| /* |
| * This function reassemble fragments using the skb of the 1st fragment, |
| * if large enough. If not, a new skb is allocated to hold incoming |
| * fragments. |
| * |
| * Fragments are copied at the end of the previous fragment. A different |
| * strategy could have been used, where a non-linear skb is allocated and |
| * fragments attached to that skb. |
| */ |
| static struct sk_buff * |
| ieee80211_defrag(struct ieee80211_node *ni, struct sk_buff *skb, int hdrlen) |
| { |
| struct ieee80211_frame *wh = (struct ieee80211_frame *) skb->data; |
| u_int16_t rxseq, last_rxseq; |
| u_int8_t fragno, last_fragno; |
| u_int8_t more_frag = wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG; |
| |
| rxseq = le16_to_cpu(*(__le16 *)wh->i_seq) >> IEEE80211_SEQ_SEQ_SHIFT; |
| fragno = le16_to_cpu(*(__le16 *)wh->i_seq) & IEEE80211_SEQ_FRAG_MASK; |
| |
| /* Quick way out, if there's nothing to defragment */ |
| if (!more_frag && fragno == 0 && ni->ni_rxfrag == NULL) |
| return skb; |
| |
| ni->ni_stats.ns_rx_fragment_pkts++; |
| |
| /* |
| * Remove frag to ensure it doesn't get reaped by timer. |
| */ |
| if (ni->ni_table == NULL) { |
| /* |
| * Should never happen. If the node is orphaned (not in |
| * the table) then input packets should not reach here. |
| * Otherwise, a concurrent request that yanks the table |
| * should be blocked by other interlocking and/or by first |
| * shutting the driver down. Regardless, be defensive |
| * here and just bail |
| */ |
| /* XXX need msg+stat */ |
| dev_kfree_skb(skb); |
| return NULL; |
| } |
| |
| /* |
| * Use this lock to make sure ni->ni_rxfrag is |
| * not freed by the timer process while we use it. |
| * XXX bogus |
| */ |
| IEEE80211_NODE_LOCK_IRQ(ni->ni_table); |
| |
| /* |
| * Update the time stamp. As a side effect, it |
| * also makes sure that the timer will not change |
| * ni->ni_rxfrag for at least 1 second, or in |
| * other words, for the remaining of this function. |
| */ |
| ni->ni_rxfragstamp = jiffies; |
| |
| IEEE80211_NODE_UNLOCK_IRQ(ni->ni_table); |
| |
| /* |
| * Validate that fragment is in order and |
| * related to the previous ones. |
| */ |
| if (ni->ni_rxfrag) { |
| struct ieee80211_frame *lwh; |
| |
| lwh = (struct ieee80211_frame *) ni->ni_rxfrag->data; |
| last_rxseq = le16_to_cpu(*(__le16 *)lwh->i_seq) >> |
| IEEE80211_SEQ_SEQ_SHIFT; |
| last_fragno = le16_to_cpu(*(__le16 *)lwh->i_seq) & |
| IEEE80211_SEQ_FRAG_MASK; |
| if (rxseq != last_rxseq |
| || fragno != last_fragno + 1 |
| || (!IEEE80211_ADDR_EQ(wh->i_addr1, lwh->i_addr1)) |
| || (!IEEE80211_ADDR_EQ(wh->i_addr2, lwh->i_addr2)) |
| || (ni->ni_rxfrag->end - ni->ni_rxfrag->tail < |
| skb->len)) { |
| /* |
| * Unrelated fragment or no space for it, |
| * clear current fragments |
| */ |
| dev_kfree_skb(ni->ni_rxfrag); |
| ni->ni_rxfrag = NULL; |
| } |
| } |
| |
| /* If this is the first fragment */ |
| if (ni->ni_rxfrag == NULL && fragno == 0) { |
| ni->ni_rxfrag = skb; |
| /* If more frags are coming */ |
| if (more_frag) { |
| if (skb_is_nonlinear(skb)) { |
| /* |
| * We need a continous buffer to |
| * assemble fragments |
| */ |
| ni->ni_rxfrag = skb_copy(skb, GFP_ATOMIC); |
| dev_kfree_skb(skb); |
| } |
| /* |
| * Check that we have enough space to hold |
| * incoming fragments |
| * 1. Don't assume MTU is the RX frame size limit. |
| * 2. Don't assume original packet starts from skb->head, in case |
| * kernel reserve some bytes at headroom. |
| */ |
| else if ((skb_end_pointer(skb) - skb->data) < |
| (IEEE80211_MAX_LEN + hdrlen)) { |
| ni->ni_rxfrag = skb_copy_expand(skb, 0, |
| (IEEE80211_MAX_LEN + hdrlen - skb->len), |
| GFP_ATOMIC); |
| dev_kfree_skb(skb); |
| } |
| } |
| } else { |
| if (ni->ni_rxfrag) { |
| struct ieee80211_frame *lwh = (struct ieee80211_frame *) |
| ni->ni_rxfrag->data; |
| |
| /* |
| * We know we have enough space to copy, |
| * we've verified that before |
| */ |
| /* Copy current fragment at end of previous one */ |
| memcpy(skb_tail_pointer(ni->ni_rxfrag), |
| skb->data + hdrlen, skb->len - hdrlen); |
| /* Update tail and length */ |
| skb_put(ni->ni_rxfrag, skb->len - hdrlen); |
| /* Keep a copy of last sequence and fragno */ |
| *(__le16 *) lwh->i_seq = *(__le16 *) wh->i_seq; |
| } |
| /* we're done with the fragment */ |
| dev_kfree_skb(skb); |
| } |
| |
| if (more_frag) { |
| /* More to come */ |
| skb = NULL; |
| } else { |
| /* Last fragment received, we're done! */ |
| skb = ni->ni_rxfrag; |
| ni->ni_rxfrag = NULL; |
| } |
| return skb; |
| } |
| |
| static void |
| ieee80211_deliver_data(struct ieee80211_node *ni, struct sk_buff *skb) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct net_device *dev = vap->iv_dev; |
| struct ether_header *eh = (struct ether_header *) skb->data; |
| |
| if (unlikely(g_l2_ext_filter)) { |
| if (!skb->ext_l2_filter && vap->iv_opmode == IEEE80211_M_HOSTAP) { |
| if (!skb->dev) |
| skb->dev = dev; |
| |
| #ifdef USE_HEADERLEN_RESV |
| skb->protocol = ath_eth_type_trans(skb, skb->dev); |
| #else |
| skb->protocol = eth_type_trans(skb, skb->dev); |
| #endif |
| if (!(skb->protocol == __constant_htons(ETH_P_PAE) && |
| IEEE80211_ADDR_EQ(eh->ether_dhost, vap->iv_myaddr))) { |
| vap->iv_ic->ic_send_to_l2_ext_filter(vap, skb); |
| return ; |
| } |
| } |
| } |
| |
| /* |
| * perform as a bridge within the vap |
| * - intra-vap bridging only |
| */ |
| if (vap->iv_opmode == IEEE80211_M_HOSTAP && |
| (vap->iv_flags & IEEE80211_F_NOBRIDGE) == 0) { |
| struct sk_buff *skb1 = NULL; |
| |
| if (ETHER_IS_MULTICAST(eh->ether_dhost)) { |
| skb1 = skb_copy(skb, GFP_ATOMIC); |
| } else { |
| /* |
| * Check if destination is associated with the |
| * same vap and authorized to receive traffic. |
| * Beware of traffic destined for the vap itself; |
| * sending it will not work; just let it be |
| * delivered normally. |
| */ |
| struct ieee80211_node *ni1 = ieee80211_find_node( |
| &vap->iv_ic->ic_sta, eh->ether_dhost); |
| if (ni1 != NULL) { |
| if (ni1->ni_vap == vap && |
| ieee80211_node_is_authorized(ni1) && |
| ni1 != vap->iv_bss) { |
| skb1 = skb; |
| skb = NULL; |
| } |
| /* XXX statistic? */ |
| ieee80211_free_node(ni1); |
| } |
| } |
| if (skb1 != NULL) { |
| skb1->dev = dev; |
| |
| skb_reset_mac_header(skb1); |
| skb_set_network_header(skb1, sizeof(struct ether_header)); |
| |
| skb1->protocol = __constant_htons(ETH_P_802_2); |
| /* XXX insert vlan tag before queue it? */ |
| dev_queue_xmit(skb1); |
| } |
| } |
| |
| if (skb != NULL) { |
| if (!skb->dev) |
| skb->dev = dev; |
| |
| #ifdef USE_HEADERLEN_RESV |
| skb->protocol = ath_eth_type_trans(skb, skb->dev); |
| #else |
| skb->protocol = eth_type_trans(skb, skb->dev); |
| #endif |
| if (ni->ni_vlan != 0 && vap->iv_vlgrp != NULL) { |
| /* TODO: There is no equivalent function in 4.7. For now lets |
| * just pass this skb to upper layer |
| */ |
| #ifdef QTN_ENABLE_BRIDGE |
| /* attach vlan tag */ |
| vlan_hwaccel_receive_skb(skb, vap->iv_vlgrp, ni->ni_vlan); |
| #else |
| netif_rx(skb); |
| #endif |
| } else { |
| netif_rx(skb); |
| } |
| dev->last_rx = jiffies; |
| } |
| } |
| |
| static struct sk_buff * |
| ieee80211_decap(struct ieee80211vap *vap, struct sk_buff *skb, int hdrlen) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211_qosframe_addr4 wh; /* Max size address frames */ |
| struct ether_header *eh; |
| struct llc *llc; |
| __be16 ether_type = 0; |
| |
| memcpy(&wh, skb->data, hdrlen); /* Only copy hdrlen over */ |
| llc = (struct llc *) skb_pull(skb, hdrlen); |
| if (skb->len >= LLC_SNAPFRAMELEN && |
| llc->llc_dsap == LLC_SNAP_LSAP && llc->llc_ssap == LLC_SNAP_LSAP && |
| llc->llc_control == LLC_UI && llc->llc_snap.org_code[0] == 0 && |
| llc->llc_snap.org_code[1] == 0 && llc->llc_snap.org_code[2] == 0) { |
| ether_type = llc->llc_un.type_snap.ether_type; |
| skb_pull(skb, LLC_SNAPFRAMELEN); |
| llc = NULL; |
| } |
| eh = (struct ether_header *) skb_push(skb, sizeof(struct ether_header)); |
| switch (wh.i_fc[1] & IEEE80211_FC1_DIR_MASK) { |
| case IEEE80211_FC1_DIR_NODS: |
| IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr1); |
| /* |
| * for TDLS Function, TDLS link with third-party station |
| * which is 3-address mode. |
| */ |
| ic->ic_bridge_set_dest_addr(skb, (void *)eh); |
| IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr2); |
| break; |
| case IEEE80211_FC1_DIR_TODS: |
| IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3); |
| IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr2); |
| break; |
| case IEEE80211_FC1_DIR_FROMDS: |
| IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr1); |
| ic->ic_bridge_set_dest_addr(skb, (void *)eh); |
| IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr3); |
| break; |
| case IEEE80211_FC1_DIR_DSTODS: |
| IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3); |
| /* |
| * for TDLS Function, associate with third-party AP |
| * which is 3-address mode. |
| */ |
| if (IEEE80211_ADDR_EQ(wh.i_addr1, wh.i_addr3)) |
| ic->ic_bridge_set_dest_addr(skb, (void *)eh); |
| IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr4); |
| break; |
| } |
| if (!ALIGNED_POINTER(skb->data + sizeof(*eh), u_int32_t)) { |
| struct sk_buff *n; |
| |
| /* XXX does this always work? */ |
| n = skb_copy(skb, GFP_ATOMIC); |
| dev_kfree_skb(skb); |
| if (n == NULL) |
| return NULL; |
| skb = n; |
| eh = (struct ether_header *) skb->data; |
| } |
| if (llc != NULL) |
| eh->ether_type = htons(skb->len - sizeof(*eh)); |
| else |
| eh->ether_type = ether_type; |
| return skb; |
| } |
| |
| int |
| ieee80211_parse_rates(struct ieee80211_node *ni, |
| const u_int8_t *rates, const u_int8_t *xrates) |
| { |
| struct ieee80211_rateset *rs = &ni->ni_rates; |
| |
| memset(rs, 0, sizeof(*rs)); |
| rs->rs_nrates = rates[1]; |
| rs->rs_legacy_nrates = rates[1]; |
| memcpy(rs->rs_rates, rates + 2, rs->rs_nrates); |
| if (xrates != NULL) { |
| u_int8_t nxrates = 0; |
| /* |
| * Tack on 11g extended supported rate element. |
| */ |
| nxrates = xrates[1]; |
| if (rs->rs_nrates + nxrates > IEEE80211_RATE_MAXSIZE) { |
| struct ieee80211vap *vap = ni->ni_vap; |
| |
| nxrates = IEEE80211_RATE_MAXSIZE - rs->rs_nrates; |
| IEEE80211_NOTE(vap, IEEE80211_MSG_XRATE, ni, |
| "extended rate set too large;" |
| " only using %u of %u rates", |
| nxrates, xrates[1]); |
| vap->iv_stats.is_rx_rstoobig++; |
| } |
| memcpy(rs->rs_rates + rs->rs_nrates, xrates+2, nxrates); |
| rs->rs_nrates += nxrates; |
| rs->rs_legacy_nrates += nxrates; |
| } |
| |
| return 1; |
| } |
| |
| /* |
| * Install received rate set information in the node's state block. |
| */ |
| int |
| ieee80211_setup_rates(struct ieee80211_node *ni, |
| const u_int8_t *rates, const u_int8_t *xrates, int flags) |
| { |
| struct ieee80211_rateset *rs = &ni->ni_rates; |
| |
| memset(rs, 0, sizeof(*rs)); |
| rs->rs_nrates = rates[1]; |
| rs->rs_legacy_nrates = rates[1]; |
| memcpy(rs->rs_rates, rates + 2, rs->rs_nrates); |
| if (xrates != NULL) { |
| u_int8_t nxrates = 0; |
| /* |
| * Tack on 11g extended supported rate element. |
| */ |
| nxrates = xrates[1]; |
| if (rs->rs_nrates + nxrates > IEEE80211_RATE_MAXSIZE) { |
| struct ieee80211vap *vap = ni->ni_vap; |
| |
| nxrates = IEEE80211_RATE_MAXSIZE - rs->rs_nrates; |
| IEEE80211_NOTE(vap, IEEE80211_MSG_XRATE, ni, |
| "extended rate set too large;" |
| " only using %u of %u rates", |
| nxrates, xrates[1]); |
| vap->iv_stats.is_rx_rstoobig++; |
| } |
| memcpy(rs->rs_rates + rs->rs_nrates, xrates+2, nxrates); |
| rs->rs_nrates += nxrates; |
| rs->rs_legacy_nrates += nxrates; |
| } |
| return ieee80211_fix_rate(ni, flags); |
| } |
| |
| static void |
| ieee80211_auth_open(struct ieee80211_node *ni, struct ieee80211_frame *wh, |
| int rssi, u_int32_t rstamp, u_int16_t seq, u_int16_t status) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| int node_reference_held = 0; |
| |
| if (ni->ni_authmode == IEEE80211_AUTH_SHARED) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, |
| ni->ni_macaddr, "open auth", |
| "bad sta auth mode %u", ni->ni_authmode); |
| vap->iv_stats.is_rx_bad_auth++; /* XXX maybe a unique error? */ |
| if (vap->iv_opmode == IEEE80211_M_HOSTAP) { |
| /* |
| * To send the frame to the requesting STA we have to create a node |
| * for the station that we're going to reject. |
| */ |
| if (ni == vap->iv_bss) { |
| ni = ieee80211_tmp_node(vap, wh->i_addr2); |
| if (ni == NULL) { |
| return; |
| } |
| node_reference_held = 1; |
| } |
| |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, |
| (seq + 1) | (IEEE80211_STATUS_ALG << 16)); |
| |
| if (node_reference_held) { |
| ieee80211_free_node(ni); |
| } |
| return; |
| } |
| } |
| switch (vap->iv_opmode) { |
| case IEEE80211_M_IBSS: |
| if (vap->iv_state != IEEE80211_S_RUN || |
| seq != IEEE80211_AUTH_OPEN_REQUEST) { |
| vap->iv_stats.is_rx_bad_auth++; |
| return; |
| } |
| ieee80211_new_state(vap, IEEE80211_S_AUTH, |
| wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK); |
| break; |
| |
| case IEEE80211_M_AHDEMO: |
| case IEEE80211_M_WDS: |
| /* should not come here */ |
| break; |
| |
| case IEEE80211_M_HOSTAP: |
| if (vap->iv_state != IEEE80211_S_RUN || |
| seq != IEEE80211_AUTH_OPEN_REQUEST) { |
| vap->iv_stats.is_rx_bad_auth++; |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_AUTH_FAILS, 1); |
| return; |
| } |
| /* always accept open authentication requests */ |
| if (ni == vap->iv_bss) { |
| ni = ieee80211_dup_bss(vap, wh->i_addr2); |
| if (ni == NULL) { |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_AUTH_FAILS, 1); |
| return; |
| } |
| ni->ni_node_type = IEEE80211_NODE_TYPE_STA; |
| node_reference_held = 1; |
| } |
| |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); |
| IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, |
| ni, "station authenticated (%s)", "open"); |
| |
| if (node_reference_held) { |
| ieee80211_free_node(ni); |
| } |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_AUTH, 1); |
| break; |
| |
| case IEEE80211_M_STA: |
| if (vap->iv_state != IEEE80211_S_AUTH || |
| seq != IEEE80211_AUTH_OPEN_RESPONSE) { |
| vap->iv_stats.is_rx_bad_auth++; |
| return; |
| } |
| if (status != 0) { |
| IEEE80211_NOTE(vap, |
| IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni, |
| "open auth failed (reason %d)", status); |
| vap->iv_stats.is_rx_auth_fail++; |
| ieee80211_new_state(vap, IEEE80211_S_SCAN, |
| IEEE80211_SCAN_FAIL_STATUS); |
| } else |
| ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); |
| break; |
| case IEEE80211_M_MONITOR: |
| break; |
| } |
| } |
| |
| /* |
| * Send a management frame error response to the specified |
| * station. If ni is associated with the station then use |
| * it; otherwise allocate a temporary node suitable for |
| * transmitting the frame and then free the reference so |
| * it will go away as soon as the frame has been transmitted. |
| */ |
| static void |
| ieee80211_send_error(struct ieee80211_node *ni, |
| const u_int8_t *mac, int subtype, int arg) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| int node_reference_held = 0; |
| |
| if (ni == vap->iv_bss) { |
| if (vap->iv_opmode == IEEE80211_M_STA) { |
| ni = _ieee80211_tmp_node(vap, mac, mac); |
| } else { |
| ni = ieee80211_tmp_node(vap, mac); |
| } |
| if (ni == NULL) { |
| return; |
| } |
| node_reference_held = 1; |
| } |
| |
| IEEE80211_SEND_MGMT(ni, subtype, arg); |
| |
| if (node_reference_held) { |
| ieee80211_free_node(ni); |
| } |
| } |
| |
| static int |
| alloc_challenge(struct ieee80211_node *ni) |
| { |
| if (ni->ni_challenge == NULL) |
| MALLOC(ni->ni_challenge, u_int32_t*, IEEE80211_CHALLENGE_LEN, |
| M_DEVBUF, M_NOWAIT); |
| if (ni->ni_challenge == NULL) { |
| IEEE80211_NOTE(ni->ni_vap, |
| IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni, |
| "%s", "shared key challenge alloc failed"); |
| /* XXX statistic */ |
| } |
| return (ni->ni_challenge != NULL); |
| } |
| |
| /* XXX TODO: add statistics */ |
| static void |
| ieee80211_auth_shared(struct ieee80211_node *ni, struct ieee80211_frame *wh, |
| u_int8_t *frm, u_int8_t *efrm, int rssi, u_int32_t rstamp, |
| u_int16_t seq, u_int16_t status) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| u_int8_t *challenge; |
| int node_reference_held = 0; |
| int estatus; |
| |
| /* |
| * NB: this can happen as we allow pre-shared key |
| * authentication to be enabled w/o wep being turned |
| * on so that configuration of these can be done |
| * in any order. It may be better to enforce the |
| * ordering in which case this check would just be |
| * for sanity/consistency. |
| */ |
| estatus = 0; /* NB: silence compiler */ |
| if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, |
| ni->ni_macaddr, "shared key auth", |
| "%s", " PRIVACY is disabled"); |
| estatus = IEEE80211_STATUS_ALG; |
| goto bad; |
| } |
| /* |
| * Pre-shared key authentication is evil; accept |
| * it only if explicitly configured (it is supported |
| * mainly for compatibility with clients like OS X). |
| */ |
| if (ni->ni_authmode != IEEE80211_AUTH_AUTO && |
| ni->ni_authmode != IEEE80211_AUTH_SHARED) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, |
| ni->ni_macaddr, "shared key auth", |
| "bad sta auth mode %u", ni->ni_authmode); |
| vap->iv_stats.is_rx_bad_auth++; /* XXX maybe a unique error? */ |
| estatus = IEEE80211_STATUS_ALG; |
| goto bad; |
| } |
| |
| challenge = NULL; |
| if (frm + 1 < efrm) { |
| if ((frm[1] + 2) > (efrm - frm)) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, |
| ni->ni_macaddr, "shared key auth", |
| "ie %d/%d too long", |
| frm[0], (frm[1] + 2) - (efrm - frm)); |
| vap->iv_stats.is_rx_bad_auth++; |
| estatus = IEEE80211_STATUS_CHALLENGE; |
| goto bad; |
| } |
| if (*frm == IEEE80211_ELEMID_CHALLENGE) |
| challenge = frm; |
| frm += frm[1] + 2; |
| } |
| switch (seq) { |
| case IEEE80211_AUTH_SHARED_CHALLENGE: |
| case IEEE80211_AUTH_SHARED_RESPONSE: |
| if (challenge == NULL) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, |
| ni->ni_macaddr, "shared key auth", |
| "%s", "no challenge"); |
| vap->iv_stats.is_rx_bad_auth++; |
| estatus = IEEE80211_STATUS_CHALLENGE; |
| goto bad; |
| } |
| if (challenge[1] != IEEE80211_CHALLENGE_LEN) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, |
| ni->ni_macaddr, "shared key auth", |
| "bad challenge len %d", challenge[1]); |
| vap->iv_stats.is_rx_bad_auth++; |
| estatus = IEEE80211_STATUS_CHALLENGE; |
| goto bad; |
| } |
| default: |
| break; |
| } |
| switch (vap->iv_opmode) { |
| case IEEE80211_M_MONITOR: |
| case IEEE80211_M_AHDEMO: |
| case IEEE80211_M_IBSS: |
| case IEEE80211_M_WDS: |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, |
| ni->ni_macaddr, "shared key auth", |
| "bad operating mode %u", vap->iv_opmode); |
| return; |
| case IEEE80211_M_HOSTAP: |
| if (vap->iv_state != IEEE80211_S_RUN) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, |
| ni->ni_macaddr, "shared key auth", |
| "bad state %u", vap->iv_state); |
| estatus = IEEE80211_STATUS_ALG; /* XXX */ |
| goto bad; |
| } |
| |
| switch (seq) { |
| case IEEE80211_AUTH_SHARED_REQUEST: |
| if (ni == vap->iv_bss) { |
| ni = ieee80211_dup_bss(vap, wh->i_addr2); |
| if (ni == NULL) { |
| return; |
| } |
| ni->ni_node_type = IEEE80211_NODE_TYPE_STA; |
| node_reference_held = 1; |
| } |
| ni->ni_rssi = rssi; |
| ni->ni_rstamp = rstamp; |
| ni->ni_last_rx = jiffies; |
| if (!alloc_challenge(ni)) { |
| /* NB: don't return error so they rexmit */ |
| if (node_reference_held) { |
| ieee80211_free_node(ni); |
| } |
| return; |
| } |
| get_random_bytes(ni->ni_challenge, |
| IEEE80211_CHALLENGE_LEN); |
| IEEE80211_NOTE(vap, |
| IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni, |
| "shared key %sauth request", node_reference_held ? "" : "re"); |
| break; |
| case IEEE80211_AUTH_SHARED_RESPONSE: |
| if (ni == vap->iv_bss) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, |
| ni->ni_macaddr, "shared key response", |
| "%s", "unknown station"); |
| /* NB: don't send a response */ |
| return; |
| } |
| if (ni->ni_challenge == NULL) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, |
| ni->ni_macaddr, "shared key response", |
| "%s", "no challenge recorded"); |
| vap->iv_stats.is_rx_bad_auth++; |
| estatus = IEEE80211_STATUS_CHALLENGE; |
| goto bad; |
| } |
| if (memcmp(ni->ni_challenge, &challenge[2], |
| challenge[1]) != 0) { |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, |
| ni->ni_macaddr, "shared key response", |
| "%s", "challenge mismatch"); |
| vap->iv_stats.is_rx_auth_fail++; |
| estatus = IEEE80211_STATUS_CHALLENGE; |
| goto bad; |
| } |
| IEEE80211_NOTE(vap, |
| IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni, |
| "station authenticated (%s)", "shared key"); |
| ieee80211_node_authorize(ni); |
| break; |
| default: |
| IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, |
| ni->ni_macaddr, "shared key auth", |
| "bad seq %d", seq); |
| vap->iv_stats.is_rx_bad_auth++; |
| estatus = IEEE80211_STATUS_SEQUENCE; |
| goto bad; |
| } |
| |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); |
| |
| if (node_reference_held) { |
| ieee80211_free_node(ni); |
| } |
| |
| break; |
| |
| case IEEE80211_M_STA: |
| if (vap->iv_state != IEEE80211_S_AUTH) |
| return; |
| switch (seq) { |
| case IEEE80211_AUTH_SHARED_PASS: |
| if (ni->ni_challenge != NULL) { |
| FREE(ni->ni_challenge, M_DEVBUF); |
| ni->ni_challenge = NULL; |
| } |
| if (status != 0) { |
| IEEE80211_NOTE_MAC(vap, |
| IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, |
| ieee80211_getbssid(vap, wh), |
| "shared key auth failed (reason %d)", |
| status); |
| vap->iv_stats.is_rx_auth_fail++; |
| /* XXX IEEE80211_SCAN_FAIL_STATUS */ |
| goto bad; |
| } |
| ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); |
| break; |
| case IEEE80211_AUTH_SHARED_CHALLENGE: |
| if (!alloc_challenge(ni)) |
| goto bad; |
| /* XXX could optimize by passing recvd challenge */ |
| memcpy(ni->ni_challenge, &challenge[2], challenge[1]); |
| IEEE80211_SEND_MGMT(ni, |
| IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); |
| break; |
| default: |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH, |
| wh, "shared key auth", "bad seq %d", seq); |
| vap->iv_stats.is_rx_bad_auth++; |
| goto bad; |
| } |
| break; |
| } |
| if(vap->iv_opmode == IEEE80211_M_HOSTAP) { |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_AUTH, 1); |
| } |
| return; |
| bad: |
| /* |
| * Send an error response; but only when operating as an AP. |
| */ |
| if (vap->iv_opmode == IEEE80211_M_HOSTAP) { |
| /* XXX hack to workaround calling convention */ |
| ieee80211_send_error(ni, wh->i_addr2, |
| IEEE80211_FC0_SUBTYPE_AUTH, |
| (seq + 1) | (estatus<<16)); |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_AUTH_FAILS, 1); |
| |
| } else if (vap->iv_opmode == IEEE80211_M_STA) { |
| /* |
| * Kick the state machine. This short-circuits |
| * using the mgt frame timeout to trigger the |
| * state transition. |
| */ |
| if (vap->iv_state == IEEE80211_S_AUTH) |
| ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); |
| } |
| } |
| |
| /* Verify the existence and length of __elem or get out. */ |
| #define IEEE80211_VERIFY_ELEMENT(__elem, __maxlen) do { \ |
| if ((__elem) == NULL) { \ |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID, \ |
| wh, ieee80211_mgt_subtype_name[subtype >> \ |
| IEEE80211_FC0_SUBTYPE_SHIFT], \ |
| "%s", "no " #__elem ); \ |
| vap->iv_stats.is_rx_elem_missing++; \ |
| return; \ |
| } \ |
| if ((__elem)[1] > (__maxlen)) { \ |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID, \ |
| wh, ieee80211_mgt_subtype_name[subtype >> \ |
| IEEE80211_FC0_SUBTYPE_SHIFT], \ |
| "bad " #__elem " len %d", (__elem)[1]); \ |
| vap->iv_stats.is_rx_elem_toobig++; \ |
| return; \ |
| } \ |
| } while (0) |
| |
| #define IEEE80211_VERIFY_LENGTH(_len, _minlen) do { \ |
| if ((_len) < (_minlen)) { \ |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID, \ |
| wh, ieee80211_mgt_subtype_name[subtype >> \ |
| IEEE80211_FC0_SUBTYPE_SHIFT], \ |
| "%s", "ie too short"); \ |
| vap->iv_stats.is_rx_elem_toosmall++; \ |
| return; \ |
| } \ |
| } while (0) |
| |
| #define IEEE80211_VERIFY_TDLS_LENGTH(_len, _minlen) do { \ |
| if ((_len) < (_minlen)) { \ |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, \ |
| IEEE80211_TDLS_MSG_DBG, "%s", "ie too short", __FUNCTION__); \ |
| vap->iv_stats.is_rx_elem_toosmall++; \ |
| return; \ |
| } \ |
| } while (0) |
| |
| #ifdef IEEE80211_DEBUG |
| static void |
| ieee80211_ssid_mismatch(struct ieee80211vap *vap, const char *tag, |
| u_int8_t mac[IEEE80211_ADDR_LEN], u_int8_t *ssid) |
| { |
| printf("[%s] discard %s frame, ssid mismatch: ", |
| ether_sprintf(mac), tag); |
| ieee80211_print_essid(ssid + 2, ssid[1]); |
| printf("\n"); |
| } |
| #endif |
| |
| enum ieee80211_verify_ssid_action { |
| IEEE80211_VERIFY_SSID_ACTION_NO = 0, |
| IEEE80211_VERIFY_SSID_ACTION_RETURN = 1, |
| IEEE80211_VERIFY_SSID_ACTION_NODE_DEL_AND_RETURN = 2 |
| }; |
| |
| static int ieee80211_verify_ssid(struct ieee80211vap *vap, |
| struct ieee80211_node *ni, |
| struct ieee80211_frame *wh, |
| u_int8_t *ssid, |
| int subtype) |
| { |
| if (ssid[1] != 0 && |
| (ssid[1] != (vap->iv_bss)->ni_esslen || |
| memcmp(ssid + 2, (vap->iv_bss)->ni_essid, ssid[1]) != 0)) { |
| #ifdef IEEE80211_DEBUG |
| if (ieee80211_msg_input(vap) && |
| subtype != IEEE80211_FC0_SUBTYPE_PROBE_REQ) { |
| ieee80211_ssid_mismatch(vap, |
| ieee80211_mgt_subtype_name[subtype >> |
| IEEE80211_FC0_SUBTYPE_SHIFT], |
| wh->i_addr2, ssid); |
| } |
| #endif |
| vap->iv_stats.is_rx_ssidmismatch++; |
| if ((ni != vap->iv_bss) && ((subtype == IEEE80211_FC0_SUBTYPE_ASSOC_REQ) || |
| (subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ))) { |
| return IEEE80211_VERIFY_SSID_ACTION_NODE_DEL_AND_RETURN; |
| } else { |
| return IEEE80211_VERIFY_SSID_ACTION_RETURN; |
| } |
| } else if ((ssid[1] == 0) && (vap->iv_flags & IEEE80211_F_HIDESSID) && |
| (subtype == IEEE80211_FC0_SUBTYPE_PROBE_REQ)) { |
| return IEEE80211_VERIFY_SSID_ACTION_RETURN; |
| } |
| |
| /* Reject empty ssid in association requests */ |
| if ((vap->iv_qtn_options & IEEE80211_QTN_NO_SSID_ASSOC_DISABLED) && |
| ((subtype == IEEE80211_FC0_SUBTYPE_ASSOC_REQ) || |
| (subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ)) && |
| (ssid[1] == 0)) { |
| return IEEE80211_VERIFY_SSID_ACTION_RETURN; |
| } |
| |
| return IEEE80211_VERIFY_SSID_ACTION_NO; |
| } |
| |
| /* unaligned little endian access */ |
| #define LE_READ_2(p) \ |
| ((u_int16_t) \ |
| ((((const u_int8_t *)(p))[0] ) | \ |
| (((const u_int8_t *)(p))[1] << 8))) |
| #define LE_READ_3(p) \ |
| ((u_int32_t) \ |
| ((((const u_int8_t *)(p))[0] ) | \ |
| (((const u_int8_t *)(p))[1] << 8) | \ |
| (((const u_int8_t *)(p))[2] << 16) | 0)) |
| #define LE_READ_4(p) \ |
| ((u_int32_t) \ |
| ((((const u_int8_t *)(p))[0] ) | \ |
| (((const u_int8_t *)(p))[1] << 8) | \ |
| (((const u_int8_t *)(p))[2] << 16) | \ |
| (((const u_int8_t *)(p))[3] << 24))) |
| |
| #define BE_READ_2(p) \ |
| ((u_int16_t) \ |
| ((((const u_int8_t *)(p))[1] ) | \ |
| (((const u_int8_t *)(p))[0] << 8))) |
| #define BE_READ_4(p) \ |
| ((u_int32_t) \ |
| ((((const u_int8_t *)(p))[3] ) | \ |
| (((const u_int8_t *)(p))[2] << 8) | \ |
| (((const u_int8_t *)(p))[1] << 16) | \ |
| (((const u_int8_t *)(p))[0] << 24))) |
| |
| static __inline int |
| iswpaoui(const u_int8_t *frm) |
| { |
| return frm[1] > 3 && LE_READ_4(frm+2) == ((WPA_RSN_OUI_TYPE<<24)|WPA_OUI); |
| } |
| |
| static __inline int |
| iswmeoui(const u_int8_t *frm) |
| { |
| return frm[1] > 3 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI); |
| } |
| |
| static __inline int |
| iswmeparam(const u_int8_t *frm) |
| { |
| return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) && |
| frm[6] == WME_PARAM_OUI_SUBTYPE; |
| } |
| |
| static __inline int |
| iswmeinfo(const u_int8_t *frm) |
| { |
| return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) && |
| frm[6] == WME_INFO_OUI_SUBTYPE; |
| } |
| |
| static __inline int |
| iswscoui(const u_int8_t *frm) |
| { |
| return frm[1] > 3 && LE_READ_4(frm+2) == ((WSC_OUI_TYPE<<24)|WPA_OUI); |
| } |
| |
| static __inline int |
| isatherosoui(const u_int8_t *frm) |
| { |
| return frm[1] > 3 && LE_READ_4(frm+2) == ((ATH_OUI_TYPE<<24)|ATH_OUI); |
| } |
| |
| static __inline int |
| isqtnie(const u_int8_t *frm) |
| { |
| return (frm[1] > 3 && |
| ((LE_READ_4(frm+2) & 0x00ffffff) == QTN_OUI) && |
| ((frm[5] == QTN_OUI_CFG))); |
| } |
| |
| static __inline int |
| isosenie(const u_int8_t *frm) |
| { |
| return (frm[1] > 3 && |
| (LE_READ_3(frm + 2) == WFA_OUI) && |
| ((frm[5] == WFA_TYPE_OSEN))); |
| } |
| |
| static __inline int |
| is_peer_mrvl( u_int8_t *rlnk, void *bcmie, void *rtkie, struct ieee80211_ie_qtn *qtnie, |
| struct ieee80211_ie_vhtcap *vhtcap, struct ieee80211_node *ni) |
| { |
| if (unlikely(!bcmie && !qtnie && !rlnk && !rtkie && !ieee80211_node_is_intel(ni) && |
| (ni->ni_flags & IEEE80211_NODE_VHT) && |
| !IEEE80211_VHTCAP_GET_SU_BEAMFORMER((struct ieee80211_ie_vhtcap *)vhtcap) && |
| IEEE80211_VHTCAP_GET_SU_BEAMFORMEE((struct ieee80211_ie_vhtcap *)vhtcap) && |
| (IEEE80211_VHTCAP_GET_BFSTSCAP((struct ieee80211_ie_vhtcap *)vhtcap) == IEEE80211_VHTCAP_RX_STS_4)) && |
| !IEEE80211_VHT_HAS_3SS(ni->ni_vhtcap.rxmcsmap)) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| #ifdef CONFIG_QVSP |
| static __inline int |
| isvspie(const u_int8_t *frm) |
| { |
| return (frm[1] > 3 && |
| ((LE_READ_4(frm+2) & 0x00ffffff) == QTN_OUI) && |
| ((frm[5] == QTN_OUI_VSP_CTRL))); |
| } |
| |
| static __inline int |
| isqtnwmeie(const uint8_t *frm) |
| { |
| return (frm[1] > 3 && |
| ((LE_READ_4(frm + 2) & 0x00ffffff) == QTN_OUI) && |
| ((frm[5] == QTN_OUI_QWME))); |
| } |
| #endif |
| |
| static __inline int |
| is_qtn_scs_oui(const u_int8_t *frm) |
| { |
| return (frm[1] > 3 && |
| ((LE_READ_4(frm+2) & 0x00ffffff) == QTN_OUI) && |
| ((frm[5] == QTN_OUI_SCS))); |
| } |
| |
| static __inline int |
| isbroadcomoui(const u_int8_t *frm) |
| { |
| return (frm[1] > 3 && (LE_READ_4(frm+2) & 0x00ffffff) == BCM_OUI); |
| } |
| |
| static __inline int |
| isbroadcomoui2(const u_int8_t *frm) |
| { |
| return (frm[1] > 3 && (LE_READ_4(frm+2) & 0x00ffffff) == BCM_OUI_2); |
| } |
| |
| static __inline int |
| isqtnpairoui(const u_int8_t *frm) |
| { |
| return (frm[1] > 3 && |
| ((LE_READ_4(frm+2) & 0x00ffffff) == QTN_OUI) && |
| (frm[5] == QTN_OUI_PAIRING)); |
| } |
| |
| static __inline int |
| is_qtn_oui_tdls_brmacs(const u_int8_t *frm) |
| { |
| return (frm[1] > 3 && |
| ((LE_READ_4(frm+2) & 0x00ffffff) == QTN_OUI) && |
| ((frm[5] == QTN_OUI_TDLS_BRMACS))); |
| } |
| |
| static __inline int |
| is_qtn_oui_tdls_sta_info(const u_int8_t *frm) |
| { |
| return (frm[1] > 3 && |
| ((LE_READ_4(frm+2) & 0x00ffffff) == QTN_OUI) && |
| ((frm[5] == QTN_OUI_TDLS))); |
| } |
| |
| static __inline int |
| isrlnkoui(const u_int8_t *frm) |
| { |
| return (frm[1] > 3 && |
| ((LE_READ_4(frm+2) & 0x00ffffff) == RLNK_OUI)); |
| } |
| |
| static __inline int |
| is_qtn_ext_role_oui(const u_int8_t *frm) |
| { |
| return ((frm[1] > 3) && |
| ((LE_READ_4(frm+2) & 0x00ffffff) == QTN_OUI) && |
| (frm[5] == QTN_OUI_EXTENDER_ROLE)); |
| } |
| |
| static __inline int |
| is_qtn_ext_bssid_oui(const u_int8_t *frm) |
| { |
| return (frm[1] > 3 && |
| ((LE_READ_4(frm+2) & 0x00ffffff) == QTN_OUI) && |
| (frm[5] == QTN_OUI_EXTENDER_BSSID)); |
| } |
| |
| static __inline int |
| is_qtn_ext_state_oui(const u_int8_t *frm) |
| { |
| return (frm[1] > 3 && |
| ((LE_READ_4(frm+2) & 0x00ffffff) == QTN_OUI) && |
| (frm[5] == QTN_OUI_EXTENDER_STATE)); |
| } |
| |
| static __inline int |
| isrealtekoui(const u_int8_t *frm) |
| { |
| return (frm[1] > 3 && |
| ((LE_READ_4(frm+2) & 0x00ffffff) == RTK_OUI)); |
| } |
| |
| static __inline int |
| isqtnmrespoui(const u_int8_t *frm) |
| { |
| return (frm[0] == IEEE80211_ELEMID_VENDOR) && |
| (frm[1] >= 5) && (LE_READ_3(&frm[2]) == QTN_OUI) && |
| (frm[6] == QTN_OUI_RM_SPCIAL || frm[6] == QTN_OUI_RM_ALL); |
| } |
| |
| static __inline int |
| isbrcmvhtoui(const u_int8_t *frm) |
| { |
| return (frm[0] == IEEE80211_ELEMID_VENDOR) && |
| (frm[1] >= 5) && (LE_READ_3(&frm[2]) == BCM_OUI) && |
| (LE_READ_2(&frm[5]) == BCM_OUI_VHT_TYPE); |
| } |
| |
| static __inline int |
| is_qtn_ocac_state_ie(const u_int8_t *frm) |
| { |
| return (frm[0] == IEEE80211_ELEMID_VENDOR) && |
| (frm[1] == OCAC_STATE_IE_LEN) && |
| (LE_READ_3(&frm[2]) == QTN_OUI) && |
| (frm[5] == QTN_OUI_OCAC_STATE); |
| } |
| |
| /* |
| * Convert a WPA cipher selector OUI to an internal |
| * cipher algorithm. Where appropriate we also |
| * record any key length. |
| */ |
| static int |
| wpa_cipher(u_int8_t *sel, u_int8_t *keylen) |
| { |
| #define WPA_SEL(x) (((x) << 24) | WPA_OUI) |
| u_int32_t w = LE_READ_4(sel); |
| |
| switch (w) { |
| case WPA_SEL(WPA_CSE_NULL): |
| return IEEE80211_CIPHER_NONE; |
| case WPA_SEL(WPA_CSE_WEP40): |
| if (keylen) |
| *keylen = 40 / NBBY; |
| return IEEE80211_CIPHER_WEP; |
| case WPA_SEL(WPA_CSE_WEP104): |
| if (keylen) |
| *keylen = 104 / NBBY; |
| return IEEE80211_CIPHER_WEP; |
| case WPA_SEL(WPA_CSE_TKIP): |
| return IEEE80211_CIPHER_TKIP; |
| case WPA_SEL(WPA_CSE_CCMP): |
| return IEEE80211_CIPHER_AES_CCM; |
| } |
| return 32; /* NB: so 1<< is discarded */ |
| #undef WPA_SEL |
| } |
| |
| /* |
| * Convert a WPA key management/authentication algorithm |
| * to an internal code. |
| */ |
| static int |
| wpa_keymgmt(u_int8_t *sel) |
| { |
| #define WPA_SEL(x) (((x)<<24)|WPA_OUI) |
| u_int32_t w = LE_READ_4(sel); |
| |
| switch (w) { |
| case WPA_SEL(WPA_ASE_8021X_UNSPEC): |
| return WPA_ASE_8021X_UNSPEC; |
| case WPA_SEL(WPA_ASE_8021X_PSK): |
| return WPA_ASE_8021X_PSK; |
| case WPA_SEL(WPA_ASE_NONE): |
| return WPA_ASE_NONE; |
| } |
| return 0; /* NB: so is discarded */ |
| #undef WPA_SEL |
| } |
| |
| /* |
| * Parse a WPA information element to collect parameters |
| * and validate the parameters against what has been |
| * configured for the system. |
| */ |
| static int |
| ieee80211_parse_wpa(struct ieee80211vap *vap, u_int8_t *frm, |
| struct ieee80211_rsnparms *rsn_parm, const struct ieee80211_frame *wh) |
| { |
| u_int8_t len = frm[1]; |
| u_int32_t w; |
| int n; |
| struct ieee80211com *ic = vap->iv_ic; |
| |
| /* |
| * Check the length once for fixed parts: OUI, type, |
| * version, mcast cipher, and 2 selector counts. |
| * Other, variable-length data, must be checked separately. |
| */ |
| if (!(vap->iv_flags & IEEE80211_F_WPA1)) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "WPA", "vap not WPA, flags 0x%x", vap->iv_flags); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| |
| if (len < 14) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "WPA", "too short, len %u", len); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| frm += 6, len -= 4; /* NB: len is payload only */ |
| /* NB: iswapoui already validated the OUI and type */ |
| w = LE_READ_2(frm); |
| if (w != WPA_VERSION) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "WPA", "bad version %u", w); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| frm += 2; |
| len -= 2; |
| |
| /* multicast/group cipher */ |
| w = wpa_cipher(frm, &rsn_parm->rsn_mcastkeylen); |
| if (w != rsn_parm->rsn_mcastcipher) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "WPA", "mcast cipher mismatch; got %u, expected %u", |
| w, rsn_parm->rsn_mcastcipher); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| if (!IEEE80211_IS_TKIP_ALLOWED(ic)) { |
| if (w == IEEE80211_CIPHER_TKIP) |
| return IEEE80211_REASON_STA_CIPHER_NOT_SUPP; |
| } |
| frm += 4; |
| len -= 4; |
| |
| /* unicast ciphers */ |
| n = LE_READ_2(frm); |
| frm += 2; |
| len -= 2; |
| if (len < n*4+2) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "WPA", "ucast cipher data too short; len %u, n %u", |
| len, n); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| w = 0; |
| for (; n > 0; n--) { |
| w |= 1 << wpa_cipher(frm, &rsn_parm->rsn_ucastkeylen); |
| frm += 4; |
| len -= 4; |
| } |
| w &= rsn_parm->rsn_ucastcipherset; |
| if (w == 0) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "WPA", "%s", "ucast cipher set empty"); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| if (w & (1 << IEEE80211_CIPHER_TKIP)) { |
| if (!IEEE80211_IS_TKIP_ALLOWED(ic)) |
| return IEEE80211_REASON_STA_CIPHER_NOT_SUPP; |
| else |
| rsn_parm->rsn_ucastcipher = IEEE80211_CIPHER_TKIP; |
| } else { |
| rsn_parm->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; |
| } |
| |
| /* key management algorithms */ |
| n = LE_READ_2(frm); |
| frm += 2; |
| len -= 2; |
| if (len < n * 4) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "WPA", "key mgmt alg data too short; len %u, n %u", |
| len, n); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| w = 0; |
| for (; n > 0; n--) { |
| w |= wpa_keymgmt(frm); |
| frm += 4; |
| len -= 4; |
| } |
| w &= rsn_parm->rsn_keymgmtset; |
| if (w == 0) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "WPA", "%s", "no acceptable key mgmt alg"); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| if (w & WPA_ASE_8021X_UNSPEC) |
| rsn_parm->rsn_keymgmt = WPA_ASE_8021X_UNSPEC; |
| else |
| rsn_parm->rsn_keymgmt = WPA_ASE_8021X_PSK; |
| |
| if (len > 2) /* optional capabilities */ |
| rsn_parm->rsn_caps = LE_READ_2(frm); |
| |
| return 0; |
| } |
| |
| /* |
| * Convert an RSN cipher selector OUI to an internal |
| * cipher algorithm. Where appropriate we also |
| * record any key length. |
| */ |
| static int |
| rsn_cipher(u_int8_t *sel, u_int8_t *keylen) |
| { |
| #define RSN_SEL(x) (((x) << 24) | RSN_OUI) |
| u_int32_t w = LE_READ_4(sel); |
| |
| switch (w) { |
| case RSN_SEL(RSN_CSE_NULL): |
| return IEEE80211_CIPHER_NONE; |
| case RSN_SEL(RSN_CSE_WEP40): |
| if (keylen) |
| *keylen = 40 / NBBY; |
| return IEEE80211_CIPHER_WEP; |
| case RSN_SEL(RSN_CSE_WEP104): |
| if (keylen) |
| *keylen = 104 / NBBY; |
| return IEEE80211_CIPHER_WEP; |
| case RSN_SEL(RSN_CSE_TKIP): |
| return IEEE80211_CIPHER_TKIP; |
| case RSN_SEL(RSN_CSE_CCMP): |
| return IEEE80211_CIPHER_AES_CCM; |
| case RSN_SEL(RSN_CSE_WRAP): |
| return IEEE80211_CIPHER_AES_OCB; |
| } |
| return 32; /* NB: so 1<< is discarded */ |
| #undef RSN_SEL |
| } |
| |
| /* |
| * Convert an RSN key management/authentication algorithm |
| * to an internal code. |
| */ |
| static int |
| rsn_keymgmt(u_int8_t *sel) |
| { |
| #define RSN_SEL(x) (((x) << 24) | RSN_OUI) |
| u_int32_t w = LE_READ_4(sel); |
| |
| switch (w) { |
| case RSN_SEL(RSN_ASE_8021X_UNSPEC): |
| return RSN_ASE_8021X_UNSPEC; |
| case RSN_SEL(RSN_ASE_8021X_PSK): |
| return RSN_ASE_8021X_PSK; |
| case RSN_SEL(RSN_ASE_FT_PSK): |
| return RSN_ASE_FT_PSK; |
| case RSN_SEL(RSN_ASE_FT_8021X): |
| return RSN_ASE_FT_8021X; |
| case RSN_SEL(RSN_ASE_8021X_SHA256): |
| return RSN_ASE_8021X_SHA256; |
| case RSN_SEL(RSN_ASE_8021X_PSK_SHA256): |
| return RSN_ASE_8021X_PSK_SHA256; |
| case RSN_SEL(RSN_ASE_NONE): |
| return RSN_ASE_NONE; |
| } |
| return 0; /* NB: so is discarded */ |
| #undef RSN_SEL |
| } |
| |
| /* |
| * Parse a WPA/RSN information element to collect parameters |
| * and populate the rsn parameters in struct |
| */ |
| int |
| ieee80211_get_rsn_from_ie(struct ieee80211vap *vap, u_int8_t *frm, |
| struct ieee80211_rsnparms *rsn_parm) |
| { |
| u_int8_t len = frm[1]; |
| u_int32_t w; |
| int n; |
| |
| /* |
| * Check the length once for fixed parts: |
| * version, mcast cipher, and 2 selector counts. |
| * Other, variable-length data, must be checked separately. |
| */ |
| if (!(vap->iv_flags & IEEE80211_F_WPA2)) { |
| printk( "vap not RSN, flags 0x%x", vap->iv_flags); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| |
| if (len < 10) { |
| printk( "too short, len %u", len); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| frm += 2; |
| w = LE_READ_2(frm); |
| if (w != RSN_VERSION) { |
| printk( "bad version %u", w); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| frm += 2; |
| len -= 2; |
| |
| /* multicast/group cipher */ |
| w = rsn_cipher(frm, &rsn_parm->rsn_mcastkeylen); |
| rsn_parm->rsn_mcastcipher = w; |
| frm += 4; |
| len -= 4; |
| |
| /* unicast ciphers */ |
| n = LE_READ_2(frm); |
| frm += 2; |
| len -= 2; |
| if (len < n * 4 + 2) { |
| printk("ucast cipher data too short; len %u, n %u", |
| len, n); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| w = 0; |
| for (; n > 0; n--) { |
| w |= 1 << rsn_cipher(frm, &rsn_parm->rsn_ucastkeylen); |
| frm += 4; |
| len -= 4; |
| } |
| |
| if (w == 0) { |
| printk( "%s", "ucast cipher set empty"); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| rsn_parm->rsn_ucastcipherset = w; |
| if (w & (1<<IEEE80211_CIPHER_TKIP)) |
| rsn_parm->rsn_ucastcipher = IEEE80211_CIPHER_TKIP; |
| else |
| rsn_parm->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; |
| |
| /* key management algorithms */ |
| n = LE_READ_2(frm); |
| frm += 2; |
| len -= 2; |
| if (len < n * 4) { |
| printk( "key mgmt alg data too short; len %u, n %u", |
| len, n); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| w = 0; |
| for (; n > 0; n--) { |
| w |= rsn_keymgmt(frm); |
| frm += 4; |
| len -= 4; |
| } |
| |
| if (w == 0) { |
| printk("%s", "no acceptable key mgmt alg"); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| rsn_parm->rsn_keymgmtset = w; |
| if (w & RSN_ASE_8021X_UNSPEC) |
| rsn_parm->rsn_keymgmt = RSN_ASE_8021X_UNSPEC; |
| if (w & RSN_ASE_8021X_PSK) |
| rsn_parm->rsn_keymgmt = RSN_ASE_8021X_UNSPEC; |
| if (w & RSN_ASE_FT_PSK) |
| rsn_parm->rsn_keymgmt = RSN_ASE_FT_PSK; |
| if (w & RSN_ASE_FT_8021X) |
| rsn_parm->rsn_keymgmt = RSN_ASE_FT_8021X; |
| |
| /* optional RSN capabilities */ |
| if (len >= 2) |
| rsn_parm->rsn_caps = LE_READ_2(frm); |
| /* XXXPMKID */ |
| return 0; |
| } |
| |
| /* |
| * Parse a WPA/RSN information element to collect parameters |
| * and validate the parameters against what has been |
| * configured for the system. |
| */ |
| static int |
| ieee80211_parse_rsn(struct ieee80211vap *vap, u_int8_t *frm, |
| struct ieee80211_rsnparms *rsn_parm, const struct ieee80211_frame *wh) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| u_int8_t len = frm[1]; |
| u_int32_t w; |
| int n; |
| |
| /* |
| * Check the length once for fixed parts: |
| * version, mcast cipher, and 2 selector counts. |
| * Other, variable-length data, must be checked separately. |
| */ |
| if (!(vap->iv_flags & IEEE80211_F_WPA2)) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "RSN", "vap not RSN, flags 0x%x", vap->iv_flags); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| |
| if (len < 10) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "RSN", "too short, len %u", len); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| frm += 2; |
| w = LE_READ_2(frm); |
| if (w != RSN_VERSION) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "RSN", "bad version %u", w); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| frm += 2; |
| len -= 2; |
| |
| /* multicast/group cipher */ |
| w = rsn_cipher(frm, &rsn_parm->rsn_mcastkeylen); |
| if (w != rsn_parm->rsn_mcastcipher) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "RSN", "mcast cipher mismatch; got %u, expected %u", |
| w, rsn_parm->rsn_mcastcipher); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| if (!IEEE80211_IS_TKIP_ALLOWED(ic)) { |
| if (w == IEEE80211_CIPHER_TKIP) { |
| return IEEE80211_REASON_STA_CIPHER_NOT_SUPP; |
| } |
| } |
| frm += 4; |
| len -= 4; |
| |
| /* unicast ciphers */ |
| n = LE_READ_2(frm); |
| frm += 2; |
| len -= 2; |
| if (len < n * 4 + 2) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "RSN", "ucast cipher data too short; len %u, n %u", |
| len, n); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| w = 0; |
| for (; n > 0; n--) { |
| w |= 1 << rsn_cipher(frm, &rsn_parm->rsn_ucastkeylen); |
| frm += 4; |
| len -= 4; |
| } |
| |
| w &= rsn_parm->rsn_ucastcipherset; |
| if (w == 0) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "RSN", "%s", "ucast cipher set empty"); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| if (w & (1 << IEEE80211_CIPHER_TKIP)) { |
| if (!IEEE80211_IS_TKIP_ALLOWED(ic)) |
| return IEEE80211_REASON_STA_CIPHER_NOT_SUPP; |
| else |
| rsn_parm->rsn_ucastcipher = IEEE80211_CIPHER_TKIP; |
| } else { |
| rsn_parm->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; |
| } |
| |
| /* key management algorithms */ |
| n = LE_READ_2(frm); |
| frm += 2; |
| len -= 2; |
| if (len < n * 4) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "RSN", "key mgmt alg data too short; len %u, n %u", |
| len, n); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| w = 0; |
| for (; n > 0; n--) { |
| w |= rsn_keymgmt(frm); |
| frm += 4; |
| len -= 4; |
| } |
| if (w & RSN_ASE_8021X_UNSPEC) |
| rsn_parm->rsn_keymgmt = RSN_ASE_8021X_UNSPEC; |
| else if (w & RSN_ASE_8021X_PSK) |
| rsn_parm->rsn_keymgmt = RSN_ASE_8021X_PSK; |
| else if (w & RSN_ASE_FT_PSK) |
| rsn_parm->rsn_keymgmt = WPA_KEY_MGMT_FT_PSK; |
| else if (w & RSN_ASE_FT_8021X) |
| rsn_parm->rsn_keymgmt = WPA_KEY_MGMT_FT_IEEE8021X; |
| else { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "RSN", "%s", "no acceptable key mgmt alg"); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| |
| /* optional RSN capabilities */ |
| if (len >= 2) |
| rsn_parm->rsn_caps = LE_READ_2(frm); |
| /* XXXPMKID */ |
| |
| return 0; |
| } |
| |
| |
| #define IEEE80211_OSEN_IE_HEAD_LEN 4 |
| #define IEEE80211_OSEN_IE_SUITE_LEN 4 |
| #define IEEE80211_OSEN_IE_SUITE_COUNT_LEN 2 |
| #define IEEE80211_IE_HEAD_LEN 2 |
| #define IEEE80211_OSEN_IE_RSN_CAPS_LEN 2 |
| #define IEEE80211_OSEN_IE_MIN_LEN (IEEE80211_OSEN_IE_HEAD_LEN + \ |
| IEEE80211_OSEN_IE_SUITE_LEN + \ |
| IEEE80211_OSEN_IE_SUITE_COUNT_LEN) |
| |
| static int |
| ieee80211_parse_osen(struct ieee80211vap *vap, u_int8_t *frm, |
| struct ieee80211_rsnparms *rsn_parm, const struct ieee80211_frame *wh) |
| { |
| uint8_t len = frm[1]; |
| uint32_t w; |
| int n; |
| |
| if (len < IEEE80211_OSEN_IE_MIN_LEN) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "OSEN", "too short, len %u", len); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| |
| if (!vap->iv_osen) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "OSEN", "%s", "vap not OSEN"); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| |
| frm += IEEE80211_IE_HEAD_LEN + IEEE80211_OSEN_IE_HEAD_LEN; |
| len -= IEEE80211_OSEN_IE_HEAD_LEN; |
| |
| w = LE_READ_4(frm); |
| if (w != ((RSN_CSE_GROUP_NOT_ALLOW << 24) | RSN_OUI)) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "OSEN", "mcast cipher mismatch; got %u, expected %u", |
| w, RSN_CSE_GROUP_NOT_ALLOW); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| |
| frm += IEEE80211_OSEN_IE_SUITE_LEN; |
| len -= IEEE80211_OSEN_IE_SUITE_LEN; |
| |
| /* unicast ciphers */ |
| n = LE_READ_2(frm); |
| frm += IEEE80211_OSEN_IE_SUITE_COUNT_LEN; |
| len -= IEEE80211_OSEN_IE_SUITE_COUNT_LEN; |
| if (len < n * IEEE80211_OSEN_IE_SUITE_LEN + IEEE80211_OSEN_IE_SUITE_COUNT_LEN) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "OSEN", "ucast cipher data too short; len %u, n %u", |
| len, n); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| w = 0; |
| for (; n > 0; n--) { |
| w |= 1 << rsn_cipher(frm, &rsn_parm->rsn_ucastkeylen); |
| frm += IEEE80211_OSEN_IE_SUITE_LEN; |
| len -= IEEE80211_OSEN_IE_SUITE_LEN; |
| } |
| |
| w &= rsn_parm->rsn_ucastcipherset; |
| if (w == 0) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "OSEN", "%s", "ucast cipher set empty"); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| if (w & (1 << IEEE80211_CIPHER_TKIP)) |
| rsn_parm->rsn_ucastcipher = IEEE80211_CIPHER_TKIP; |
| else |
| rsn_parm->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; |
| |
| /* key management algorithms */ |
| n = LE_READ_2(frm); |
| frm += IEEE80211_OSEN_IE_SUITE_COUNT_LEN; |
| len -= IEEE80211_OSEN_IE_SUITE_COUNT_LEN; |
| if (len < n * IEEE80211_OSEN_IE_SUITE_LEN) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "OSEN", "key mgmt alg data too short; len %u, n %u", |
| len, n); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| |
| if (LE_READ_4(frm) != ((WFA_AKM_TYPE_OSEN << 24) | WFA_OUI)) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, |
| wh, "OSEN", "%s", "no acceptable key mgmt alg"); |
| return IEEE80211_REASON_IE_INVALID; |
| } |
| |
| rsn_parm->rsn_keymgmt = RSN_ASE_8021X_UNSPEC; |
| |
| /* optional RSN capabilities */ |
| if (len >= IEEE80211_OSEN_IE_RSN_CAPS_LEN) |
| rsn_parm->rsn_caps = LE_READ_2(frm); |
| |
| return 0; |
| } |
| |
| void |
| ieee80211_saveie(u_int8_t **iep, const u_int8_t *ie) |
| { |
| if (*iep == NULL) |
| { |
| if (ie != NULL) |
| MALLOC(*iep, void*, ie[1] + 2, M_DEVBUF, M_ZERO); |
| } |
| else |
| { |
| if (((*iep)[1] != ie[1]) || (ie == NULL)) { |
| FREE(*iep, M_DEVBUF); |
| *iep = NULL; |
| if (ie != NULL) { |
| MALLOC(*iep, void*, ie[1] + 2, M_DEVBUF, M_ZERO); |
| } |
| } |
| } |
| |
| if ((*iep != NULL) && (ie != NULL)) |
| memcpy(*iep, ie, ie[1] + 2); |
| } |
| EXPORT_SYMBOL(ieee80211_saveie); |
| |
| static int |
| ieee80211_parse_wmeie(u_int8_t *frm, const struct ieee80211_frame *wh, |
| struct ieee80211_node *ni) |
| { |
| u_int len = frm[1]; |
| |
| if (len != 7) { |
| IEEE80211_DISCARD_IE(ni->ni_vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME, |
| wh, "WME IE", "too short, len %u", len); |
| return -1; |
| } |
| ni->ni_uapsd = frm[WME_CAPINFO_IE_OFFSET]; |
| if (ni->ni_uapsd) { |
| ni->ni_flags |= IEEE80211_NODE_UAPSD; |
| } |
| IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_POWER, ni, |
| "UAPSD bit settings from STA: %02x", ni->ni_uapsd); |
| |
| return 1; |
| } |
| /* |
| * This function is only used on STA and the purpose is to save the cipher |
| * info filled in ASSOC_REQ and apply it to node allocation once association |
| * response received. |
| * If TKIP, need two entries; If others, only need one entry |
| */ |
| void |
| ieee80211_parse_cipher_key(struct ieee80211vap *vap, void *ie, uint16_t len) |
| { |
| struct ieee80211_rsnparms *ni_rsn = &(vap->iv_bss->ni_rsn); |
| uint8_t type; |
| uint8_t count; |
| int16_t length = len; |
| uint8_t *frm = ie; |
| |
| if (vap->iv_opmode != IEEE80211_M_STA) |
| return; |
| |
| vap->iv_bss->ni_rsn.rsn_ucastcipher = IEEE80211_CIPHER_NONE; |
| vap->iv_bss->ni_rsn.rsn_mcastcipher = IEEE80211_CIPHER_NONE; |
| |
| while(length > 0) { |
| type = *frm; |
| length -= 2; |
| if (likely (type == IEEE80211_ELEMID_RSN) && |
| (length >= 10)) { |
| /* |
| * fixed part for RSN IE. version, mcast cipher, and 2 selector counts. |
| * Other, variable-length data, must be checked separately. |
| */ |
| frm += 4; |
| ni_rsn->rsn_mcastcipher = rsn_cipher(frm, NULL); |
| frm += 4; |
| count = LE_READ_2(frm); |
| if (count != 1) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, |
| "%s: more than one unicast cipher in optie RSN\n", |
| __func__); |
| } |
| frm += 2; |
| ni_rsn->rsn_ucastcipher = rsn_cipher(frm, NULL); |
| return; |
| } else if ((type == IEEE80211_ELEMID_VENDOR) && iswpaoui(frm) && |
| (length >= 14)) { |
| /* |
| * Check the length once for fixed parts: OUI, type, |
| * version, mcast cipher, and 2 selector counts. |
| * Other, variable-length data, must be checked separately. |
| */ |
| frm += 8; |
| ni_rsn->rsn_mcastcipher = wpa_cipher(frm, NULL); |
| frm += 4; |
| count = LE_READ_2(frm); |
| if (count != 1) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, |
| "%s: more than one unicast cipher in optie WPA\n", |
| __func__); |
| } |
| frm += 2; |
| ni_rsn->rsn_ucastcipher = wpa_cipher(frm, NULL); |
| return; |
| } else { |
| length -= *(frm + 1); |
| frm += (*(frm + 1) + 2); |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, |
| "%s: No WPA/RSN IE\n", |
| __func__); |
| } |
| } |
| |
| return; |
| } |
| |
| static int |
| ieee80211_parse_wmeparams(struct ieee80211vap *vap, u_int8_t *frm, |
| const struct ieee80211_frame *wh, u_int8_t *qosinfo) |
| { |
| struct ieee80211_wme_state *wme = &vap->iv_ic->ic_wme; |
| u_int len = frm[1], qosinfo_count; |
| int i; |
| |
| *qosinfo = 0; |
| |
| if (len < sizeof(struct ieee80211_wme_param)-2) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME, |
| wh, "WME", "too short, len %u", len); |
| return -1; |
| } |
| *qosinfo = frm[__offsetof(struct ieee80211_wme_param, param_qosInfo)]; |
| qosinfo_count = *qosinfo & WME_QOSINFO_COUNT; |
| /* XXX do proper check for wraparound */ |
| if (qosinfo_count == wme->wme_wmeChanParams.cap_info_count) { |
| return 0; |
| } |
| frm += __offsetof(struct ieee80211_wme_param, params_acParams); |
| for (i = 0; i < WME_NUM_AC; i++) { |
| struct wmm_params *wmep = |
| &wme->wme_wmeChanParams.cap_wmeParams[i]; |
| /* NB: ACI not used */ |
| wmep->wmm_acm = MS(frm[0], WME_PARAM_ACM); |
| wmep->wmm_aifsn = MS(frm[0], WME_PARAM_AIFSN); |
| wmep->wmm_logcwmin = MS(frm[1], WME_PARAM_LOGCWMIN); |
| wmep->wmm_logcwmax = MS(frm[1], WME_PARAM_LOGCWMAX); |
| wmep->wmm_txopLimit = LE_READ_2(frm + 2); |
| frm += 4; |
| } |
| wme->wme_wmeChanParams.cap_info_count = qosinfo_count; |
| return 1; |
| } |
| |
| static void |
| ieee80211_parse_athParams(struct ieee80211_node *ni, u_int8_t *ie) |
| { |
| struct ieee80211_ie_athAdvCap *athIe = |
| (struct ieee80211_ie_athAdvCap *) ie; |
| |
| ni->ni_ath_flags = athIe->athAdvCap_capability; |
| if (ni->ni_ath_flags & IEEE80211_ATHC_COMP) |
| ni->ni_ath_defkeyindex = LE_READ_2(&athIe->athAdvCap_defKeyIndex); |
| } |
| |
| static void |
| ieee80211_skb_dev_set(struct net_device *dev, struct sk_buff *skb) |
| { |
| struct net_bridge_port *br_port = get_br_port(dev); |
| #if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) |
| if (br_port && br_port->br) |
| skb->dev = br_port->br->dev; |
| else |
| skb->dev = dev; |
| #else |
| skb->dev = dev; |
| #endif |
| } |
| static void |
| forward_mgmt_to_app_for_further_processing(struct ieee80211vap *vap, int subtype, struct sk_buff *skb, |
| struct ieee80211_frame *wh) |
| { |
| struct net_device *dev = vap->iv_dev; |
| struct sk_buff *skb1; |
| skb1 = skb_copy(skb, GFP_ATOMIC); |
| if (skb1 == NULL) |
| return; |
| |
| ieee80211_skb_dev_set(dev, skb1); |
| skb_reset_mac_header(skb1); |
| skb1->ip_summed = CHECKSUM_NONE; |
| skb1->pkt_type = PACKET_OTHERHOST; |
| skb1->protocol = __constant_htons(0x0019); /* ETH_P_80211_RAW */ |
| netif_rx(skb1); |
| } |
| |
| static void |
| forward_mgmt_to_app(struct ieee80211vap *vap, int subtype, struct sk_buff *skb, |
| struct ieee80211_frame *wh) |
| { |
| struct net_device *dev = vap->iv_dev; |
| int filter_type = 0; |
| |
| switch (subtype) { |
| case IEEE80211_FC0_SUBTYPE_BEACON: |
| filter_type = IEEE80211_FILTER_TYPE_BEACON; |
| break; |
| case IEEE80211_FC0_SUBTYPE_PROBE_REQ: |
| filter_type = IEEE80211_FILTER_TYPE_PROBE_REQ; |
| break; |
| case IEEE80211_FC0_SUBTYPE_PROBE_RESP: |
| filter_type = IEEE80211_FILTER_TYPE_PROBE_RESP; |
| break; |
| case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: |
| case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: |
| filter_type = IEEE80211_FILTER_TYPE_ASSOC_REQ; |
| break; |
| case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: |
| case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: |
| filter_type = IEEE80211_FILTER_TYPE_ASSOC_RESP; |
| break; |
| case IEEE80211_FC0_SUBTYPE_AUTH: |
| filter_type = IEEE80211_FILTER_TYPE_AUTH; |
| break; |
| case IEEE80211_FC0_SUBTYPE_DEAUTH: |
| filter_type = IEEE80211_FILTER_TYPE_DEAUTH; |
| break; |
| case IEEE80211_FC0_SUBTYPE_DISASSOC: |
| filter_type = IEEE80211_FILTER_TYPE_DISASSOC; |
| break; |
| case IEEE80211_FC0_SUBTYPE_ACTION: |
| filter_type = IEEE80211_FILTER_TYPE_ACTION; |
| break; |
| default: |
| break; |
| } |
| |
| if (filter_type && ((vap->app_filter & filter_type) == filter_type)) { |
| struct sk_buff *skb1; |
| skb1 = skb_copy(skb, GFP_ATOMIC); |
| if (skb1 == NULL) |
| return; |
| |
| ieee80211_skb_dev_set(dev, skb1); |
| skb_reset_mac_header(skb1); |
| skb1->ip_summed = CHECKSUM_NONE; |
| skb1->pkt_type = PACKET_OTHERHOST; |
| skb1->protocol = __constant_htons(0x0019); /* ETH_P_80211_RAW */ |
| netif_rx(skb1); |
| } |
| } |
| |
| void |
| ieee80211_saveath(struct ieee80211_node *ni, u_int8_t *ie) |
| { |
| const struct ieee80211_ie_athAdvCap *athIe = |
| (const struct ieee80211_ie_athAdvCap *) ie; |
| |
| ni->ni_ath_flags = athIe->athAdvCap_capability; |
| if (ni->ni_ath_flags & IEEE80211_ATHC_COMP) |
| ni->ni_ath_defkeyindex = LE_READ_2(&athIe->athAdvCap_defKeyIndex); |
| ieee80211_saveie(&ni->ni_ath_ie, ie); |
| } |
| |
| struct ieee80211_channel * |
| ieee80211_doth_findchan(struct ieee80211vap *vap, u_int8_t chan) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211_channel *c; |
| int flags, freq; |
| |
| KASSERT(ic->ic_bsschan != IEEE80211_CHAN_ANYC, ("BSS channel not set up")); |
| |
| /* NB: try first to preserve turbo */ |
| flags = ic->ic_bsschan->ic_flags & IEEE80211_CHAN_ALL; |
| freq = ieee80211_ieee2mhz(chan, 0); |
| c = ieee80211_find_channel(ic, freq, flags); |
| if (c == NULL) |
| c = ieee80211_find_channel(ic, freq, 0); |
| return c; |
| } |
| |
| static void |
| ieee80211_doth_cancel_cs(struct ieee80211vap *vap) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| /* attempt a cancel */ |
| ic->ic_set_channel_deferred(ic, 0, IEEE80211_SET_CHANNEL_DEFERRED_CANCEL); |
| } |
| |
| int |
| ieee80211_parse_htcap(struct ieee80211_node *ni, u_int8_t *ie) |
| { |
| struct ieee80211com *ic = ni->ni_ic; |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_ie_htcap *htcap = (struct ieee80211_ie_htcap *)ie; |
| u_int16_t peer_cap = IEEE80211_HTCAP_CAPABILITIES(htcap); |
| u_int16_t merged_cap = peer_cap & ic->ic_htcap.cap; |
| |
| /* Compare with stored ie, return 0 if unchanged */ |
| if (memcmp(htcap, &ni->ni_ie_htcap, sizeof(struct ieee80211_ie_htcap)) == 0) { |
| return 0; |
| } |
| |
| memcpy(&ni->ni_ie_htcap, htcap, sizeof(ni->ni_ie_htcap)); |
| |
| /* Take the combination of IC and STA parameters */ |
| /* set HT capabilities */ |
| ni->ni_htcap.cap = 0; |
| |
| /* set channel width */ |
| ni->ni_htcap.cap |= peer_cap & IEEE80211_HTCAP_C_CHWIDTH40; |
| |
| /* Set power save mode - STA determines this, not the AP. */ |
| if (vap->iv_opmode == IEEE80211_M_STA) { |
| ni->ni_htcap.pwrsave = ic->ic_htcap.pwrsave; |
| } else if (vap->iv_opmode == IEEE80211_M_HOSTAP) { |
| u_int8_t smps = IEEE80211_HTCAP_PWRSAVE_MODE(htcap); |
| ni->ni_htcap.pwrsave = smps; |
| } else if (vap->iv_opmode == IEEE80211_M_WDS) { |
| /* WDS power save unsupported */ |
| ni->ni_htcap.pwrsave = IEEE80211_HTCAP_C_MIMOPWRSAVE_NONE; |
| } |
| |
| /* Default invalid PS mode to NONE */ |
| if (ni->ni_htcap.pwrsave == IEEE80211_HTCAP_C_MIMOPWRSAVE_NA) |
| ni->ni_htcap.pwrsave = IEEE80211_HTCAP_C_MIMOPWRSAVE_NONE; |
| |
| /* set SHORT GI options */ |
| ni->ni_htcap.cap |= merged_cap & IEEE80211_HTCAP_C_SHORTGI20; |
| ni->ni_htcap.cap |= merged_cap & IEEE80211_HTCAP_C_SHORTGI40; |
| |
| /* Set STBC options */ |
| if ((ic->ic_htcap.cap & IEEE80211_HTCAP_C_TXSTBC) |
| && (IEEE80211_HTCAP_RX_STBC_MODE(htcap))) |
| ni->ni_htcap.cap |= IEEE80211_HTCAP_C_TXSTBC; |
| |
| if (ic->ic_htcap.cap & IEEE80211_HTCAP_C_TXSTBC) |
| ni->ni_htcap.numrxstbcstr = IEEE80211_HTCAP_RX_STBC_MODE(htcap); |
| else |
| ni->ni_htcap.numrxstbcstr = 0; |
| |
| /* delayed block ack */ |
| ni->ni_htcap.cap |= peer_cap & IEEE80211_HTCAP_C_DELAYEDBLKACK; |
| |
| /* Maximum A-MSDU size */ |
| if (peer_cap & IEEE80211_HTCAP_C_MAXAMSDUSIZE_8K) |
| ni->ni_htcap.maxmsdu = 7935; |
| else |
| ni->ni_htcap.maxmsdu = 3839; |
| |
| /* DSSS/CCK mode in 40 MHz */ |
| ni->ni_htcap.cap |= peer_cap & IEEE80211_HTCAP_C_DSSSCCK40; |
| |
| /* PSMP support (only if AP supports) */ |
| ni->ni_htcap.cap |= peer_cap & IEEE80211_HTCAP_C_PSMP; |
| |
| /* set 40 MHz intolerant */ |
| ni->ni_htcap.cap |= peer_cap & IEEE80211_HTCAP_C_40_INTOLERANT; |
| |
| /* set L-SIG TXOP support */ |
| ni->ni_htcap.cap |= peer_cap & IEEE80211_HTCAP_C_LSIGTXOPPROT; |
| |
| /* set maximum A-MPDU size */ |
| ni->ni_htcap.maxampdu = IEEE80211_HTCAP_MAX_AMPDU_LEN(htcap); |
| |
| /* set maximum MPDU spacing */ |
| ni->ni_htcap.mpduspacing = IEEE80211_HTCAP_MIN_AMPDU_SPACING(htcap); |
| |
| /* set MCS rate indexes */ |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS1] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap, IEEE80211_HT_MCSSET_20_40_NSS1); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS2] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap, IEEE80211_HT_MCSSET_20_40_NSS2); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS3] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap, IEEE80211_HT_MCSSET_20_40_NSS3); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS4] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap, IEEE80211_HT_MCSSET_20_40_NSS4); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM1] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap, IEEE80211_HT_MCSSET_20_40_UEQM1); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM2] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap, IEEE80211_HT_MCSSET_20_40_UEQM2); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM3] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap, IEEE80211_HT_MCSSET_20_40_UEQM3); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM4] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap, IEEE80211_HT_MCSSET_20_40_UEQM4); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM5] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap, IEEE80211_HT_MCSSET_20_40_UEQM5); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM6] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap, IEEE80211_HT_MCSSET_20_40_UEQM6); |
| |
| /* set maximum data rate */ |
| ni->ni_htcap.maxdatarate = IEEE80211_HTCAP_HIGHEST_DATA_RATE(htcap); |
| |
| /* set MCS parameters */ |
| ni->ni_htcap.mcsparams = 0; |
| |
| if (IEEE80211_HTCAP_MCS_PARAMS(htcap) & IEEE80211_HTCAP_MCS_TX_SET_DEFINED) { |
| ni->ni_htcap.mcsparams |= IEEE80211_HTCAP_MCS_TX_SET_DEFINED; |
| |
| /* set number of Tx spatial streams */ |
| if(IEEE80211_HTCAP_MCS_PARAMS(htcap) & IEEE80211_HTCAP_MCS_TX_RX_SET_NEQ) { |
| ni->ni_htcap.numtxspstr = IEEE80211_HTCAP_MCS_STREAMS(htcap); |
| ni->ni_htcap.mcsparams |= IEEE80211_HTCAP_MCS_TX_RX_SET_NEQ | |
| (IEEE80211_HTCAP_MCS_PARAMS(htcap) & IEEE80211_HTCAP_MCS_TX_UNEQ_MOD); |
| } else { |
| ni->ni_htcap.numtxspstr = 0; |
| } |
| } else { |
| ni->ni_htcap.numtxspstr = 0; |
| } |
| |
| if (ni->ni_qtn_assoc_ie || (vap->iv_ht_flags & IEEE80211_HTF_LDPC_ALLOW_NON_QTN)) { |
| ni->ni_htcap.cap |= peer_cap & IEEE80211_HTCAP_C_LDPCCODING; |
| } |
| |
| ni->ni_htcap.hc_txbf[0] = ic->ic_htcap.hc_txbf[0] & htcap->hc_txbf[0]; |
| ni->ni_htcap.hc_txbf[1] = ic->ic_htcap.hc_txbf[1] & htcap->hc_txbf[1]; |
| ni->ni_htcap.hc_txbf[2] = ic->ic_htcap.hc_txbf[2] & htcap->hc_txbf[2]; |
| ni->ni_htcap.hc_txbf[3] = ic->ic_htcap.hc_txbf[3] & htcap->hc_txbf[3]; |
| |
| return 1; |
| } |
| static u_int16_t |
| ieee80211_merge_vhtmcs(u_int16_t local_vhtmcs, u_int16_t far_vhtmcs) |
| { |
| /* Spatial stream from 1-8 = 3 (not supported) */ |
| u_int16_t merge_vhtmcsmap = IEEE80211_VHTMCS_ALL_DISABLE; |
| |
| enum ieee80211_vht_nss vhtnss; |
| enum ieee80211_vht_mcs_supported vhtmcs = IEEE80211_VHT_MCS_NA; |
| |
| for (vhtnss = IEEE80211_VHT_NSS1; vhtnss <= IEEE80211_VHT_NSS8; vhtnss++) { |
| |
| /* Check if perticular stream is not supported by peer */ |
| if (((local_vhtmcs & 0x0003) == IEEE80211_VHT_MCS_NA) || |
| ((far_vhtmcs & 0x0003) == IEEE80211_VHT_MCS_NA)) { |
| vhtmcs = IEEE80211_VHT_MCS_NA; |
| } else { |
| vhtmcs = min((local_vhtmcs & 0x0003), (far_vhtmcs & 0x0003)); |
| } |
| |
| switch(vhtnss) { |
| case IEEE80211_VHT_NSS1: |
| merge_vhtmcsmap &= 0xFFFC; |
| merge_vhtmcsmap |= vhtmcs; |
| break; |
| case IEEE80211_VHT_NSS2: |
| merge_vhtmcsmap &= 0xFFF3; |
| merge_vhtmcsmap |= (vhtmcs << 2); |
| break; |
| case IEEE80211_VHT_NSS3: |
| merge_vhtmcsmap &= 0xFFCF; |
| merge_vhtmcsmap |= (vhtmcs << 4); |
| break; |
| case IEEE80211_VHT_NSS4: |
| merge_vhtmcsmap &= 0xFF3F; |
| merge_vhtmcsmap |= (vhtmcs << 6); |
| break; |
| case IEEE80211_VHT_NSS5: |
| merge_vhtmcsmap &= 0xFCFF; |
| merge_vhtmcsmap |= (vhtmcs << 8); |
| break; |
| case IEEE80211_VHT_NSS6: |
| merge_vhtmcsmap &= 0xF3FF; |
| merge_vhtmcsmap |= (vhtmcs << 10); |
| break; |
| case IEEE80211_VHT_NSS7: |
| merge_vhtmcsmap &= 0xCFFF; |
| merge_vhtmcsmap |= (vhtmcs << 12); |
| break; |
| case IEEE80211_VHT_NSS8: |
| merge_vhtmcsmap &= 0x3FFF; |
| merge_vhtmcsmap |= (vhtmcs << 14); |
| break; |
| } |
| local_vhtmcs = local_vhtmcs >> 2; |
| far_vhtmcs = far_vhtmcs >> 2; |
| } |
| |
| return (merge_vhtmcsmap); |
| } |
| |
| |
| void |
| ieee80211_parse_vhtcap(struct ieee80211_node *ni, u_int8_t *ie) |
| { |
| struct ieee80211com *ic = ni->ni_ic; |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_ie_vhtcap *rvhtcap = (struct ieee80211_ie_vhtcap *)ie; |
| u_int32_t peer_vhtcap = IEEE80211_VHTCAP_GET_CAPFLAGS(rvhtcap); |
| struct ieee80211_vhtcap *ic_vhtcap = NULL; |
| u_int32_t merged_cap; |
| |
| if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) |
| ic_vhtcap = &ic->ic_vhtcap_24g; |
| else |
| ic_vhtcap = &ic->ic_vhtcap; |
| |
| merged_cap = peer_vhtcap & ic_vhtcap->cap_flags; |
| |
| /* Following BF related fields require cross merging hence clear them first */ |
| merged_cap &= ~(IEEE80211_VHTCAP_C_SU_BEAM_FORMEE_CAP | |
| IEEE80211_VHTCAP_C_SU_BEAM_FORMER_CAP | |
| IEEE80211_VHTCAP_C_MU_BEAM_FORMEE_CAP | |
| IEEE80211_VHTCAP_C_MU_BEAM_FORMER_CAP); |
| |
| memcpy(&ni->ni_ie_vhtcap, rvhtcap, sizeof(ni->ni_ie_vhtcap)); |
| |
| /* Take the combination of IC and STA parameters */ |
| /* Set VHT capabilities in the node structure */ |
| ni->ni_vhtcap.cap_flags = merged_cap; |
| |
| ni->ni_vhtcap.maxmpdu = min(ic_vhtcap->maxmpdu, |
| IEEE80211_VHTCAP_GET_MAXMPDU(rvhtcap)); |
| ni->ni_vhtcap.chanwidth = min(ic_vhtcap->chanwidth, |
| IEEE80211_VHTCAP_GET_CHANWIDTH(rvhtcap)); |
| |
| ni->ni_vhtcap.rxstbc = IEEE80211_VHTCAP_GET_RXSTBC(rvhtcap); |
| ni->ni_vhtcap.bfstscap = IEEE80211_VHTCAP_GET_BFSTSCAP(rvhtcap); |
| ni->ni_vhtcap.numsounding = IEEE80211_VHTCAP_GET_NUMSOUND(rvhtcap); |
| ni->ni_vhtcap.maxampduexp = min(ic_vhtcap->maxampduexp, |
| IEEE80211_VHTCAP_GET_MAXAMPDUEXP(rvhtcap)); |
| |
| ni->ni_vhtcap.lnkadptcap = min(ic_vhtcap->lnkadptcap, |
| IEEE80211_VHTCAP_GET_LNKADPTCAP(rvhtcap)); |
| |
| if ((vap->iv_vht_flags & IEEE80211_VHTCAP_C_TX_STBC) && |
| (ni->ni_vhtcap.rxstbc)) { |
| ni->ni_vhtcap.cap_flags |= IEEE80211_VHTCAP_C_TX_STBC; |
| } |
| |
| if ((ic->ic_vhtcap.cap_flags & IEEE80211_VHTCAP_C_SU_BEAM_FORMEE_CAP) && |
| IEEE80211_VHTCAP_GET_SU_BEAMFORMER(rvhtcap)) { |
| ni->ni_vhtcap.cap_flags |= IEEE80211_VHTCAP_C_SU_BEAM_FORMER_CAP; |
| } |
| |
| if ((ic->ic_vhtcap.cap_flags & IEEE80211_VHTCAP_C_SU_BEAM_FORMER_CAP) && |
| IEEE80211_VHTCAP_GET_SU_BEAMFORMEE(rvhtcap)) { |
| ni->ni_vhtcap.cap_flags |= IEEE80211_VHTCAP_C_SU_BEAM_FORMEE_CAP; |
| } |
| |
| if ((ic->ic_vhtcap.cap_flags & IEEE80211_VHTCAP_C_MU_BEAM_FORMEE_CAP) && |
| IEEE80211_VHTCAP_GET_MU_BEAMFORMER(rvhtcap)) { |
| ni->ni_vhtcap.cap_flags |= IEEE80211_VHTCAP_C_MU_BEAM_FORMER_CAP; |
| } |
| |
| if ((ic->ic_vhtcap.cap_flags & IEEE80211_VHTCAP_C_MU_BEAM_FORMER_CAP) && |
| IEEE80211_VHTCAP_GET_MU_BEAMFORMEE(rvhtcap)) { |
| ni->ni_vhtcap.cap_flags |= IEEE80211_VHTCAP_C_MU_BEAM_FORMEE_CAP; |
| } |
| |
| ni->ni_vhtcap.txlgimaxrate = min(ic_vhtcap->rxlgimaxrate, |
| IEEE80211_VHTCAP_GET_TX_LGIMAXRATE(rvhtcap)); |
| ni->ni_vhtcap.rxlgimaxrate = min(ic_vhtcap->txlgimaxrate, |
| IEEE80211_VHTCAP_GET_RX_LGIMAXRATE(rvhtcap)); |
| ni->ni_vhtcap.txmcsmap = ieee80211_merge_vhtmcs(ic_vhtcap->rxmcsmap, |
| IEEE80211_VHTCAP_GET_TX_MCS_NSS(rvhtcap)); |
| ni->ni_vhtcap.rxmcsmap = ieee80211_merge_vhtmcs(ic_vhtcap->txmcsmap, |
| IEEE80211_VHTCAP_GET_RX_MCS_NSS(rvhtcap)); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "flags: local(0x%08x) remote(0x%08x) >> node(0x%08x)\n", |
| ic_vhtcap->cap_flags, |
| IEEE80211_VHTCAP_GET_CAPFLAGS(rvhtcap), |
| ni->ni_vhtcap.cap_flags); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "maxmpdu: local(%d) remote(%d) >> node(%d)\n", |
| ic_vhtcap->maxmpdu, |
| IEEE80211_VHTCAP_GET_MAXMPDU(rvhtcap), |
| ni->ni_vhtcap.maxmpdu); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "chanwidth: local(%d) remote(%d) >> node(%d)\n", |
| ic_vhtcap->chanwidth, |
| IEEE80211_VHTCAP_GET_CHANWIDTH(rvhtcap), |
| ni->ni_vhtcap.chanwidth); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "rxstbc: local(%d) remote(%d) >> node(%d)\n", |
| ic_vhtcap->rxstbc, |
| IEEE80211_VHTCAP_GET_RXSTBC(rvhtcap), |
| ni->ni_vhtcap.rxstbc); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "bfstscap: local(%d) remote(%d) >> node(%d)\n", |
| ic_vhtcap->bfstscap, |
| IEEE80211_VHTCAP_GET_BFSTSCAP(rvhtcap), |
| ni->ni_vhtcap.bfstscap); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "numsounding: local(%d) remote(%d) >> node(%d)\n", |
| ic_vhtcap->numsounding, |
| IEEE80211_VHTCAP_GET_NUMSOUND(rvhtcap), |
| ni->ni_vhtcap.numsounding); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "maxampduexp: local(%d) remote(%d) >> node(%d)\n", |
| ic_vhtcap->maxampduexp, |
| IEEE80211_VHTCAP_GET_MAXAMPDUEXP(rvhtcap), |
| ni->ni_vhtcap.maxampduexp); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "lnkadptcap: local(%d) remote(%d) >> node(%d)\n", |
| ic_vhtcap->lnkadptcap, |
| IEEE80211_VHTCAP_GET_LNKADPTCAP(rvhtcap), |
| ni->ni_vhtcap.lnkadptcap); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "rxlgimaxrate: local(%d) remote-tx(%d) >> node(%d)\n", |
| ic_vhtcap->rxlgimaxrate, |
| IEEE80211_VHTCAP_GET_TX_LGIMAXRATE(rvhtcap), |
| ni->ni_vhtcap.rxlgimaxrate); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "txlgimaxrate: local(%d) remote-rx(%d) >> node(%d)\n", |
| ic_vhtcap->txlgimaxrate, |
| IEEE80211_VHTCAP_GET_RX_LGIMAXRATE(rvhtcap), |
| ni->ni_vhtcap.txlgimaxrate); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "rxmcsmap: local(0x%08x) remote-tx(0x%08x) >> node(0x%08x)\n", |
| ic_vhtcap->rxmcsmap, |
| IEEE80211_VHTCAP_GET_TX_MCS_NSS(rvhtcap), |
| ni->ni_vhtcap.rxmcsmap); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "txmcsmap: local(0x%08x) remote-rx(0x%08x) >> node(0x%08x)\n", |
| ic_vhtcap->txmcsmap, |
| IEEE80211_VHTCAP_GET_RX_MCS_NSS(rvhtcap), |
| ni->ni_vhtcap.txmcsmap); |
| } |
| |
| int |
| ieee80211_check_and_parse_vhtcap(struct ieee80211_node *ni, u_int8_t *ie) |
| { |
| struct ieee80211_ie_vhtcap *rvhtcap = (struct ieee80211_ie_vhtcap *)ie; |
| |
| /* Compare with stored ie, return 0 if unchanged */ |
| if (memcmp(rvhtcap, &ni->ni_ie_vhtcap, sizeof(struct ieee80211_ie_vhtcap)) == 0) { |
| return 0; |
| } |
| |
| ieee80211_parse_vhtcap(ni, ie); |
| return 1; |
| } |
| |
| static uint8_t * |
| ieee80211_get_vhtcap_from_brcmvht(struct ieee80211_node *ni, uint8_t *ie) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_ie_brcm_vht *brcm_vht = (struct ieee80211_ie_brcm_vht *)ie; |
| uint8_t *vhtie = brcm_vht->vht_ies; |
| uint8_t *end = ie + ie[1]; |
| uint8_t *vhtcap = NULL; |
| |
| while (vhtie < end) { |
| switch (*vhtie) { |
| case IEEE80211_ELEMID_VHTCAP: |
| vhtcap = vhtie; |
| return vhtcap; |
| default: |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "unhandled id %u, len %u", *vhtie, vhtie[1]); |
| vap->iv_stats.is_rx_elem_unknown++; |
| break; |
| } |
| vhtie += vhtie[1] + 2; |
| } |
| |
| return vhtcap; |
| } |
| |
| static uint8_t * |
| ieee80211_get_vhtop_from_brcmvht(struct ieee80211_node *ni, uint8_t *ie) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_ie_brcm_vht *brcm_vht = (struct ieee80211_ie_brcm_vht *)ie; |
| uint8_t *vhtie = brcm_vht->vht_ies; |
| uint8_t *end = ie + ie[1]; |
| uint8_t *vhtop = NULL; |
| |
| while (vhtie < end) { |
| switch (*vhtie) { |
| case IEEE80211_ELEMID_VHTOP: |
| vhtop = vhtie; |
| return vhtop; |
| default: |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "unhandled id %u, len %u", *vhtie, vhtie[1]); |
| vap->iv_stats.is_rx_elem_unknown++; |
| break; |
| } |
| vhtie += vhtie[1] + 2; |
| } |
| |
| return vhtop; |
| } |
| |
| |
| void |
| ieee80211_parse_measinfo(struct ieee80211_node *ni, u_int8_t *ie) |
| { |
| #if defined(CONFIG_QTN_80211K_SUPPORT) |
| struct ieee80211com *ic = ni->ni_ic; |
| struct ieee80211_ie_measure_comm *ie_comm = (struct ieee80211_ie_measure_comm *)(void*) ie; |
| struct ieee80211_ie_measreq *measinfo = (struct ieee80211_ie_measreq *)(void*) ie_comm->data; |
| struct ieee80211_channel *ch; |
| u_int32_t duration; |
| u_int64_t tsf; |
| |
| if ( !(ic->ic_flags & IEEE80211_F_CCA) ) { |
| ic->ic_flags |= IEEE80211_F_CCA; |
| |
| ch = findchannel(ic, measinfo->chan_num, ic->ic_des_mode); |
| |
| if (ie_comm->token - ic->ic_cca_token > 0) { |
| ic->ic_cca_token = ie_comm->token; |
| tsf = ntohll(measinfo->start_tsf); |
| duration = ntohs(measinfo->duration_tu); |
| duration = IEEE80211_TU_TO_MS(duration); |
| ic->ic_set_start_cca_measurement(ic, ch, |
| tsf, duration); |
| } |
| } |
| #else |
| struct ieee80211com *ic = ni->ni_ic; |
| struct ieee80211_ie_measreq *measinfo = (struct ieee80211_ie_measreq *)(void*) ie; |
| struct ieee80211_channel *ch; |
| u_int32_t duration; |
| u_int64_t tsf; |
| |
| if ( !(ic->ic_flags & IEEE80211_F_CCA) ) { |
| ic->ic_flags |= IEEE80211_F_CCA; |
| |
| ch = findchannel(ic, measinfo->chan_num, ic->ic_des_mode); |
| |
| if (measinfo->meas_token - ic->ic_cca_token > 0) { |
| ic->ic_cca_token = measinfo->meas_token; |
| tsf = ntohll(measinfo->start_tsf); |
| duration = ntohs(measinfo->duration_tu); |
| duration = IEEE80211_TU_TO_MS(duration); |
| ic->ic_set_start_cca_measurement(ic, ch, |
| tsf, duration); |
| } |
| } |
| #endif |
| } |
| |
| int |
| ieee80211_parse_htinfo(struct ieee80211_node *ni, u_int8_t *ie) |
| { |
| struct ieee80211com *ic = ni->ni_ic; |
| struct ieee80211_ie_htinfo *htinfo = (struct ieee80211_ie_htinfo *)ie; |
| |
| /* Compare with stored ie, return 0 if unchanged */ |
| if (memcmp(htinfo, &ni->ni_ie_htinfo, sizeof(struct ieee80211_ie_htinfo)) == 0) |
| return 0; |
| |
| memcpy(&ni->ni_ie_htinfo, htinfo, sizeof(ni->ni_ie_htinfo)); |
| |
| /* set primary channel */ |
| ni->ni_htinfo.ctrlchannel = IEEE80211_HTINFO_PRIMARY_CHANNEL(htinfo); |
| |
| /* set byte 1 values */ |
| ni->ni_htinfo.byte1 = 0; |
| |
| /* set the channel width and secondary channel offset */ |
| if ((IEEE80211_HTINFO_BYTE_ONE(htinfo) & IEEE80211_HTINFO_B1_REC_TXCHWIDTH_40) && |
| (ic->ic_htcap.cap & IEEE80211_HTCAP_C_CHWIDTH40)) { |
| ni->ni_htinfo.choffset = IEEE80211_HTINFO_B1_EXT_CHOFFSET(htinfo); |
| ni->ni_htinfo.byte1 |= IEEE80211_HTINFO_B1_REC_TXCHWIDTH_40; |
| } else { |
| ni->ni_htinfo.choffset = 0; |
| } |
| |
| /* force 20MHz bw if secondary channel offset is unknown */ |
| if ((ni->ni_htcap.cap & IEEE80211_HTCAP_C_CHWIDTH40) && !ni->ni_htinfo.choffset) |
| ni->ni_htcap.cap &= ~IEEE80211_HTCAP_C_CHWIDTH40; |
| |
| /* XXX set the S-PSMP support */ |
| if (ni->ni_htcap.cap & IEEE80211_HTCAP_C_PSMP) |
| { |
| if (IEEE80211_HTINFO_BYTE_ONE(htinfo) & IEEE80211_HTINFO_B1_CONTROLLED_ACCESS) |
| ni->ni_htinfo.byte1 |= (ic->ic_htinfo.byte1 & IEEE80211_HTINFO_B1_CONTROLLED_ACCESS); |
| } |
| |
| /* service level granularity */ |
| if (ni->ni_htinfo.byte1 & IEEE80211_HTINFO_B1_CONTROLLED_ACCESS) |
| ni->ni_htinfo.sigranularity = ic->ic_htinfo.sigranularity; |
| |
| /* set byte 2 values */ |
| ni->ni_htinfo.byte2 = 0; |
| |
| ni->ni_htinfo.opmode = IEEE80211_HTINFO_B2_OP_MODE(htinfo); |
| ni->ni_htinfo.byte2 |= IEEE80211_HTINFO_BYTE_TWO(htinfo) & IEEE80211_HTINFO_B2_NON_GF_PRESENT; |
| ni->ni_htinfo.byte2 |= IEEE80211_HTINFO_BYTE_TWO(htinfo) & IEEE80211_HTINFO_B2_OBSS_PROT; |
| |
| /* set byte 3 values */ |
| ni->ni_htinfo.byte3 = 0; |
| |
| /* set byte 4 values */ |
| ni->ni_htinfo.byte4 = 0; |
| |
| ni->ni_htinfo.byte4 |= IEEE80211_HTINFO_BYTE_FOUR(htinfo) & IEEE80211_HTINFO_B4_DUAL_BEACON; |
| ni->ni_htinfo.byte4 |= IEEE80211_HTINFO_BYTE_FOUR(htinfo) & IEEE80211_HTINFO_B4_DUAL_CTS; |
| |
| /* set byte 5 values */ |
| ni->ni_htinfo.byte5 = 0; |
| |
| ni->ni_htinfo.byte5 |= IEEE80211_HTINFO_BYTE_FIVE(htinfo) & IEEE80211_HTINFO_B5_STBC_BEACON; |
| ni->ni_htinfo.byte5 |= IEEE80211_HTINFO_BYTE_FIVE(htinfo) & IEEE80211_HTINFO_B5_LSIGTXOPPROT; |
| ni->ni_htinfo.byte5 |= IEEE80211_HTINFO_BYTE_FIVE(htinfo) & IEEE80211_HTINFO_B5_PCO_ACTIVE; |
| ni->ni_htinfo.byte5 |= IEEE80211_HTINFO_BYTE_FIVE(htinfo) & IEEE80211_HTINFO_B5_40MHZPHASE; |
| |
| /* set basic rates */ |
| /* CBW = 20/40 MHz, Nss = 1, Nes = 1, EQM/ No EQM */ |
| ni->ni_htinfo.basicmcsset[IEEE80211_HT_MCSSET_20_40_NSS1] = |
| IEEE80211_HTINFO_BASIC_MCS_VALUE(htinfo, IEEE80211_HT_MCSSET_20_40_NSS1); |
| |
| /* CBW = 20/40 MHz, Nss = 2, Nes = 1, EQM */ |
| ni->ni_htinfo.basicmcsset[IEEE80211_HT_MCSSET_20_40_NSS2] = |
| IEEE80211_HTINFO_BASIC_MCS_VALUE(htinfo, IEEE80211_HT_MCSSET_20_40_NSS2); |
| |
| /* Enable RTS-CTS if HT-protection bit is set */ |
| if (IEEE80211_11N_PROTECT_ENABLED(ic) && |
| ni->ni_htinfo.opmode && !ic->ic_local_rts && |
| (ni->ni_vap->iv_opmode == IEEE80211_M_STA || |
| ni->ni_vap->iv_opmode == IEEE80211_M_WDS)) { |
| /* RTS-CTS can be enabled only when Rev B and later is used */ |
| if (get_hardware_revision() != HARDWARE_REVISION_RUBY_A) { |
| ic->ic_local_rts = 1; |
| ic->ic_use_rtscts(ic); |
| } |
| } |
| /* Disable RTS-CTS if HT-protection bit is not set * |
| * and if RTS-CTS is in use currently */ |
| if (IEEE80211_11N_PROTECT_ENABLED(ic) && |
| ic->ic_local_rts && !(ni->ni_htinfo.opmode) && |
| (ni->ni_vap->iv_opmode == IEEE80211_M_STA || |
| ni->ni_vap->iv_opmode == IEEE80211_M_WDS)) { |
| /* RTS-CTS can be enabled only when Rev B and later is used */ |
| if (get_hardware_revision() != HARDWARE_REVISION_RUBY_A) { |
| ic->ic_local_rts = 0; |
| ic->ic_use_rtscts(ic); |
| } |
| } |
| return 1; |
| } |
| |
| int |
| ieee80211_parse_vhtop(struct ieee80211_node *ni, u_int8_t *ie) |
| { |
| struct ieee80211_ie_vhtop *rvhtop = (struct ieee80211_ie_vhtop *)ie; |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = ni->ni_ic; |
| struct ieee80211_vhtop *ic_vhtop = NULL; |
| |
| /* Compare with stored ie, return 0 if unchanged */ |
| if (memcmp(rvhtop, &ni->ni_ie_vhtop, sizeof(struct ieee80211_ie_vhtop)) == 0) |
| return 0; |
| |
| if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) |
| ic_vhtop = &ic->ic_vhtop_24g; |
| else |
| ic_vhtop = &ic->ic_vhtop; |
| |
| memcpy(&ni->ni_ie_vhtop, rvhtop, sizeof(ni->ni_ie_vhtop)); |
| |
| ni->ni_vhtop.chanwidth = MIN(IEEE80211_VHTOP_GET_CHANWIDTH(rvhtop), ic_vhtop->chanwidth); |
| ni->ni_vhtop.centerfreq0 = IEEE80211_VHTOP_GET_CENTERFREQ0(rvhtop); |
| ni->ni_vhtop.centerfreq1 = IEEE80211_VHTOP_GET_CENTERFREQ1(rvhtop); |
| ni->ni_vhtop.basicvhtmcsnssset = IEEE80211_VHTOP_GET_BASIC_MCS_NSS(rvhtop); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "vht op: chan width: %d\n", ni->ni_vhtop.chanwidth); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "vht op: center freq0: %d\n", ni->ni_vhtop.centerfreq0); |
| |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "vht op: center freq1: %d\n", ni->ni_vhtop.centerfreq1); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "vht op: basicvhtmcsnssset: 0x%08x\n", ni->ni_vhtop.basicvhtmcsnssset); |
| |
| return 1; |
| } |
| |
| /* |
| static int |
| ieee80211_setup_vhtcap(struct ieee80211_node *ni, u_int8_t *ie) |
| { |
| return 1; |
| } |
| */ |
| |
| static int |
| ieee80211_setup_htcap(struct ieee80211_node *ni, u_int8_t *ie) |
| { |
| struct ieee80211com *ic = ni->ni_ic; |
| struct ieee80211_ie_htcap *htcap = (struct ieee80211_ie_htcap *)(void*) ie; |
| struct ieee80211vap *vap = ni->ni_vap; |
| int error = 0; |
| u_int32_t flags = vap->iv_ht_flags; |
| u_int16_t peer_cap = IEEE80211_HTCAP_CAPABILITIES(htcap); |
| u_int16_t merged_cap = peer_cap & ic->ic_htcap.cap; |
| |
| /* save the original HT CAP IE */ |
| memcpy(&ni->ni_ie_htcap, htcap, sizeof(ni->ni_ie_htcap)); |
| /* Take the combination of IC and STA parameters */ |
| /* set HT capabilities */ |
| ni->ni_htcap.cap = 0; |
| |
| /* set channel width */ |
| if ((flags & IEEE80211_HTF_CBW_40MHZ_ONLY) |
| && !(peer_cap & IEEE80211_HTCAP_C_CHWIDTH40)) |
| error |= 0x0001; |
| else |
| ni->ni_htcap.cap |= (peer_cap & IEEE80211_HTCAP_C_CHWIDTH40); |
| |
| /* set power save mode */ |
| /* NOTE: only ever called when we're running as an AP - take the SM power save as |
| * being what the client advertises, NOT what the AP wants. |
| */ |
| { |
| int pwrsave = IEEE80211_HTCAP_PWRSAVE_MODE(htcap); |
| |
| if (pwrsave == IEEE80211_HTCAP_C_MIMOPWRSAVE_NA) |
| { |
| /* Default to Dyanmic powersave if invalid value passed in the (re)association request */ |
| pwrsave = IEEE80211_HTCAP_C_MIMOPWRSAVE_NONE; |
| } |
| ni->ni_htcap.pwrsave = pwrsave; |
| } |
| |
| /* set SHORT GI options */ |
| if ((flags & IEEE80211_HTF_SHORTGI20_ONLY) && |
| !(peer_cap & IEEE80211_HTCAP_C_SHORTGI20)) |
| error |= 0x0002; |
| else |
| ni->ni_htcap.cap |= (merged_cap & IEEE80211_HTCAP_C_SHORTGI20); |
| |
| if ((flags & IEEE80211_HTF_SHORTGI40_ONLY) && |
| !(peer_cap & IEEE80211_HTCAP_C_SHORTGI40)) |
| error |= 0x0004; |
| else |
| ni->ni_htcap.cap |= (merged_cap & IEEE80211_HTCAP_C_SHORTGI40); |
| |
| /* Set STBC options */ |
| if ((flags & IEEE80211_HTF_TXSTBC_ONLY) && |
| !(IEEE80211_HTCAP_RX_STBC_MODE(htcap))) |
| error |= 0x0008; |
| else { |
| if ((vap->iv_ht_flags & IEEE80211_HTF_STBC_ENABLED) && |
| IEEE80211_HTCAP_RX_STBC_MODE(htcap)) |
| ni->ni_htcap.cap |= IEEE80211_HTCAP_C_TXSTBC; |
| } |
| |
| if ((flags & IEEE80211_HTF_RXSTBC_ONLY) && |
| !(peer_cap & IEEE80211_HTCAP_C_TXSTBC)) |
| error |= 0x0010; |
| else { |
| if (vap->iv_ht_flags & IEEE80211_HTF_STBC_ENABLED) { |
| ni->ni_htcap.numrxstbcstr = IEEE80211_HTCAP_RX_STBC_MODE(htcap); |
| /* If STA is capable of receive more streams then we support for tx, |
| then limit our transmission to what we support */ |
| ni->ni_htcap.numrxstbcstr = (ni->ni_htcap.numrxstbcstr > IEEE80211_MAX_TX_STBC_SS) ? |
| IEEE80211_MAX_TX_STBC_SS : ni->ni_htcap.numrxstbcstr; |
| } |
| else |
| ni->ni_htcap.numrxstbcstr = 0; |
| } |
| |
| if (IEEE80211_HTCAP_RX_STBC_MODE(htcap)) { |
| ni->ni_htcap.cap |= IEEE80211_HTCAP_C_RXSTBC; |
| } |
| |
| /* delayed block ack */ |
| ni->ni_htcap.cap |= (peer_cap & IEEE80211_HTCAP_C_DELAYEDBLKACK); |
| |
| /* Maximum A-MSDU size */ |
| if (peer_cap & IEEE80211_HTCAP_C_MAXAMSDUSIZE_8K) |
| ni->ni_htcap.maxmsdu = 7935; |
| else |
| ni->ni_htcap.maxmsdu = 3839; |
| |
| /* DSSS/CCK mode in 40 MHz */ |
| if (flags & IEEE80211_HTF_DSSS_40MHZ_ONLY) { |
| if (!(peer_cap & IEEE80211_HTCAP_C_DSSSCCK40)) |
| error |= 0x0020; |
| } |
| ni->ni_htcap.cap |= (peer_cap & IEEE80211_HTCAP_C_DSSSCCK40); |
| |
| /* PSMP support (only if AP supports) */ |
| if ((flags & IEEE80211_HTF_PSMP_SUPPORT_ONLY) |
| && !(peer_cap & IEEE80211_HTCAP_C_PSMP)) |
| error |= 0x0040; |
| else |
| ni->ni_htcap.cap |= (peer_cap & IEEE80211_HTCAP_C_PSMP); |
| |
| /* set 40 MHz intolerant */ |
| ni->ni_htcap.cap |= (peer_cap & IEEE80211_HTCAP_C_40_INTOLERANT); |
| |
| /* set L-SIG TXOP support */ |
| if ((flags & IEEE80211_HTF_LSIG_TXOP_ONLY) |
| && !(peer_cap & IEEE80211_HTCAP_C_LSIGTXOPPROT)) |
| error |= 0x0080; |
| else |
| ni->ni_htcap.cap |= (peer_cap & IEEE80211_HTCAP_C_LSIGTXOPPROT); |
| |
| /* set maximum A-MPDU size */ |
| ni->ni_htcap.maxampdu = IEEE80211_HTCAP_MAX_AMPDU_LEN(htcap); |
| |
| /* set maximum MPDU spacing */ |
| ni->ni_htcap.mpduspacing = IEEE80211_HTCAP_MIN_AMPDU_SPACING(htcap); |
| |
| /* set MCS rate indexes */ |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS1] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap,IEEE80211_HT_MCSSET_20_40_NSS1); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS2] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap,IEEE80211_HT_MCSSET_20_40_NSS2); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS3] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap,IEEE80211_HT_MCSSET_20_40_NSS3); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS4] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap,IEEE80211_HT_MCSSET_20_40_NSS4); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM1] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap,IEEE80211_HT_MCSSET_20_40_UEQM1); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM2] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap,IEEE80211_HT_MCSSET_20_40_UEQM2); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM3] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap,IEEE80211_HT_MCSSET_20_40_UEQM3); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM4] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap,IEEE80211_HT_MCSSET_20_40_UEQM4); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM5] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap,IEEE80211_HT_MCSSET_20_40_UEQM5); |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM6] = |
| IEEE80211_HTCAP_MCS_VALUE(htcap,IEEE80211_HT_MCSSET_20_40_UEQM6); |
| |
| /* set maximum data rate */ |
| ni->ni_htcap.maxdatarate = IEEE80211_HTCAP_HIGHEST_DATA_RATE(htcap); |
| |
| /* set MCS parameters */ |
| ni->ni_htcap.mcsparams = 0; |
| |
| if (IEEE80211_HTCAP_MCS_PARAMS(htcap) & IEEE80211_HTCAP_MCS_TX_SET_DEFINED) |
| { |
| ni->ni_htcap.mcsparams |= IEEE80211_HTCAP_MCS_TX_SET_DEFINED; |
| |
| /* set number of Tx spatial streams */ |
| if(IEEE80211_HTCAP_MCS_PARAMS(htcap) & IEEE80211_HTCAP_MCS_TX_RX_SET_NEQ) |
| { |
| ni->ni_htcap.numtxspstr = IEEE80211_HTCAP_MCS_STREAMS(htcap); |
| ni->ni_htcap.mcsparams |= IEEE80211_HTCAP_MCS_TX_RX_SET_NEQ | |
| (IEEE80211_HTCAP_MCS_PARAMS(htcap) & IEEE80211_HTCAP_MCS_TX_UNEQ_MOD); |
| } |
| else |
| ni->ni_htcap.numtxspstr = 0; |
| } |
| else |
| ni->ni_htcap.numtxspstr = 0; |
| |
| if ((flags & IEEE80211_HTF_LDPC_ENABLED) && |
| (peer_cap & IEEE80211_HTCAP_C_LDPCCODING)) { |
| ni->ni_htcap.cap |= IEEE80211_HTCAP_C_LDPCCODING; |
| } |
| |
| if ((flags & IEEE80211_HTF_NSS_2_ONLY) && (ni->ni_htcap.numrxstbcstr < 2)) |
| error |= 0x0100; |
| |
| if (ni->ni_vendor != PEER_VENDOR_RLNK) { |
| ni->ni_htcap.hc_txbf[0] = ic->ic_htcap.hc_txbf[0] & htcap->hc_txbf[0]; |
| ni->ni_htcap.hc_txbf[1] = ic->ic_htcap.hc_txbf[1] & htcap->hc_txbf[1]; |
| ni->ni_htcap.hc_txbf[2] = ic->ic_htcap.hc_txbf[2] & htcap->hc_txbf[2]; |
| ni->ni_htcap.hc_txbf[3] = ic->ic_htcap.hc_txbf[3] & htcap->hc_txbf[3]; |
| } |
| |
| return error; |
| } |
| |
| static inline int ieee80211_ssid_compare(struct ieee80211vap *vap, struct ieee80211_scanparams *p_scan) |
| { |
| return ((vap->iv_des_ssid[0].len != p_scan->ssid[1]) || |
| (memcmp(vap->iv_des_ssid[0].ssid, p_scan->ssid + 2, p_scan->ssid[1]) != 0)); |
| } |
| |
| void extender_event_data_prepare(struct ieee80211com *ic, |
| struct ieee80211_scanparams *p_scan, |
| struct qtn_wds_ext_event_data *data, |
| uint8_t cmd, |
| uint8_t *peer_mac) |
| { |
| memset(data, 0, sizeof(struct qtn_wds_ext_event_data)); |
| strncpy(data->name, "QTN-WDS-EXT", sizeof(data->name) - 1); |
| data->cmd = cmd; |
| data->extender_role = ic->ic_extender_role; |
| data->bandwidth = ic->ic_extender_rbs_bw; |
| if (peer_mac) |
| memcpy(data->mac, peer_mac, IEEE80211_ADDR_LEN); |
| if (p_scan) |
| data->channel = p_scan->bchan; |
| } |
| |
| int ieee80211_extender_send_event( |
| struct ieee80211vap *vap, |
| const struct qtn_wds_ext_event_data *p_data, uint8_t *ie) |
| { |
| struct qtn_wds_ext_event_data *wds_event_data; |
| uint8_t event_data[IEEE80211_MAX_EXT_EVENT_DATA_LEN]; |
| union iwreq_data wreq; |
| |
| memset(event_data, 0, sizeof(event_data)); |
| wds_event_data = (struct qtn_wds_ext_event_data *)event_data; |
| |
| if (sizeof(*p_data) > sizeof(event_data)) |
| return 0; |
| memcpy(wds_event_data, p_data, sizeof(*p_data)); |
| |
| if ((wds_event_data->cmd != WDS_EXT_LINK_STATUS_UPDATE) && ie) { |
| wds_event_data->ie_len = ie[1] + 2; |
| if ((sizeof(*p_data) + wds_event_data->ie_len) > sizeof(event_data)) |
| return 0; |
| memcpy(event_data + sizeof(*p_data), ie, wds_event_data->ie_len); |
| } |
| |
| memset(&wreq, 0, sizeof(wreq)); |
| wreq.data.length = sizeof(*wds_event_data) + (ie ? wds_event_data->ie_len : 0); |
| wireless_send_event(vap->iv_dev, IWEVCUSTOM, &wreq, (char *)&event_data); |
| |
| return 0; |
| } |
| |
| struct ieee80211_extender_wds_info * |
| ieee80211_extender_find_peer_wds_info(struct ieee80211com *ic, uint8_t *mac_addr) |
| { |
| struct ieee80211_extender_wds_info *peer_wds = NULL; |
| struct ieee80211vap *primary_vap = TAILQ_FIRST(&ic->ic_vaps); |
| unsigned long flags; |
| int hash; |
| |
| hash = IEEE80211_NODE_HASH(mac_addr); |
| spin_lock_irqsave(&primary_vap->iv_extender_wds_lock, flags); |
| LIST_FOREACH(peer_wds, &primary_vap->iv_extender_wds_hash[hash], peer_wds_hash) { |
| if (IEEE80211_ADDR_EQ(mac_addr, peer_wds->peer_addr)) { |
| break; |
| } |
| } |
| spin_unlock_irqrestore(&primary_vap->iv_extender_wds_lock, flags); |
| |
| return peer_wds; |
| } |
| |
| static struct ieee80211_extender_wds_info* |
| ieee80211_extender_create_peer_wds_info(struct ieee80211com *ic, uint8_t *mac_addr) |
| { |
| struct ieee80211_extender_wds_info *peer_wds = NULL; |
| struct ieee80211vap *primary_vap = TAILQ_FIRST(&ic->ic_vaps); |
| unsigned long flags; |
| int update_beacon = 0; |
| int rbs_num = 0; |
| int hash; |
| int i; |
| |
| MALLOC(peer_wds, struct ieee80211_extender_wds_info *, sizeof(*peer_wds), |
| M_DEVBUF, M_WAITOK); |
| if (peer_wds) { |
| hash = IEEE80211_NODE_HASH(mac_addr); |
| memcpy(peer_wds->peer_addr, mac_addr, IEEE80211_ADDR_LEN); |
| spin_lock_irqsave(&primary_vap->iv_extender_wds_lock, flags); |
| LIST_INSERT_HEAD(&primary_vap->iv_extender_wds_hash[hash], peer_wds, peer_wds_hash); |
| if ((ic->ic_extender_role == IEEE80211_EXTENDER_ROLE_MBS) && |
| (ic->ic_extender_rbs_num < QTN_MAX_RBS_NUM)) { |
| for(i=0; i<QTN_MAX_RBS_NUM; i++) { |
| if (is_zero_ether_addr(ic->ic_extender_rbs_bssid[i])) { |
| IEEE80211_ADDR_COPY(ic->ic_extender_rbs_bssid[i], mac_addr); |
| update_beacon = 1; |
| break; |
| } |
| } |
| |
| for(i=0; i<QTN_MAX_RBS_NUM; i++) { |
| if (!is_zero_ether_addr(ic->ic_extender_rbs_bssid[i])) |
| rbs_num++; |
| } |
| ic->ic_extender_rbs_num = rbs_num; |
| } |
| spin_unlock_irqrestore(&primary_vap->iv_extender_wds_lock, flags); |
| |
| if (ic->ic_extender_role == IEEE80211_EXTENDER_ROLE_RBS) |
| IEEE80211_ADDR_COPY(ic->ic_extender_mbs_bssid, mac_addr); |
| |
| IEEE80211_EXTENDER_DPRINTF(primary_vap, IEEE80211_EXTENDER_MSG_DBG, |
| "EXTENDER %s: add wds peer [%pM]\n", __func__, |
| peer_wds->peer_addr); |
| |
| if (update_beacon) |
| ic->ic_beacon_update(primary_vap); |
| } |
| return peer_wds; |
| } |
| |
| int |
| ieee80211_extender_remove_peer_wds_info(struct ieee80211com *ic, |
| uint8_t *mac_addr) |
| { |
| struct ieee80211_extender_wds_info *peer_wds = NULL; |
| struct ieee80211vap *primary_vap = TAILQ_FIRST(&ic->ic_vaps); |
| unsigned long flags; |
| int update_beacon = 0; |
| int rbs_num = 0; |
| int hash; |
| int i; |
| |
| hash = IEEE80211_NODE_HASH(mac_addr); |
| spin_lock_irqsave(&primary_vap->iv_extender_wds_lock, flags); |
| LIST_FOREACH(peer_wds, &primary_vap->iv_extender_wds_hash[hash], peer_wds_hash) { |
| if (IEEE80211_ADDR_EQ(mac_addr, peer_wds->peer_addr)) { |
| LIST_REMOVE(peer_wds, peer_wds_hash); |
| FREE(peer_wds, M_DEVBUF); |
| |
| if ((ic->ic_extender_role == IEEE80211_EXTENDER_ROLE_MBS) && |
| (ic->ic_extender_rbs_num > 0)) { |
| for(i=0; i<QTN_MAX_RBS_NUM; i++) { |
| if (IEEE80211_ADDR_EQ(ic->ic_extender_rbs_bssid[i], mac_addr)) { |
| IEEE80211_ADDR_SET_NULL(ic->ic_extender_rbs_bssid[i]); |
| update_beacon = 1; |
| break; |
| } |
| } |
| |
| for(i=0; i<QTN_MAX_RBS_NUM; i++) { |
| if (!is_zero_ether_addr(ic->ic_extender_rbs_bssid[i])) |
| rbs_num++; |
| } |
| ic->ic_extender_rbs_num = rbs_num; |
| } else if (ic->ic_extender_role == IEEE80211_EXTENDER_ROLE_RBS) { |
| IEEE80211_ADDR_SET_NULL(ic->ic_extender_mbs_bssid); |
| ic->ic_extender_rbs_num = 0; |
| for(i=0; i<QTN_MAX_RBS_NUM; i++) |
| IEEE80211_ADDR_SET_NULL(ic->ic_extender_rbs_bssid[i]); |
| update_beacon = 1; |
| } |
| |
| break; |
| } |
| } |
| spin_unlock_irqrestore(&primary_vap->iv_extender_wds_lock, flags); |
| |
| if (update_beacon) |
| ic->ic_beacon_update(primary_vap); |
| |
| return 0; |
| } |
| |
| void |
| ieee80211_extender_notify_ext_role(struct ieee80211_node *ni) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct qtn_wds_ext_event_data extender_event_data; |
| struct ieee80211_qtn_ext_bssid *ext_bssid_ie = |
| (struct ieee80211_qtn_ext_bssid *)ni->ni_ext_bssid_ie; |
| int i; |
| |
| memset(&extender_event_data, 0, sizeof(extender_event_data)); |
| extender_event_data.cmd = WDS_EXT_STA_UPDATE_EXT_INFO; |
| extender_event_data.extender_role = ni->ni_ext_role; |
| strncpy(extender_event_data.name, "QTN-WDS-EXT", sizeof(extender_event_data.name)); |
| memcpy(extender_event_data.mac, ni->ni_bssid, IEEE80211_ADDR_LEN); |
| |
| if (ext_bssid_ie) { |
| IEEE80211_EXTENDER_DPRINTF(vap, IEEE80211_EXTENDER_MSG_DBG, |
| "EXTENDER %s: trigger role info upate event, extender role [%u], " |
| "mbs address [%pM], rbs num %u, rbs address: ", __func__, ni->ni_ext_role, |
| ext_bssid_ie->mbs_bssid, ext_bssid_ie->rbs_num); |
| for (i=0; i<QTN_MAX_RBS_NUM; i++) { |
| if (!is_zero_ether_addr(ext_bssid_ie->rbs_bssid[i])) { |
| IEEE80211_EXTENDER_DPRINTF(vap, IEEE80211_EXTENDER_MSG_DBG, "%pM\n", |
| ext_bssid_ie->rbs_bssid[i]); |
| } |
| } |
| } else { |
| IEEE80211_EXTENDER_DPRINTF(vap, IEEE80211_EXTENDER_MSG_DBG, "EXTENDER %s: " |
| "trigger role info upate event, extender role: none\n", __func__); |
| } |
| |
| ieee80211_extender_send_event(vap, &extender_event_data, (uint8_t *)ext_bssid_ie); |
| } |
| |
| void |
| ieee80211_extender_sta_update_info(struct ieee80211_node *ni, |
| const struct ieee80211_qtn_ext_role *ie_role, |
| const struct ieee80211_qtn_ext_bssid *ie_bssid) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| int update = 0; |
| if ((ni == vap->iv_bss) && (ni->ni_flags & IEEE80211_NODE_AUTH)) { |
| if (ie_role) { |
| if (ni->ni_ext_role != ie_role->role) { |
| ni->ni_ext_role = ie_role->role; |
| update = 1; |
| } |
| } else { |
| if (ni->ni_ext_role != IEEE80211_EXTENDER_ROLE_NONE) { |
| ni->ni_ext_role = IEEE80211_EXTENDER_ROLE_NONE; |
| update = 1; |
| } |
| } |
| |
| if (ie_bssid) { |
| if (!ni->ni_ext_bssid_ie || memcmp(ie_bssid, ni->ni_ext_bssid_ie, sizeof(*ie_bssid))) { |
| ieee80211_saveie(&ni->ni_ext_bssid_ie, (uint8_t *)ie_bssid); |
| update = 1; |
| } |
| } else { |
| if (ni->ni_ext_bssid_ie != NULL) { |
| FREE(ni->ni_ext_bssid_ie, M_DEVBUF); |
| ni->ni_ext_bssid_ie = NULL; |
| update = 1; |
| } |
| } |
| |
| if (update) |
| ieee80211_extender_notify_ext_role(ni); |
| } |
| } |
| |
| void |
| ieee80211_extender_vdetach(struct ieee80211vap *vap) |
| { |
| int i; |
| unsigned long flags; |
| struct ieee80211_extender_wds_info *peer_wds = NULL; |
| |
| spin_lock_irqsave(&vap->iv_extender_wds_lock, flags); |
| for (i = 0; i < IEEE80211_NODE_HASHSIZE; i++) { |
| LIST_FOREACH(peer_wds, &vap->iv_extender_wds_hash[i], peer_wds_hash) { |
| if (peer_wds != NULL) { |
| LIST_REMOVE(peer_wds, peer_wds_hash); |
| FREE(peer_wds, M_DEVBUF); |
| } |
| } |
| } |
| spin_unlock_irqrestore(&vap->iv_extender_wds_lock, flags); |
| } |
| |
| static int extender_role_to_event_cmd(uint8_t role) |
| { |
| if (role == IEEE80211_EXTENDER_ROLE_MBS) |
| return WDS_EXT_RECEIVED_MBS_IE; |
| else |
| return WDS_EXT_RECEIVED_RBS_IE; |
| } |
| |
| void ieee80211_extender_cleanup_wds_link(struct ieee80211vap *vap) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211vap *pri_vap = TAILQ_FIRST(&ic->ic_vaps); |
| struct ieee80211_extender_wds_info *peer_wds = NULL; |
| struct qtn_wds_ext_event_data event_data; |
| unsigned long flags; |
| int i; |
| |
| spin_lock_irqsave(&vap->iv_extender_wds_lock, flags); |
| for (i = 0; i < IEEE80211_NODE_HASHSIZE; i++) { |
| LIST_FOREACH(peer_wds, &vap->iv_extender_wds_hash[i], peer_wds_hash) { |
| if (!peer_wds) |
| continue; |
| extender_event_data_prepare(ic, NULL, |
| &event_data, |
| WDS_EXT_CLEANUP_WDS_LINK, |
| peer_wds->peer_addr); |
| ieee80211_extender_send_event(pri_vap, &event_data, NULL); |
| LIST_REMOVE(peer_wds, peer_wds_hash); |
| FREE(peer_wds, M_DEVBUF); |
| } |
| } |
| spin_unlock_irqrestore(&vap->iv_extender_wds_lock, flags); |
| } |
| |
| static int extender_check_rssi_change(struct ieee80211com *ic, |
| struct ieee80211vap *vap, int rssi) |
| { |
| if (vap->iv_opmode == IEEE80211_M_STA) { |
| if (rssi <= ic->ic_extender_mbs_best_rssi) { |
| ic->ic_extender_rssi_continue = 0; |
| return 0; |
| } |
| } else { |
| if (rssi >= ic->ic_extender_mbs_best_rssi - ic->ic_extender_mbs_rssi_margin) { |
| ic->ic_extender_rssi_continue = 0; |
| return 0; |
| } |
| } |
| |
| if (ic->ic_extender_rssi_continue++ < QTN_EXTENDER_RSSI_MAX_COUNT) |
| return 0; |
| |
| ic->ic_extender_rssi_continue = 0; |
| |
| return 1; |
| } |
| |
| static int ieee80211_trigger_extender_event( |
| struct ieee80211vap *vap, |
| const struct ieee80211_qtn_ext_role *ie, |
| struct ieee80211_scanparams *p_scan, |
| struct ieee80211_frame *wh, |
| int rssi) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211_extender_wds_info *peer_wds = NULL; |
| struct qtn_wds_ext_event_data extender_event_data; |
| |
| peer_wds = ieee80211_extender_find_peer_wds_info(ic, wh->i_addr2); |
| if (peer_wds) { |
| if (memcmp(&peer_wds->extender_ie, ie, sizeof(*ie)) == 0) |
| return 0; |
| } else { |
| if (vap->iv_opmode == IEEE80211_M_STA && |
| extender_check_rssi_change(ic, vap, rssi) == 0) |
| return 0; |
| |
| peer_wds = ieee80211_extender_create_peer_wds_info(ic, wh->i_addr2); |
| if (!peer_wds) |
| return 0; |
| } |
| |
| memcpy(&peer_wds->extender_ie, ie, sizeof(*ie)); |
| extender_event_data_prepare(ic, p_scan, |
| &extender_event_data, |
| extender_role_to_event_cmd(ie->role), |
| wh->i_addr2); |
| |
| if ((p_scan->ssid != NULL) && (p_scan->ssid[1] > 0)) { |
| if (p_scan->ssid[1] < sizeof(extender_event_data.ssid)) { |
| memcpy(extender_event_data.ssid, p_scan->ssid + 2, p_scan->ssid[1]); |
| } else { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ELEMID, "%s : ssid is too long\n", __func__); |
| memcpy(extender_event_data.ssid, p_scan->ssid + 2, sizeof(extender_event_data.ssid) - 1); |
| } |
| } |
| |
| ieee80211_extender_send_event(vap, &extender_event_data, (uint8_t *)ie); |
| return 0; |
| } |
| |
| static void ieee80211_extender_update_rbs_macs(struct ieee80211com *ic, |
| const struct ieee80211_qtn_ext_role *ie_role, |
| const struct ieee80211_qtn_ext_bssid *ie_bssid, |
| const struct ieee80211_qtn_ext_state *ie_state) |
| { |
| struct ieee80211vap *primary_vap = TAILQ_FIRST(&ic->ic_vaps); |
| uint8_t ext_role = ie_role->role; |
| int update_beacon = 0; |
| |
| if (ic->ic_extender_role != IEEE80211_EXTENDER_ROLE_RBS) |
| return; |
| |
| if (!ie_bssid || (ie_bssid->len < (sizeof(struct ieee80211_qtn_ext_bssid) - 2))) |
| return; |
| |
| if (ext_role == IEEE80211_EXTENDER_ROLE_MBS) { |
| if (memcmp(ic->ic_extender_mbs_bssid, |
| ie_bssid->mbs_bssid, sizeof(ic->ic_extender_mbs_bssid))) { |
| memcpy(ic->ic_extender_mbs_bssid, |
| ie_bssid->mbs_bssid, sizeof(ic->ic_extender_mbs_bssid)); |
| update_beacon = 1; |
| } |
| |
| if ((ic->ic_extender_rbs_num != ie_bssid->rbs_num) || |
| memcmp(ic->ic_extender_rbs_bssid[0], |
| ie_bssid->rbs_bssid[0], sizeof(ic->ic_extender_rbs_bssid))) { |
| ic->ic_extender_rbs_num = ie_bssid->rbs_num; |
| memcpy(ic->ic_extender_rbs_bssid[0], |
| ie_bssid->rbs_bssid[0], sizeof(ic->ic_extender_rbs_bssid)); |
| update_beacon = 1; |
| } |
| |
| if (ie_state && !!(ie_state->state1 & QTN_EXT_MBS_OCAC) != ic->ic_extender_mbs_ocac) { |
| ic->ic_extender_mbs_ocac = !!(ie_state->state1 & QTN_EXT_MBS_OCAC); |
| update_beacon = 2; |
| } |
| } |
| |
| if (update_beacon == 1) |
| ic->ic_beacon_update(primary_vap); |
| else if (update_beacon == 2) |
| ieee80211_beacon_update_all(ic); |
| } |
| |
| static int |
| ieee80211_get_max_bandwidth(struct ieee80211com *ic, uint8_t channel) |
| { |
| int max_glob_bw; |
| int max_chan_bw; |
| |
| max_glob_bw = ieee80211_get_max_system_bw(ic); |
| max_chan_bw = ieee80211_get_max_channel_bw(ic, channel); |
| |
| return MIN(max_glob_bw, max_chan_bw); |
| } |
| |
| static int |
| ieee80211_parse_peer_bandwidth(u_int8_t *vhtop, u_int8_t *htinfo) |
| { |
| if (vhtop != NULL) { |
| struct ieee80211_ie_vhtop *vht_op = (struct ieee80211_ie_vhtop *)vhtop; |
| |
| /* |
| * Channel Width |
| */ |
| if (IEEE80211_VHTOP_GET_CHANWIDTH(vht_op)) |
| return BW_HT80; |
| } |
| |
| if (htinfo != NULL) { |
| struct ieee80211_ie_htinfo *ht_info = (struct ieee80211_ie_htinfo *)htinfo; |
| |
| /* |
| * Secondary Channel Offset |
| */ |
| if (IEEE80211_HTINFO_B1_EXT_CHOFFSET(ht_info)) |
| return BW_HT40; |
| } |
| |
| return BW_HT20; |
| } |
| |
| static int |
| ieee80211_parse_qtn_extender_ie( |
| struct ieee80211_node *ni, |
| const struct ieee80211_qtn_ext_role *ie, |
| const struct ieee80211_qtn_ext_bssid *ext_bssid_ie, |
| const struct ieee80211_qtn_ext_state *ie_state, |
| struct ieee80211_scanparams *p_scan, |
| struct ieee80211_frame *wh, |
| int rssi) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211vap *primary_vap; |
| struct ieee80211_extender_wds_info *peer_wds = NULL; |
| struct qtn_wds_ext_event_data event_data; |
| |
| uint32_t mbs_bw; |
| uint32_t rbs_bw; |
| |
| if (ie->len < (sizeof(*ie) - 2)) |
| return -1; |
| |
| p_scan->extender_role = ie->role; |
| |
| switch(vap->iv_opmode) { |
| case IEEE80211_M_STA: |
| if ((ic->ic_extender_role == IEEE80211_EXTENDER_ROLE_RBS) && |
| (ie->role == IEEE80211_EXTENDER_ROLE_MBS)) { |
| if ((ni->ni_associd == 0) || |
| (vap->iv_state != IEEE80211_S_RUN) || |
| !(ni->ni_flags & IEEE80211_NODE_AUTH) || |
| !IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid)) |
| return 0; |
| /* need to verify the essid in the frame is the same to our */ |
| if ((p_scan->ssid == NULL) || (p_scan->ssid[1] == 0)) { |
| return 0; |
| } else { |
| if (ieee80211_ssid_compare(vap, p_scan)) |
| return 0; |
| } |
| |
| mbs_bw = ieee80211_parse_peer_bandwidth(p_scan->vhtop, p_scan->htinfo); |
| rbs_bw = ieee80211_get_max_bandwidth(ic, p_scan->bchan); |
| |
| ic->ic_extender_rbs_bw = MIN(mbs_bw, rbs_bw); |
| ieee80211_trigger_extender_event(vap, ie, p_scan, wh, rssi); |
| ic->ic_extender_mbs_detected_jiffies = jiffies; |
| } |
| break; |
| case IEEE80211_M_HOSTAP: |
| if ((ic->ic_extender_role == IEEE80211_EXTENDER_ROLE_NONE)) |
| return 0; |
| |
| /* need to verify the essid in the frame is the same to our */ |
| if ((p_scan->ssid == NULL) || (p_scan->ssid[1] == 0)) { |
| return 0; |
| } else { |
| if (ieee80211_ssid_compare(vap, p_scan)) |
| return 0; |
| } |
| |
| if ((ic->ic_extender_role == IEEE80211_EXTENDER_ROLE_MBS && |
| ie->role == IEEE80211_EXTENDER_ROLE_RBS) || |
| (ic->ic_extender_role == IEEE80211_EXTENDER_ROLE_RBS && |
| ie->role == IEEE80211_EXTENDER_ROLE_MBS)) { |
| if ((ni->ni_node_type == IEEE80211_NODE_TYPE_STA) && |
| (ie->role == IEEE80211_EXTENDER_ROLE_RBS)) { |
| IEEE80211_EXTENDER_DPRINTF(vap, |
| IEEE80211_EXTENDER_MSG_WARN, |
| "QHop: peer %pM should disassociate first\n", |
| wh->i_addr2); |
| ieee80211_node_leave(ni); |
| } |
| /* ignore Beacon and Probe Response frames from other QHop networks */ |
| if (ext_bssid_ie != NULL) { |
| if (IEEE80211_COM_WDS_IS_MBS(ic)) { |
| /* ignore if we are NOT the announced MBS, or else we |
| * create WDS link for sender unexpectedly |
| */ |
| if (!IEEE80211_ADDR_EQ(ext_bssid_ie->mbs_bssid, |
| vap->iv_myaddr)) |
| break; |
| } else { |
| /* ignore if we already use another MBS, or else we |
| * update MBS BSSID unexpectedly |
| */ |
| if (!IEEE80211_ADDR_EQ(ext_bssid_ie->mbs_bssid, |
| ic->ic_extender_mbs_bssid)) { |
| /* make an exception for RBS boots up as AP */ |
| if (!IEEE80211_ADDR_NULL(ic->ic_extender_mbs_bssid)) |
| break; |
| } |
| } |
| } |
| ieee80211_trigger_extender_event(vap, ie, p_scan, wh, 0); |
| ic->ic_extender_mbs_detected_jiffies = jiffies; |
| |
| /* update extender bssid ie */ |
| if ((ic->ic_extender_role == IEEE80211_EXTENDER_ROLE_RBS) && |
| (ie->role == IEEE80211_EXTENDER_ROLE_MBS)) |
| ieee80211_extender_update_rbs_macs(ic, ie, ext_bssid_ie, ie_state); |
| } |
| break; |
| case IEEE80211_M_WDS: |
| if (ic->ic_extender_role != IEEE80211_EXTENDER_ROLE_RBS || |
| ie->role != IEEE80211_EXTENDER_ROLE_MBS || |
| !IEEE80211_ADDR_EQ(wh->i_addr2, vap->wds_mac)) { |
| return 0; |
| } |
| primary_vap = TAILQ_FIRST(&ic->ic_vaps); |
| /* need to verify the essid in the frame is the same to our */ |
| |
| if ((p_scan->ssid == NULL) || (p_scan->ssid[1] == 0)) { |
| return 0; |
| } else { |
| if (ieee80211_ssid_compare(primary_vap, p_scan)) |
| return 0; |
| } |
| |
| ic->ic_extender_mbs_detected_jiffies = jiffies; |
| peer_wds = ieee80211_extender_find_peer_wds_info(ic, wh->i_addr2); |
| if (!peer_wds) { |
| peer_wds = ieee80211_extender_create_peer_wds_info(ic, wh->i_addr2); |
| if (peer_wds) |
| memcpy(&peer_wds->extender_ie, ie, sizeof(*ie)); |
| } |
| |
| /* update extender bssid ie */ |
| if ((ic->ic_extender_role == IEEE80211_EXTENDER_ROLE_RBS) && |
| (ie->role == IEEE80211_EXTENDER_ROLE_MBS)) |
| ieee80211_extender_update_rbs_macs(ic, ie, ext_bssid_ie, ie_state); |
| |
| if (unlikely(ic->ic_bsschan != IEEE80211_CHAN_ANYC && |
| ic->ic_bsschan != ic->ic_curchan)) { |
| extender_event_data_prepare(ic, p_scan, |
| &event_data, |
| WDS_EXT_RBS_SET_CHANNEL, |
| wh->i_addr2); |
| ieee80211_extender_send_event(primary_vap, &event_data, NULL); |
| return 0; |
| } |
| |
| if (extender_check_rssi_change(ic, primary_vap, rssi)) { |
| IEEE80211_EXTENDER_DPRINTF(vap, IEEE80211_EXTENDER_MSG_WARN, |
| "QHop: MBS %pM rssi %d is out of BRR %u\n", |
| wh->i_addr2, rssi, ic->ic_extender_mbs_best_rssi); |
| extender_event_data_prepare(ic, NULL, |
| &event_data, |
| WDS_EXT_RBS_OUT_OF_BRR, |
| wh->i_addr2); |
| ieee80211_extender_send_event(primary_vap, &event_data, NULL); |
| ieee80211_extender_remove_peer_wds_info(ic, wh->i_addr2); |
| return 0; |
| } |
| break; |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| static int ieee80211_extender_process( |
| struct ieee80211_node *ni, |
| const struct ieee80211_qtn_ext_role *ie_role, |
| const struct ieee80211_qtn_ext_bssid *ie_bssid, |
| const struct ieee80211_qtn_ext_state *ie_state, |
| struct ieee80211_scanparams *p_scan, |
| struct ieee80211_frame *wh, |
| int rssi) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211vap *primary_vap = NULL; |
| struct ieee80211_extender_wds_info *wds_info = NULL; |
| struct qtn_wds_ext_event_data extender_event_data; |
| |
| p_scan->ext_bssid_ie = (uint8_t *)ie_bssid; |
| |
| if (ie_role) { |
| ieee80211_parse_qtn_extender_ie(ni, ie_role, ie_bssid, ie_state, p_scan, wh, rssi); |
| } else { |
| p_scan->extender_role = IEEE80211_EXTENDER_ROLE_NONE; |
| if ((vap->iv_opmode == IEEE80211_M_HOSTAP) || (vap->iv_opmode == IEEE80211_M_WDS)) { |
| primary_vap = TAILQ_FIRST(&ic->ic_vaps); |
| wds_info = ieee80211_extender_find_peer_wds_info(ic, wh->i_addr2); |
| if (wds_info) { |
| IEEE80211_EXTENDER_DPRINTF(vap, IEEE80211_EXTENDER_MSG_WARN, |
| "QHop: Extender IE of peer %pM is missing\n", |
| wh->i_addr2); |
| extender_event_data_prepare(ic, NULL, |
| &extender_event_data, |
| WDS_EXT_LINK_STATUS_UPDATE, |
| wh->i_addr2); |
| ieee80211_extender_send_event(primary_vap, &extender_event_data, NULL); |
| ieee80211_extender_remove_peer_wds_info(ic, wh->i_addr2); |
| } |
| } |
| } |
| |
| if (vap->iv_opmode == IEEE80211_M_STA) |
| ieee80211_extender_sta_update_info(ni, ie_role, ie_bssid); |
| |
| return 0; |
| } |
| |
| void ieee80211_channel_switch_post(struct ieee80211com *ic) |
| { |
| struct ieee80211vap *vap; |
| |
| ic->ic_prevchan = ic->ic_curchan; |
| ic->ic_curchan = ic->ic_csa_chan; |
| ic->ic_bsschan = ic->ic_csa_chan; |
| |
| if (ic->ic_flags_qtn & IEEE80211_QTN_MONITOR) |
| printk("switched to chan %u\n", ic->ic_csa_chan->ic_ieee); |
| |
| TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOTH, |
| "%s: switched to %d\n", |
| __FUNCTION__, ic->ic_csa_chan->ic_ieee); |
| |
| if (vap->iv_opmode == IEEE80211_M_HOSTAP) { |
| struct ieee80211_node *ni; |
| ni = ieee80211_find_node(&ic->ic_sta, vap->iv_myaddr); |
| if (ni == NULL) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_DOTH, |
| vap->iv_myaddr, "Node not found %d\n", 1); |
| return; |
| } |
| |
| ieee80211_node_set_chan(ic, ni); |
| ieee80211_free_node(ni); |
| |
| /* Remove the CSA IE from beacons */ |
| vap->iv_flags &= ~IEEE80211_F_CHANSWITCH; |
| ic->ic_flags &= ~IEEE80211_F_CHANSWITCH; |
| ic->ic_beacon_update(vap); |
| } |
| } |
| } |
| EXPORT_SYMBOL(ieee80211_channel_switch_post); |
| |
| static void |
| ieee80211_relay_csaie(struct ieee80211com *ic, |
| struct ieee80211_channel* new_chan, uint8_t csa_count) |
| { |
| uint32_t flags = IEEE80211_CSA_F_ACTION; |
| |
| if (csa_count) |
| flags |= IEEE80211_CSA_F_BEACON; |
| |
| ieee80211_enter_csa(ic, new_chan, NULL, IEEE80211_CSW_REASON_CSA, |
| csa_count, IEEE80211_CSA_MUST_STOP_TX, flags); |
| } |
| |
| #ifdef CONFIG_QHOP |
| static void ieee80211_dfs_trigger_channel_switch(unsigned long arg) |
| { |
| struct ieee80211com *ic = (struct ieee80211com *)arg; |
| |
| ieee80211_finish_csa((unsigned long)ic); |
| ic->ic_pm_reason = IEEE80211_PM_LEVEL_CSA_DFS_ACTION; |
| ieee80211_pm_queue_work_custom(ic, BOARD_PM_WLAN_IDLE_TIMEOUT); |
| } |
| |
| void |
| ieee80211_dfs_send_csa(struct ieee80211vap *vap, uint8_t new_chan) |
| { |
| uint32_t flags = IEEE80211_CSA_F_ACTION | IEEE80211_CSA_F_BEACON; |
| struct ieee80211_channel *c; |
| |
| c = ieee80211_doth_findchan(vap, new_chan); |
| if (c == NULL) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOTH, |
| "%s: channel %u lookup failed " "\n", __func__, new_chan); |
| return; |
| } |
| |
| ieee80211_enter_csa(vap->iv_ic, c, ieee80211_dfs_trigger_channel_switch, IEEE80211_CSW_REASON_DFS, |
| vap->iv_ic->ic_dfs_csa_cnt, IEEE80211_CSA_MUST_STOP_TX, flags); |
| } |
| EXPORT_SYMBOL(ieee80211_dfs_send_csa); |
| #endif |
| |
| static inline int |
| ieee80211_should_relay_csaie(struct ieee80211com *ic, struct ieee80211vap *vap) |
| { |
| if (ic->ic_opmode == IEEE80211_M_HOSTAP && (IEEE80211_COM_WDS_IS_RBS(ic) || |
| IEEE80211_VAP_WDS_IS_RBS(vap))) { |
| return true; |
| } |
| |
| if (ieee80211_is_repeater(ic)) |
| return true; |
| |
| return false; |
| } |
| |
| #if defined(PLATFORM_QFDR) |
| static DEFINE_TIMER(ieee80211_csa_otherband_timer, NULL, 0, 0); |
| |
| static void ieee80211_csa_otherband_notify(unsigned long data) |
| { |
| struct ieee80211vap *vap = (struct ieee80211vap *)data; |
| struct ieee80211com *ic = vap->iv_ic; |
| ic->ic_csa_count = 0; |
| /* disassociate STA from RootAP */ |
| ieee80211_new_state(vap, IEEE80211_S_INIT, 0); |
| } |
| #endif |
| |
| #define QTN_CSAIE_ERR_INVALID_IE (-1) |
| #define QTN_CSAIE_ERR_CHAN_NOT_SUPP (-2) |
| static int |
| ieee80211_parse_csaie(struct ieee80211_node *ni, u_int8_t *csa_frm, u_int8_t *csa_tsf_frm, |
| const struct ieee80211_frame *wh) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211_channel *c; |
| struct ieee80211_ie_csa *csa_ie = (struct ieee80211_ie_csa *)csa_frm; |
| struct ieee80211_ie_qtn_csa_tsf *csa_tsf_ie = (struct ieee80211_ie_qtn_csa_tsf *)csa_tsf_frm; |
| int subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; |
| #if defined(PLATFORM_QFDR) |
| int otherband = 0; |
| #endif |
| |
| if (!(ic->ic_opmode == IEEE80211_M_STA || |
| (ic->ic_opmode == IEEE80211_M_HOSTAP && |
| IEEE80211_VAP_WDS_IS_RBS(vap)))) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOTH, |
| "%s: incorrect operation mode - ic %d vap %d, " |
| "role %d, flags_ext 0x%x\n", __func__, |
| ic->ic_opmode, vap->iv_opmode, |
| ic->ic_extender_role, ic->ic_flags_ext); |
| return 0; |
| } |
| |
| if (!csa_ie) { |
| if (ic->ic_csa_count) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOTH, |
| "%s: channel switch is scheduled, but we got " |
| "Beacon without CSA IE!\n", __func__); |
| } |
| return 0; |
| } |
| |
| ic->ic_csa_frame[(subtype == IEEE80211_FC0_SUBTYPE_BEACON) |
| ? IEEE80211_CSA_FRM_BEACON : IEEE80211_CSA_FRM_ACTION]++; |
| |
| if (csa_ie->csa_len != 3) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, |
| wh, "channel switch", "invalid length %u", |
| csa_ie->csa_len); |
| return QTN_CSAIE_ERR_INVALID_IE; |
| } |
| |
| if (isclr(ic->ic_chan_avail, csa_ie->csa_chan)) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, |
| wh, "channel switch", "invalid channel %u", |
| csa_ie->csa_chan); |
| return QTN_CSAIE_ERR_CHAN_NOT_SUPP; |
| } |
| |
| if (isclr(ic->ic_chan_active, csa_ie->csa_chan)) { |
| #if defined(PLATFORM_QFDR) |
| /* channel may switched into other band */ |
| otherband = 1; |
| #else |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, |
| wh, "channel switch", "disabled channel %u", |
| csa_ie->csa_chan); |
| return QTN_CSAIE_ERR_CHAN_NOT_SUPP; |
| #endif |
| } |
| |
| c = ieee80211_doth_findchan(vap, csa_ie->csa_chan); |
| if (!c) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, |
| wh, "channel switch", |
| "channel %u lookup failed", csa_ie->csa_chan); |
| return QTN_CSAIE_ERR_CHAN_NOT_SUPP; |
| } |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOTH, |
| "%s: channel switch to %u in %u tbtt (mode %u) announced, trigger_frame=0x%x\n", |
| __func__, csa_ie->csa_chan, csa_ie->csa_count, |
| csa_ie->csa_mode, subtype); |
| |
| if (ic->ic_csa_count) { |
| /* CSA was received recently */ |
| if (c != ic->ic_csa_chan) { |
| /* XXX abuse? */ |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOTH, |
| "%s: channel switch channel " |
| "changed from %u to %u!\n", __func__, |
| ic->ic_csa_chan->ic_ieee, |
| csa_ie->csa_chan); |
| #if defined(PLATFORM_QFDR) |
| if (isclr(ic->ic_chan_active, ic->ic_csa_chan->ic_ieee)) { |
| /* channel was switching to other band */ |
| del_timer(&ieee80211_csa_otherband_timer); |
| ic->ic_csa_count = 0; |
| return 0; |
| } |
| #endif |
| ieee80211_doth_cancel_cs(vap); |
| return 0; |
| } |
| |
| if (csa_ie->csa_mode != ic->ic_csa_mode) { |
| /* Can be abused, but with no (to little) impact. */ |
| |
| /* CS mode change has no influence on our actions since |
| * we don't respect cs modes at all (yet). Complain and |
| * forget. */ |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOTH, |
| "%s: channel switch mode changed from " |
| "%u to %u!\n", __func__, |
| ic->ic_csa_mode, csa_ie->csa_mode); |
| } |
| |
| ic->ic_csa_count = csa_ie->csa_count; |
| if (ic->ic_csa_count == 0) { |
| /* keep ic_csa_count unzero to avoid 2nd csa trigger */ |
| ic->ic_csa_count = 1; |
| } |
| } else { |
| /* CSA wasn't received recently, so this is the first one in |
| * the sequence. */ |
| uint64_t tsf = 0; |
| |
| ic->ic_csa_mode = csa_ie->csa_mode; |
| ic->ic_csa_count = csa_ie->csa_count; |
| ic->ic_csa_chan = c; |
| |
| #if defined(PLATFORM_QFDR) |
| if (ieee80211_should_relay_csaie(ic, vap) && !otherband) |
| ieee80211_relay_csaie(ic, c, ic->ic_csa_count); |
| #else |
| if (ieee80211_should_relay_csaie(ic, vap)) |
| ieee80211_relay_csaie(ic, c, ic->ic_csa_count); |
| #endif |
| |
| if (ic->ic_csa_count == 0) { |
| /* keep ic_csa_count unzero to avoid 2nd csa trigger */ |
| ic->ic_csa_count = 1; |
| } |
| |
| if (ic->ic_opmode == IEEE80211_M_STA) { |
| if (csa_tsf_ie && csa_tsf_ie->id == IEEE80211_ELEMID_VENDOR && isqtnie((u_int8_t *)csa_tsf_ie)) { |
| tsf = ntohll(csa_tsf_ie->tsf); |
| } else { |
| ic->ic_get_tsf(&tsf); |
| tsf += IEEE80211_MS_TO_USEC(ic->ic_csa_count * ni->ni_intval); |
| } |
| if (ic->ic_flags_qtn & IEEE80211_QTN_MONITOR) |
| printk("%s: switching to chan=%u\n", __func__, |
| ic->ic_csa_chan->ic_ieee); |
| #if defined(PLATFORM_QFDR) |
| if (otherband) { |
| ieee80211_eventf(vap->iv_dev, |
| "%s[CSA-OTHER-BAND] FREQ=%u DELAY=%u MAC=%pM", |
| QEVT_COMMON_PREFIX, |
| ic->ic_csa_chan->ic_freq, |
| csa_ie->csa_count * ni->ni_intval, |
| wh->i_addr2); |
| |
| init_timer(&ieee80211_csa_otherband_timer); |
| ieee80211_csa_otherband_timer.function = ieee80211_csa_otherband_notify; |
| ieee80211_csa_otherband_timer.data = (unsigned long)vap; |
| ieee80211_csa_otherband_timer.expires = jiffies + |
| IEEE80211_MS_TO_JIFFIES(csa_ie->csa_count * ni->ni_intval); |
| add_timer(&ieee80211_csa_otherband_timer); |
| |
| return 0; |
| } |
| #endif |
| ieee80211_eventf(vap->iv_dev, "%s[CSA] switch to %u in %u TBTT, MAC: %pM", QEVT_COMMON_PREFIX, |
| ic->ic_csa_chan->ic_ieee, csa_ie->csa_count, wh->i_addr2); |
| ic->ic_set_channel_deferred(ic, tsf, 0); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| ieee80211_narrower_bw_supported(struct ieee80211_node *ni, u_int8_t *csa_frm, int cur_bw) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211_ie_csa *csa_ie = (struct ieee80211_ie_csa *)csa_frm; |
| |
| switch (cur_bw) { |
| case BW_HT80: |
| case BW_HT40: |
| if (isset(ic->ic_chan_active_20, csa_ie->csa_chan)) { |
| return 1; |
| } |
| break; |
| } |
| return 0; |
| } |
| static int |
| ieee80211_wider_bw_supported(struct ieee80211_node *ni, u_int8_t *csa_frm, int cur_bw) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211_ie_csa *csa_ie = (struct ieee80211_ie_csa *)csa_frm; |
| |
| if (ic->ic_max_system_bw <= cur_bw) |
| return 0; |
| |
| switch (cur_bw) { |
| case BW_HT20: |
| if (isset(ic->ic_chan_active_40, csa_ie->csa_chan)) { |
| return 1; |
| } |
| break; |
| case BW_HT40: |
| if (isset(ic->ic_chan_active_80, csa_ie->csa_chan)) { |
| return 1; |
| } |
| break; |
| } |
| return 0; |
| } |
| |
| /* XXX. Not the right place for such a definition */ |
| struct l2_update_frame { |
| struct ether_header eh; |
| u8 dsap; |
| u8 ssap; |
| u8 control; |
| u8 xid[3]; |
| } __packed; |
| |
| static void |
| ieee80211_deliver_l2uf(struct ieee80211_node *ni) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct net_device *dev = vap->iv_dev; |
| struct sk_buff *skb; |
| struct l2_update_frame *l2uf; |
| struct ether_header *eh; |
| |
| skb = dev_alloc_skb(sizeof(*l2uf)); |
| if (!skb) { |
| printk(KERN_INFO "ieee80211_deliver_l2uf: no buf available\n"); |
| return; |
| } |
| skb_put(skb, sizeof(*l2uf)); |
| l2uf = (struct l2_update_frame *)(skb->data); |
| eh = &l2uf->eh; |
| /* dst: Broadcast address */ |
| IEEE80211_ADDR_COPY(eh->ether_dhost, dev->broadcast); |
| /* src: associated STA */ |
| IEEE80211_ADDR_COPY(eh->ether_shost, ni->ni_macaddr); |
| eh->ether_type = htons(skb->len - sizeof(*eh)); |
| |
| l2uf->dsap = 0; |
| l2uf->ssap = 0; |
| l2uf->control = 0xf5; |
| l2uf->xid[0] = 0x81; |
| l2uf->xid[1] = 0x80; |
| l2uf->xid[2] = 0x00; |
| |
| ieee80211_skb_dev_set(dev, skb); |
| skb->protocol = eth_type_trans(skb, skb->dev); |
| skb_push(skb, ETH_HLEN); |
| skb_reset_mac_header(skb); |
| |
| ieee80211_deliver_data(ni, skb); |
| return; |
| } |
| |
| static __inline int |
| contbgscan(struct ieee80211vap *vap) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| |
| return ((ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN) && |
| time_after(jiffies, ic->ic_lastdata + vap->iv_bgscanidle)); |
| } |
| |
| static __inline int |
| startbgscan(struct ieee80211vap *vap) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| |
| if (ic->ic_scan_opchan_enable) { |
| return (!IEEE80211_IS_CHAN_DTURBO(ic->ic_curchan) && |
| time_after(jiffies, ic->ic_lastscan + vap->iv_bgscanintvl)); |
| } else { |
| return ((vap->iv_flags & IEEE80211_F_BGSCAN) && |
| !IEEE80211_IS_CHAN_DTURBO(ic->ic_curchan) && |
| time_after(jiffies, ic->ic_lastscan + vap->iv_bgscanintvl) && |
| time_after(jiffies, ic->ic_lastdata + vap->iv_bgscanidle)); |
| } |
| } |
| |
| /* |
| * Process beacon/probe response frames in: |
| * o AP mode, to check for non-HT APs on same channel |
| * o station mode when associated, to collect state updates such as 802.11g slot time |
| * o monitor/sniffer node, to handle CSA events |
| * o WDS mode, to set peer node capabilities and rates |
| * o adhoc mode, to discover neighbors |
| * o any mode, when scanning |
| */ |
| static int |
| ieee80211_beacon_should_discard(struct ieee80211_node *ni) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| |
| switch (vap->iv_opmode) { |
| case IEEE80211_M_HOSTAP: |
| return 0; |
| break; |
| case IEEE80211_M_STA: |
| if (ni->ni_associd) |
| return 0; |
| if (ic->ic_flags_qtn & IEEE80211_QTN_MONITOR) |
| return 0; |
| break; |
| case IEEE80211_M_WDS: |
| if (IEEE80211_ADDR_EQ(ni->ni_macaddr, vap->wds_mac)) |
| return 0; |
| break; |
| case IEEE80211_M_IBSS: |
| return 0; |
| break; |
| default: |
| break; |
| } |
| |
| if ((ic->ic_flags & IEEE80211_F_SCAN) || (ic->ic_flags_qtn & IEEE80211_QTN_BGSCAN)) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* |
| * FIXME |
| * Ignore changes in Primary Channel and Secondary channel offset to skip node update |
| * caused by channel switch. |
| */ |
| static int |
| ieee80211_wds_compare_htinfo(struct ieee80211_ie_htinfo *old, |
| struct ieee80211_ie_htinfo *new) |
| { |
| int ret; |
| |
| old->hi_ctrlchannel = new->hi_ctrlchannel; |
| |
| old->hi_byte1 &= ~IEEE80211_HTINFO_B1_SEC_CHAN_OFFSET; |
| old->hi_byte1 |= new->hi_byte1 & IEEE80211_HTINFO_B1_SEC_CHAN_OFFSET; |
| |
| ret = memcmp(old, new, sizeof(struct ieee80211_ie_htinfo)); |
| |
| return (ret != 0); |
| } |
| |
| /* |
| * FIXME |
| * Ignore changes in Channel Center Segment 0/1 to skip node update |
| * caused by channel switch. |
| */ |
| static int |
| ieee80211_wds_compare_vhtop(struct ieee80211_ie_vhtop *old, |
| struct ieee80211_ie_vhtop *new) |
| { |
| int ret; |
| |
| old->vhtop_info[1] = new->vhtop_info[1]; |
| old->vhtop_info[2] = new->vhtop_info[2]; |
| |
| ret = memcmp(old, new, sizeof(struct ieee80211_ie_vhtop)); |
| |
| return (ret != 0); |
| } |
| |
| static void |
| ieee80211_update_wds_peer_node(struct ieee80211_node *ni, |
| struct ieee80211_scanparams *scan) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| int ni_update_required = 0; |
| int htinfo_update_required = 0; |
| int vhtcap_update_required = 0; |
| int htcap_update_required = 0; |
| int vhtop_update_required = 0; |
| struct ieee80211_ie_htinfo lhtinfo; |
| |
| if (scan->qtn && ni->ni_qtn_assoc_ie == NULL) { |
| ieee80211_saveie(&ni->ni_qtn_assoc_ie, scan->qtn); |
| ni_update_required = 1; |
| } |
| |
| if (scan->htcap && ieee80211_parse_htcap(ni, scan->htcap)) { |
| htcap_update_required = 1; |
| ni_update_required = 1; |
| } |
| |
| memcpy(&lhtinfo, &ni->ni_ie_htinfo, sizeof(struct ieee80211_ie_htinfo)); |
| |
| if (scan->htinfo && ieee80211_parse_htinfo(ni, scan->htinfo)) { |
| htinfo_update_required = ieee80211_wds_compare_htinfo(&lhtinfo, |
| (struct ieee80211_ie_htinfo *)scan->htinfo); |
| ni_update_required = 1; |
| } |
| |
| /* 802.11ac */ |
| if (scan->vhtcap && IS_IEEE80211_VHT_ENABLED(ic)) { |
| ni->ni_flags |= IEEE80211_NODE_VHT; |
| if (ieee80211_check_and_parse_vhtcap(ni, scan->vhtcap)) { |
| vhtcap_update_required = 1; |
| ni_update_required = 1; |
| } |
| } |
| if (scan->vhtop && IS_IEEE80211_VHT_ENABLED(ic)) { |
| struct ieee80211_ie_vhtop lvhtop; |
| |
| memcpy(&lvhtop, &ni->ni_ie_vhtop, sizeof(struct ieee80211_ie_vhtop)); |
| |
| if (ieee80211_parse_vhtop(ni, scan->vhtop)) { |
| vhtop_update_required = ieee80211_wds_compare_vhtop(&lvhtop, |
| (struct ieee80211_ie_vhtop *)scan->vhtop); |
| ni_update_required = 1; |
| } |
| } |
| |
| if ((ic->ic_peer_rts_mode == IEEE80211_PEER_RTS_PMP) && |
| ((ic->ic_sta_assoc - ic->ic_nonqtn_sta) >= IEEE80211_MAX_STA_CCA_ENABLED)) { |
| |
| ic->ic_peer_rts = 1; |
| } |
| |
| if (ni_update_required) { |
| struct ieee80211_rateset old_ni_rates; /* negotiated rate set */ |
| struct ieee80211_ht_rateset old_ni_htrates; /* negotiated ht rate set */ |
| int ba_established = (ni->ni_ba_tx[IEEE80211_WDS_LINK_MAINTAIN_BA_TID].state == IEEE80211_BA_ESTABLISHED); |
| |
| old_ni_rates = ni->ni_rates; |
| old_ni_htrates = ni->ni_htrates; |
| |
| ieee80211_fix_rate(ni, IEEE80211_F_DOXSECT | IEEE80211_F_DODEL); |
| ieee80211_fix_ht_rate(ni, IEEE80211_F_DOXSECT | IEEE80211_F_DODEL); |
| |
| if (ba_established && |
| ieee80211_node_is_qtn(ni) && |
| (memcmp(&ni->ni_rates, &old_ni_rates, sizeof(old_ni_rates)) == 0) && |
| (memcmp(&ni->ni_htrates, &old_ni_htrates, sizeof(old_ni_htrates)) == 0) && |
| !vhtcap_update_required && !vhtop_update_required) { |
| return; |
| } |
| |
| if (ic->ic_newassoc != NULL) { |
| ic->ic_newassoc(ni, 0); |
| |
| /* update key peer WDS */ |
| if (vap->iv_wds_peer_key.wk_keylen != 0) { |
| ieee80211_key_update_begin(vap); |
| vap->iv_key_set(vap, &vap->iv_wds_peer_key, ni->ni_macaddr); |
| ieee80211_key_update_end(vap); |
| } |
| } |
| |
| ieee80211_node_ba_state_clear(ni); |
| } |
| } |
| |
| void |
| ieee80211_update_tbtt(struct ieee80211vap *vap, |
| struct ieee80211_node *ni) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| uint64_t cur_tsf; |
| |
| ic->ic_get_tsf(&cur_tsf); |
| ni->ni_tbtt = cur_tsf + IEEE80211_TU_TO_USEC(ni->ni_intval); |
| |
| if (vap->iv_dtim_count == 0) |
| ni->ni_dtim_tbtt = cur_tsf + |
| (uint32_t)vap->iv_dtim_period * IEEE80211_TU_TO_USEC(ni->ni_intval); |
| } |
| |
| static int |
| ieee80211_get_band_chan_step(int chan) |
| { |
| struct ieee80211_band_info *band; |
| int band_idx; |
| int temp_chan; |
| int chan_cnt; |
| int chan_step; |
| |
| for (band_idx = 0; band_idx < IEEE80211_BAND_IDX_MAX; band_idx++) { |
| band = ieee80211_get_band_info(band_idx); |
| if (band == NULL) |
| continue; |
| |
| temp_chan = band->band_first_chan; |
| chan_cnt = band->band_chan_cnt; |
| chan_step = band->band_chan_step; |
| |
| while (chan_cnt--) { |
| if (temp_chan == chan) |
| return chan_step; |
| temp_chan += chan_step; |
| } |
| } |
| |
| return -1; |
| } |
| |
| int |
| ieee80211_parse_supp_chan(struct ieee80211_node *ni, uint8_t *ie) |
| { |
| int chan_tuples; |
| uint8_t *chan; |
| uint8_t *chan_len; |
| uint8_t chan_step; |
| int i; |
| |
| if (!ni || !ie) |
| return -1; |
| |
| chan_tuples = ie[1] / 2; |
| for (i = 0; i < chan_tuples; i++) { |
| chan = ie + 2 + i * 2; |
| if (ieee80211_get_band_chan_step(*chan) < 0) { |
| IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ELEMID, |
| "%s: Invalid channel: %u\n", |
| __func__, *chan); |
| return -1; |
| } |
| } |
| |
| memset(ni->ni_supp_chans, 0, sizeof(ni->ni_supp_chans)); |
| ni->ni_chan_num = 0; |
| |
| chan_tuples = ie[1] / 2; |
| for (i = 0; i < chan_tuples; i++) { |
| chan = ie + 2 + i * 2; |
| chan_len = ie + 3 + i * 2; |
| chan_step = ieee80211_get_band_chan_step(*chan); |
| while ((*chan_len)--) { |
| setbit(ni->ni_supp_chans, *chan); |
| ni->ni_chan_num++; |
| *chan += chan_step; |
| } |
| } |
| |
| return 0; |
| } |
| static int ieee80211_parse_80211_v(struct ieee80211_node *ni, uint8_t *frm) |
| { |
| uint32_t extcap[IEEE8211_EXTCAP_LENGTH / 4] = {0}; |
| uint32_t temp_extcap = 0; |
| uint8_t len = 0; |
| uint8_t *ie = NULL; |
| uint8_t i = 0; |
| struct ieee80211vap *vap = ni->ni_vap; |
| |
| if (vap == NULL) |
| return IEEE80211_REASON_IE_INVALID; |
| |
| ie = frm; |
| len = ie[1]; |
| if ((len == 0) || (len > IEEE8211_EXTCAP_LENGTH)) |
| return IEEE80211_REASON_IE_INVALID; |
| |
| ie += 2; |
| for (i = 0; i < len; i++) { |
| temp_extcap = ie[i]; |
| /* 4 bytes compose an extcap value, lower byte is on lower 8-bit of extcap value */ |
| temp_extcap <<= (i % 4) * 8; |
| extcap[i / 4] |= temp_extcap; |
| } |
| |
| if (extcap[0] & IEEE80211_EXTCAP1_BSS_TRANSITION) { |
| ni->ni_ext_flags |= IEEE80211_NODE_BSS_TRANSITION; |
| } else { |
| ni->ni_ext_flags &= ~IEEE80211_NODE_BSS_TRANSITION; |
| } |
| return 0; |
| |
| } |
| |
| static int ieee80211_parse_extcap(struct ieee80211_node *ni, uint8_t *frm, uint8_t *bssid) |
| { |
| uint32_t extcap[IEEE8211_EXTCAP_LENGTH / 4] = {0}; |
| uint32_t temp_extcap = 0; |
| uint8_t len = 0; |
| uint8_t *ie = NULL; |
| uint8_t i = 0; |
| struct ieee80211vap *vap = ni->ni_vap; |
| |
| if (vap == NULL) |
| return IEEE80211_REASON_IE_INVALID; |
| |
| ie = frm; |
| len = ie[1]; |
| if ((len == 0) || (len > IEEE8211_EXTCAP_LENGTH)) |
| return IEEE80211_REASON_IE_INVALID; |
| |
| ie += 2; |
| |
| /* check BSS transition management capbility and set node info */ |
| if ((len >= 3) && ie[2] & IEEE80211_EXTCAP_BTM) |
| ni->ni_wnm_capability |= IEEE80211_NODE_WNM_BTM_CAPABLE; |
| |
| for (i = 0; i < len; i++) { |
| temp_extcap = (uint32_t)ie[i]; |
| /* 4 bytes compose an extcap value, lower byte is on lower 8-bit of extcap value */ |
| temp_extcap <<= (i % 4) * 8; |
| extcap[i / 4] |= temp_extcap; |
| } |
| |
| if ((vap->iv_opmode == IEEE80211_M_STA) && |
| (vap->iv_state == IEEE80211_S_RUN) && |
| (IEEE80211_ADDR_EQ(vap->iv_bss->ni_bssid, bssid))) { |
| if (extcap[1] & IEEE80211_EXTCAP2_TDLS_PROHIB) { |
| if ((vap->iv_flags_ext & IEEE80211_FEXT_AP_TDLS_PROHIB) == 0) { |
| if ((vap->iv_flags_ext & IEEE80211_FEXT_TDLS_PROHIB) == 0) { |
| /* teardown the link and clear timer */ |
| ieee80211_tdls_teardown_all_link(vap); |
| ieee80211_tdls_clear_disc_timer(vap); |
| ieee80211_tdls_clear_node_expire_timer(vap); |
| } |
| vap->iv_flags_ext |= IEEE80211_FEXT_AP_TDLS_PROHIB; |
| } |
| } else { |
| if ((vap->iv_flags_ext & IEEE80211_FEXT_AP_TDLS_PROHIB) != 0) { |
| vap->iv_flags_ext &= ~IEEE80211_FEXT_AP_TDLS_PROHIB; |
| if ((vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) == 0) { |
| ieee80211_tdls_start_disc_timer(vap); |
| ieee80211_tdls_start_node_expire_timer(vap); |
| } |
| } |
| } |
| |
| if (extcap[1] & IEEE80211_EXTCAP2_TDLS_CS_PROHIB) |
| vap->iv_flags_ext |= IEEE80211_FEXT_TDLS_CS_PROHIB; |
| else |
| vap->iv_flags_ext &= ~IEEE80211_FEXT_TDLS_CS_PROHIB; |
| } |
| |
| if ((vap->iv_opmode == IEEE80211_M_HOSTAP) && |
| IEEE80211_ADDR_EQ(ni->ni_macaddr, bssid)) { |
| if (extcap[1] & IEEE80211_EXTCAP2_OP_MODE_NOTI) |
| ni->ni_ext_flags |= IEEE80211_NODE_OP_MODE_NOTI; |
| else |
| ni->ni_ext_flags &= ~IEEE80211_NODE_OP_MODE_NOTI; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Parse the wireless header for a TDLS frame |
| */ |
| static void |
| ieee80211_parse_tdls_hdr(struct ieee80211vap *vap, |
| struct ieee80211_tdls_params *tdls, struct ieee80211_frame *wh) |
| { |
| struct ieee80211_frame_addr4 *wh4; |
| |
| switch (wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) { |
| case IEEE80211_FC1_DIR_NODS: |
| tdls->da = wh->i_addr1; |
| tdls->sa = wh->i_addr2; |
| break; |
| case IEEE80211_FC1_DIR_TODS: |
| tdls->sa = wh->i_addr2; |
| tdls->da = wh->i_addr3; |
| break; |
| case IEEE80211_FC1_DIR_FROMDS: |
| tdls->da = wh->i_addr1; |
| tdls->sa = wh->i_addr3; |
| break; |
| case IEEE80211_FC1_DIR_DSTODS: |
| wh4 = (struct ieee80211_frame_addr4 *)wh; |
| tdls->da = wh4->i_addr3; |
| tdls->sa = wh4->i_addr4; |
| break; |
| } |
| } |
| |
| /* |
| * Parse the TLVs in a TDLS action frame or public tdls action frame |
| * Returns 0 if successful, else 1. |
| */ |
| static int |
| ieee80211_parse_tdls_tlvs(struct ieee80211vap *vap, |
| struct ieee80211_tdls_params *tdls, uint8_t **frm_p, uint8_t *efrm, |
| uint8_t ia_action) |
| { |
| uint8_t *frm = *frm_p; |
| uint32_t size = (uint32_t)efrm - (uint32_t)frm; |
| char *elem_type = "none"; |
| |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, "TDLS %s: parse TLVs\n", __func__); |
| /* Parse TLVs */ |
| while (frm < efrm) { |
| if (size < frm[1]) |
| goto error; |
| switch (*frm) { |
| case IEEE80211_ELEMID_RATES: |
| elem_type = "rates"; |
| tdls->rates = frm; |
| break; |
| case IEEE80211_ELEMID_COUNTRY: |
| elem_type = "country"; |
| tdls->country = frm; |
| break; |
| case IEEE80211_ELEMID_XRATES: |
| elem_type = "xrates"; |
| tdls->xrates = frm; |
| break; |
| case IEEE80211_ELEMID_SUPPCHAN: |
| elem_type = "suppchan"; |
| tdls->supp_chan = frm; |
| break; |
| case IEEE80211_ELEMID_SEC_CHAN_OFF: |
| elem_type = "sec_chan_off"; |
| tdls->sec_chan_off = frm; |
| break; |
| case IEEE80211_ELEMID_RSN: |
| elem_type = "rsn"; |
| tdls->rsn = frm; |
| break; |
| case IEEE80211_ELEMID_EXTCAP: |
| elem_type = "ext cap"; |
| tdls->ext_cap = frm; |
| break; |
| case IEEE80211_ELEMID_EDCA: |
| elem_type = "edca"; |
| tdls->edca = frm; |
| break; |
| case IEEE80211_ELEMID_QOSCAP: |
| elem_type = "qoscap"; |
| tdls->qos_cap = frm; |
| break; |
| case IEEE80211_ELEMID_FTIE: |
| elem_type = "ftie"; |
| tdls->ftie = frm; |
| break; |
| case IEEE80211_ELEMID_TIMEOUT_INT: |
| elem_type = "timeout_int"; |
| tdls->tpk_timeout = frm; |
| break; |
| case IEEE80211_ELEMID_REG_CLASSES: |
| elem_type = "reg_classes"; |
| tdls->sup_reg_class = frm; |
| break; |
| case IEEE80211_ELEMID_HTCAP: |
| elem_type = "htcap"; |
| tdls->htcap = frm; |
| break; |
| case IEEE80211_ELEMID_HTINFO: |
| elem_type = "htinfo"; |
| tdls->htinfo = frm; |
| break; |
| case IEEE80211_ELEMID_VHTCAP: |
| elem_type = "vhtcap"; |
| tdls->vhtcap = frm; |
| break; |
| case IEEE80211_ELEMID_VHTOP: |
| elem_type = "vhtop"; |
| tdls->vhtop = frm; |
| break; |
| case IEEE80211_ELEMID_20_40_BSS_COEX: |
| elem_type = "20/40 bss coex"; |
| tdls->bss_2040_coex = frm; |
| break; |
| case IEEE80211_ELEMID_AID: |
| elem_type = "aid"; |
| if (size < sizeof(*tdls->aid)) |
| goto error; |
| tdls->aid = (struct ieee80211_ie_aid *)frm; |
| break; |
| case IEEE80211_ELEMID_TDLS_LINK_ID: |
| elem_type = "link id"; |
| if (size < sizeof(*tdls->link_id)) |
| goto error; |
| tdls->link_id = (struct ieee80211_tdls_link_id *)frm; |
| break; |
| case IEEE80211_ELEMID_TDLS_WKUP_SCHED: |
| elem_type = "wkup sched"; |
| if (size < sizeof(*tdls->wkup_sched)) |
| goto error; |
| tdls->wkup_sched = (struct ieee80211_tdls_wkup_sched *)frm; |
| break; |
| case IEEE80211_ELEMID_TDLS_CS_TIMING: |
| elem_type = "cs timing"; |
| if (size < sizeof(*tdls->cs_timing)) |
| goto error; |
| tdls->cs_timing = (struct ieee80211_tdls_cs_timing *)frm; |
| break; |
| case IEEE80211_ELEMID_TDLS_PTI_CTRL: |
| elem_type = "pti ctrl"; |
| if (size < sizeof(*tdls->pti_ctrl)) |
| goto error; |
| tdls->pti_ctrl = (struct ieee80211_tdls_pti_ctrl *)frm; |
| break; |
| case IEEE80211_ELEMID_TDLS_PU_BUF_STAT: |
| elem_type = "pu buf stat"; |
| if (size < sizeof(*tdls->pu_buf_stat)) |
| goto error; |
| tdls->pu_buf_stat = (struct ieee80211_tdls_pu_buf_stat *)frm; |
| break; |
| case IEEE80211_ELEMID_WBWCHANSWITCH: |
| elem_type = "wide_bw_cs"; |
| if (size < sizeof(*tdls->wide_bw_cs)) |
| goto error; |
| tdls->wide_bw_cs = (struct ieee80211_ie_wbchansw *)frm; |
| break; |
| case IEEE80211_ELEMID_VHTXMTPWRENVLP: |
| elem_type = "vht_tx_pw_envelope"; |
| if (size < sizeof(*tdls->vht_tx_pw_env)) |
| goto error; |
| tdls->vht_tx_pw_env = (struct ieee80211_ie_vtxpwren *)frm; |
| break; |
| case IEEE80211_ELEMID_VENDOR: |
| elem_type = "vendor"; |
| /* Unsupported vendor IEs are silently ignored*/ |
| if (isqtnie(frm)) { |
| tdls->qtn_info = frm; |
| } else if (is_qtn_oui_tdls_brmacs(frm)) { |
| tdls->qtn_brmacs = frm; |
| } else { |
| /* TDLS debugging */ |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, |
| "unhandled id %u, len %u", *frm, frm[1]); |
| vap->iv_stats.is_rx_elem_unknown++; |
| } |
| break; |
| default: |
| elem_type = "unknown"; |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, |
| "unhandled id %u, len %u", *frm, frm[1]); |
| vap->iv_stats.is_rx_elem_unknown++; |
| break; |
| } |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, "TDLS %s: got %s pos=%p " |
| "id=%u len=%u\n", __func__, elem_type, frm, *frm, frm[1]); |
| |
| frm += frm[1] + 2; |
| } |
| |
| if (frm > efrm) { |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, "TDLS %s: " |
| "frame len invalid frm=%p efrm=%p\n", __func__, frm, efrm); |
| vap->iv_stats.is_rx_elem_toobig++; |
| return 1; |
| } |
| |
| *frm_p = frm; |
| |
| return 0; |
| |
| error: |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, |
| "TDLS %s: ie (%s) too short", __func__, elem_type); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| |
| return 1; |
| } |
| |
| /* |
| * Process a public TDLS action frame |
| */ |
| static void |
| ieee80211_recv_action_public_tdls(struct ieee80211_node *ni, struct sk_buff *skb, |
| struct ieee80211_frame *wh, struct ieee80211_action *ia, int rssi) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_tdls_params tdls; |
| uint8_t *frm = (u_int8_t *)(ia + 1); |
| uint8_t *efrm = skb->data + skb->len; |
| uint32_t dump_len = sizeof(struct ieee80211_ht_qosframe) + LLC_SNAPFRAMELEN; |
| int subtype = IEEE80211_FC0_SUBTYPE_ACTION; /* for validation scripts */ |
| |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, "TDLS %s: " |
| "got TDLS type %u\n", __func__, ia->ia_action); |
| |
| if (unlikely(ieee80211_msg(vap, IEEE80211_MSG_TDLS) && ieee80211_tdls_msg(vap, IEEE80211_TDLS_MSG_DBG))) { |
| if (unlikely(skb->len < dump_len)) |
| dump_len = skb->len; |
| ieee80211_dump_pkt(vap->iv_ic, skb->data, dump_len, -1, rssi); |
| } |
| |
| if (vap->iv_opmode != IEEE80211_M_STA) { |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, "TDLS %s: " |
| "Ignoring public TDLS action frame - not STA\n", __func__); |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, ieee80211_tdls_action_name_get(ia->ia_action), |
| "%s: Ignoring public TDLS action frame - not STA\n", |
| ia->ia_action); |
| vap->iv_stats.is_rx_mgtdiscard++; |
| return; |
| } |
| if (ia->ia_action != IEEE80211_ACTION_PUB_TDLS_DISC_RESP) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, |
| wh, ieee80211_mgt_subtype_name[ |
| IEEE80211_FC0_SUBTYPE_ACTION >> IEEE80211_FC0_SUBTYPE_SHIFT], |
| "unsupported TDLS public action %u", ia->ia_action); |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, "TDLS %s: " |
| "unsupported TDLS public action\n", __func__); |
| vap->iv_stats.is_rx_badsubtype++; |
| return; |
| } |
| |
| memset(&tdls, 0, sizeof(tdls)); |
| |
| ieee80211_parse_tdls_hdr(vap, &tdls, wh); |
| |
| IEEE80211_VERIFY_LENGTH(efrm - frm, |
| sizeof(tdls.diag_token) + |
| sizeof(tdls.caps)); |
| tdls.diag_token = *frm; |
| frm += sizeof(tdls.diag_token); |
| tdls.caps = le16toh(*(__le16 *)frm); |
| frm += sizeof(tdls.caps); |
| |
| if (ieee80211_parse_tdls_tlvs(vap, &tdls, &frm, efrm, ia->ia_action)) { |
| return; |
| } |
| |
| ieee80211_tdls_recv_disc_resp(ni, skb, rssi, &tdls); |
| } |
| |
| void |
| ieee80211_find_ht_pri_sec_chan(struct ieee80211vap *vap, |
| const struct ieee80211_scan_entry *se, |
| uint8_t *pri_chan, uint8_t *sec_chan) |
| { |
| struct ieee80211_ie_htinfo *htinfo = |
| (struct ieee80211_ie_htinfo *)se->se_htinfo_ie; |
| uint8_t choff; |
| |
| if (!htinfo) { |
| *pri_chan = se->se_chan->ic_ieee; |
| *sec_chan = 0; |
| return; |
| } |
| |
| *pri_chan = IEEE80211_HTINFO_PRIMARY_CHANNEL(htinfo); |
| choff = IEEE80211_HTINFO_B1_EXT_CHOFFSET(htinfo); |
| if (choff == IEEE80211_HTINFO_EXTOFFSET_ABOVE) |
| *sec_chan = *pri_chan + IEEE80211_CHAN_SEC_SHIFT; |
| else if (choff == IEEE80211_HTINFO_EXTOFFSET_BELOW) |
| *sec_chan = *pri_chan - IEEE80211_CHAN_SEC_SHIFT; |
| else if (choff == IEEE80211_HTINFO_EXTOFFSET_NA) |
| *sec_chan = 0; |
| else |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, |
| "%s: wrong channel offset %u in htinfo IE\n", |
| __func__, choff); |
| |
| if (*pri_chan != se->se_chan->ic_ieee) |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, |
| "%s: error - scan channel %u is different" |
| " with primary channel in htinfo\n", __func__, |
| se->se_chan->ic_ieee, pri_chan); |
| } |
| |
| uint8_t |
| ieee80211_find_ht_center_chan(struct ieee80211vap *vap, |
| const struct ieee80211_scan_entry *se) |
| { |
| uint8_t pri_chan; |
| uint8_t sec_chan; |
| |
| ieee80211_find_ht_pri_sec_chan(vap, se, &pri_chan, &sec_chan); |
| |
| if (sec_chan) |
| return (pri_chan + sec_chan) / 2; |
| else |
| return pri_chan; |
| } |
| |
| int |
| ieee80211_20_40_operation_permitted(struct ieee80211com *ic, |
| struct ieee80211_channel *chan, uint8_t se_pri_chan, uint8_t se_sec_chan) |
| { |
| uint16_t affected_start; |
| uint16_t affected_end; |
| uint8_t pri_chan; |
| uint8_t sec_chan; |
| uint16_t pri_freq; |
| uint16_t sec_freq; |
| uint16_t se_pri_freq; |
| uint16_t se_sec_freq; |
| |
| pri_chan = chan->ic_ieee; |
| pri_freq = chan->ic_freq; |
| if (chan->ic_flags & IEEE80211_CHAN_HT40U) { |
| sec_chan = pri_chan + IEEE80211_SEC_CHAN_OFFSET; |
| sec_freq = pri_freq + IEEE80211_SEC_CHAN_OFFSET * IEEE80211_CHAN_SPACE; |
| } else if (chan->ic_flags & IEEE80211_CHAN_HT40D) { |
| sec_chan = pri_chan - IEEE80211_SEC_CHAN_OFFSET; |
| sec_freq = pri_freq - IEEE80211_SEC_CHAN_OFFSET * IEEE80211_CHAN_SPACE; |
| } else { |
| return 0; |
| } |
| |
| /* Finding the frquency range */ |
| affected_start = ((pri_freq + sec_freq) >> 1) - IEEE80211_BW_RANGE; |
| affected_end = ((pri_freq + sec_freq) >> 1) + IEEE80211_BW_RANGE; |
| |
| se_pri_freq = ieee80211_ieee2mhz(se_pri_chan, 0); |
| se_sec_freq = (se_sec_chan != 0) ? ieee80211_ieee2mhz(se_sec_chan, 0) : 0; |
| if (((se_pri_freq > affected_start) && (se_pri_freq < affected_end)) || |
| ((se_sec_freq > affected_start) && (se_sec_freq < affected_end))) { |
| if ((pri_chan == se_pri_chan) && (se_sec_chan == 0)) |
| /* |
| * The scanned 20M bandwidth AP shares same channel |
| * with the primary channel of current AP |
| */ |
| return 1; |
| else if ((pri_chan == se_pri_chan) && (sec_chan == se_sec_chan)) |
| /* |
| * The scanned 40M bandwidth AP shares same primary channel |
| * and secondary channel with current AP |
| */ |
| return 1; |
| else |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| ieee80211_is_40_allowed(struct ieee80211vap *vap, int channel) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| uint16_t ch_freq; |
| uint16_t affected_start; |
| uint16_t affected_end; |
| uint16_t pri_freq; |
| uint16_t sec_freq; |
| |
| ch_freq = ieee80211_ieee2mhz(channel, 0); |
| pri_freq = ic->ic_curchan->ic_freq; |
| |
| if (ic->ic_curchan->ic_flags & IEEE80211_CHAN_HT40U) |
| sec_freq = pri_freq + 20; |
| else if (ic->ic_curchan->ic_flags & IEEE80211_CHAN_HT40D) |
| sec_freq = pri_freq - 20; |
| else { |
| return 0; |
| } |
| |
| /* Finding the frquency range */ |
| affected_start = ((pri_freq + sec_freq) >> 1) - IEEE80211_BW_RANGE; |
| affected_end = ((pri_freq + sec_freq) >> 1) + IEEE80211_BW_RANGE; |
| |
| if ((ch_freq < affected_start || ch_freq > affected_end)) |
| return 1; |
| |
| return 0; |
| } |
| |
| int |
| ieee80211_check_40_bw_allowed(struct ieee80211vap *vap) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211_scan_state *ss = ic->ic_scan; |
| struct ap_scan_entry *se; |
| struct ap_state *as = ss->ss_priv; |
| uint8_t se_pri_chan = 0; |
| uint8_t se_sec_chan = 0; |
| int change_bw = 0; |
| int i; |
| |
| if (ic->ic_opmode != IEEE80211_M_HOSTAP) { |
| return -EINVAL; |
| } |
| for (i = 0; i < IEEE80211_CHAN_MAX; i++) { |
| TAILQ_FOREACH(se, &as->as_scan_list[i].asl_head, ase_list) { |
| ieee80211_find_ht_pri_sec_chan(vap, &se->base, |
| &se_pri_chan, &se_sec_chan); |
| if (!ieee80211_20_40_operation_permitted(ic, |
| ic->ic_curchan, se_pri_chan, se_sec_chan)) { |
| change_bw = 1; |
| break; |
| } |
| } |
| } |
| if (change_bw && IEEE80211_IS_11NG_40(ic) && (ic->ic_20_40_coex_enable)) { |
| ieee80211_change_bw(vap, BW_HT20, 0); |
| ic->ic_coex_stats_update(ic, WLAN_COEX_STATS_BW_SCAN); |
| } |
| ic->ic_obss_scan_count = 1; |
| return 0; |
| } |
| EXPORT_SYMBOL(ieee80211_check_40_bw_allowed); |
| |
| static int |
| is_ieee80211_obss_grant(struct ieee80211com *ic, struct ieee80211_node *ni) |
| { |
| struct ieee80211_node *ni_tmp; |
| struct ieee80211_node_table *nt = &ic->ic_sta; |
| uint8_t retval = 0; |
| |
| IEEE80211_NODE_LOCK_BH(nt); |
| TAILQ_FOREACH(ni_tmp, &nt->nt_node, ni_list) { |
| if (ni_tmp->ni_vap->iv_opmode != IEEE80211_M_HOSTAP) |
| continue; |
| if (ni == ni_tmp) |
| continue; |
| if (ni_tmp->ni_associd == 0) |
| continue; |
| if (ni_tmp->ni_obss_scan & IEEE80211_NODE_OBSS_RUNNING) |
| retval = WLAN_20_40_BSS_COEX_OBSS_EXEMPT_GRNT; |
| } |
| IEEE80211_NODE_UNLOCK_BH(nt); |
| |
| return retval; |
| } |
| |
| static void |
| ieee80211_recv_action_public_coex(struct ieee80211_node *ni, struct sk_buff *skb, |
| struct ieee80211_frame *wh, struct ieee80211_action *ia, int rssi) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| uint8_t *frm = (u_int8_t *)(ia + 1); |
| uint8_t *efrm = skb->data + skb->len; |
| struct ieee80211_20_40_coex_param *coex_ie; |
| struct ieee80211_20_40_in_ch_rep *ch_rep_ie; |
| int change_bw = 0; |
| int i; |
| |
| if ((efrm - (u_int8_t *)ia) < sizeof(struct ieee80211_20_40_coex_param)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "20/40 coexistence action frame header too small"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| coex_ie = (struct ieee80211_20_40_coex_param *)frm; |
| if (coex_ie->param_id != IEEE80211_ELEMID_20_40_BSS_COEX) { |
| vap->iv_stats.is_rx_elem_unknown++; |
| return; |
| } |
| ni->ni_coex = coex_ie->coex_param; |
| frm = (u_int8_t *)(coex_ie + 1); |
| |
| |
| if (ic->ic_opmode == IEEE80211_M_STA) { |
| if (coex_ie->coex_param & WLAN_20_40_BSS_COEX_INFO_REQ) { |
| u_int8_t coex = vap->iv_coex; |
| struct ieee80211_action_data action_data; |
| action_data.cat = IEEE80211_ACTION_CAT_PUBLIC; |
| action_data.action = IEEE80211_ACTION_PUB_20_40_COEX; |
| action_data.params = &coex; |
| |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_ACTION, (int)&action_data); |
| } |
| if (coex_ie->coex_param & WLAN_20_40_BSS_COEX_OBSS_EXEMPT_GRNT) |
| del_timer_sync(&ic->ic_obss_timer); |
| return; |
| } |
| |
| /* For element id and length */ |
| if (efrm - frm > 3) { |
| if (*frm == IEEE80211_ELEMID_20_40_IT_CH_REP) { |
| ch_rep_ie = (struct ieee80211_20_40_in_ch_rep *)frm; |
| for (i = 0;i < ch_rep_ie->param_len - 1;i++) { |
| if (!ieee80211_is_40_allowed(vap, ch_rep_ie->chan[i])) { |
| change_bw = 1; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (coex_ie->coex_param & |
| (WLAN_20_40_BSS_COEX_40MHZ_INTOL | WLAN_20_40_BSS_COEX_20MHZ_WIDTH_REQ)) { |
| change_bw = 1; |
| } |
| |
| if (change_bw && IEEE80211_IS_11NG_40(ic) && (ic->ic_20_40_coex_enable)) { |
| ieee80211_change_bw(vap, BW_HT20, 0); |
| ic->ic_coex_stats_update(ic, WLAN_COEX_STATS_BW_ACTION); |
| } |
| |
| if (coex_ie->coex_param & WLAN_20_40_BSS_COEX_OBSS_EXEMPT_REQ) { |
| struct ieee80211_action_data action_data; |
| uint8_t coex_value = 0; |
| action_data.cat = IEEE80211_ACTION_CAT_PUBLIC; |
| action_data.action = IEEE80211_ACTION_PUB_20_40_COEX; |
| |
| coex_value |= is_ieee80211_obss_grant(ic, ni); |
| action_data.params = &coex_value; |
| |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_ACTION, (int)&action_data); |
| } |
| } |
| |
| /* |
| * Process a public action frame |
| */ |
| static void |
| ieee80211_recv_action_public(struct ieee80211_node *ni, struct sk_buff *skb, |
| struct ieee80211_frame *wh, struct ieee80211_action *ia, int rssi) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| |
| switch (ia->ia_action) { |
| case IEEE80211_ACTION_PUB_20_40_COEX: |
| ieee80211_recv_action_public_coex(ni, skb, wh, ia, rssi); |
| break; |
| case IEEE80211_ACTION_PUB_TDLS_DISC_RESP: |
| ieee80211_recv_action_public_tdls(ni, skb, wh, ia, rssi); |
| break; |
| default: |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, "TDLS %s: " |
| "Received unsupported public action frame type %u\n", |
| __func__, ia->ia_action); |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s: Received unsupported public action frame type %u\n", |
| ia->ia_action); |
| vap->iv_stats.is_rx_mgtdiscard++; |
| break; |
| } |
| } |
| |
| /* |
| * Process a TDLS Action Frame. |
| * Note: these are management frames encapsulated in data frames |
| */ |
| static void |
| ieee80211_recv_action_tdls(struct ieee80211_node *ni, struct sk_buff *skb, |
| struct ieee80211_action *ia, int ieee80211_header, int rssi) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_tdls_params tdls; |
| uint8_t *frm = (uint8_t *)(ia + 1); |
| uint8_t *efrm = skb->data + skb->len; |
| struct ether_header *eh; |
| struct ieee80211_frame *wh; |
| |
| if (ia->ia_category != IEEE80211_ACTION_CAT_TDLS) { |
| vap->iv_stats.is_rx_mgtdiscard++; |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, "TDLS %s: " |
| "invalid category %d!\n", __func__, ia->ia_category); |
| return; |
| } |
| |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, "TDLS %s: " |
| "got TDLS type %u\n", __func__, ia->ia_action); |
| |
| memset(&tdls, 0, sizeof(tdls)); |
| |
| if (ieee80211_header) { |
| wh = (struct ieee80211_frame *)skb->data; |
| ieee80211_parse_tdls_hdr(vap, &tdls, wh); |
| } else { |
| eh = (struct ether_header *)skb->data; |
| tdls.da = eh->ether_dhost; |
| tdls.sa = eh->ether_shost; |
| } |
| |
| tdls.act = ia->ia_action; |
| |
| /* Parse fixed length fields */ |
| switch (ia->ia_action) { |
| case IEEE80211_ACTION_TDLS_SETUP_REQ: |
| IEEE80211_VERIFY_TDLS_LENGTH(efrm - frm, |
| sizeof(tdls.diag_token) + |
| sizeof(tdls.caps)); |
| tdls.diag_token = *frm; |
| frm += sizeof(tdls.diag_token); |
| tdls.caps = le16toh(*(__le16 *)frm); |
| frm += sizeof(tdls.caps); |
| break; |
| case IEEE80211_ACTION_TDLS_SETUP_RESP: |
| IEEE80211_VERIFY_TDLS_LENGTH(efrm - frm, |
| sizeof(tdls.status) + |
| sizeof(tdls.diag_token)); |
| tdls.status = le16toh(*(__le16 *)frm); |
| frm += sizeof(tdls.status); |
| tdls.diag_token = *frm; |
| frm += sizeof(tdls.diag_token); |
| if (tdls.status == IEEE80211_STATUS_SUCCESS) { |
| tdls.caps = le16toh(*(__le16 *)frm); |
| frm += sizeof(tdls.caps); |
| } |
| break; |
| case IEEE80211_ACTION_TDLS_SETUP_CONFIRM: |
| IEEE80211_VERIFY_TDLS_LENGTH(efrm - frm, |
| sizeof(tdls.status) + |
| sizeof(tdls.diag_token)); |
| tdls.status = le16toh(*(__le16 *)frm); |
| frm += sizeof(tdls.status); |
| tdls.diag_token = *frm; |
| frm += sizeof(tdls.diag_token); |
| break; |
| case IEEE80211_ACTION_TDLS_TEARDOWN: |
| IEEE80211_VERIFY_TDLS_LENGTH(efrm - frm, |
| sizeof(tdls.reason)); |
| tdls.reason = le16toh(*(__le16 *)frm); |
| frm += sizeof(tdls.reason); |
| break; |
| case IEEE80211_ACTION_TDLS_PTI: |
| IEEE80211_VERIFY_TDLS_LENGTH(efrm - frm, |
| sizeof(tdls.diag_token)); |
| tdls.diag_token = *frm; |
| frm += sizeof(tdls.diag_token); |
| break; |
| case IEEE80211_ACTION_TDLS_CS_REQ: |
| IEEE80211_VERIFY_TDLS_LENGTH(efrm - frm, |
| sizeof(tdls.target_chan) + |
| sizeof(tdls.reg_class)); |
| tdls.target_chan = *frm; |
| frm += sizeof(tdls.target_chan); |
| tdls.reg_class = *frm; |
| frm += sizeof(tdls.reg_class); |
| break; |
| case IEEE80211_ACTION_TDLS_CS_RESP: |
| IEEE80211_VERIFY_TDLS_LENGTH(efrm - frm, |
| sizeof(tdls.status)); |
| tdls.status = le16toh(*(__le16 *)frm); |
| frm += sizeof(tdls.status); |
| break; |
| case IEEE80211_ACTION_TDLS_PEER_PSM_REQ: |
| IEEE80211_VERIFY_TDLS_LENGTH(efrm - frm, |
| sizeof(tdls.diag_token)); |
| tdls.diag_token = *frm; |
| frm += sizeof(tdls.diag_token); |
| break; |
| case IEEE80211_ACTION_TDLS_PEER_PSM_RESP: |
| IEEE80211_VERIFY_TDLS_LENGTH(efrm - frm, |
| sizeof(tdls.diag_token) + |
| sizeof(tdls.status)); |
| tdls.diag_token = *frm; |
| frm += sizeof(tdls.diag_token); |
| tdls.status = le16toh(*(__le16 *)frm); |
| frm += sizeof(tdls.status); |
| break; |
| case IEEE80211_ACTION_TDLS_PEER_TRAF_RESP: |
| IEEE80211_VERIFY_TDLS_LENGTH(efrm - frm, |
| sizeof(tdls.diag_token)); |
| tdls.diag_token = *frm; |
| frm += sizeof(tdls.diag_token); |
| break; |
| case IEEE80211_ACTION_TDLS_DISC_REQ: |
| IEEE80211_VERIFY_TDLS_LENGTH(efrm - frm, |
| sizeof(tdls.diag_token)); |
| tdls.diag_token = *frm; |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, "TDLS %s: " |
| "disc req - diag_token=%u\n", __func__, tdls.diag_token); |
| frm += sizeof(tdls.diag_token); |
| break; |
| default: |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, "TDLS %s: " |
| "unsupported TDLS action %u\n", __func__, ia->ia_action); |
| vap->iv_stats.is_rx_badsubtype++; |
| return; |
| } |
| |
| if (ieee80211_parse_tdls_tlvs(vap, &tdls, &frm, efrm, ia->ia_action)) { |
| return; |
| } |
| |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, |
| "TDLS %s: process frame %u\n", __func__, ia->ia_action); |
| /* Process the frame */ |
| switch (ia->ia_action) { |
| case IEEE80211_ACTION_TDLS_SETUP_REQ: |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, |
| "TDLS %s: process setup_req\n", __func__); |
| ieee80211_tdls_recv_setup_req(ni, skb, rssi, &tdls); |
| break; |
| case IEEE80211_ACTION_TDLS_SETUP_RESP: |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, |
| "TDLS %s: process setup_resp\n", __func__); |
| ieee80211_tdls_recv_setup_resp(ni, skb, rssi, &tdls); |
| break; |
| case IEEE80211_ACTION_TDLS_SETUP_CONFIRM: |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, |
| "TDLS %s: process setup_confirm\n", __func__); |
| ieee80211_tdls_recv_setup_confirm(ni, skb, rssi, &tdls); |
| break; |
| case IEEE80211_ACTION_TDLS_TEARDOWN: |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, |
| "TDLS %s: process teardown\n", __func__); |
| ieee80211_tdls_recv_teardown(ni, skb, rssi, &tdls); |
| break; |
| case IEEE80211_ACTION_TDLS_PTI: |
| break; |
| case IEEE80211_ACTION_TDLS_CS_REQ: |
| ieee80211_tdls_recv_chan_switch_req(ni, skb, rssi, &tdls); |
| break; |
| case IEEE80211_ACTION_TDLS_CS_RESP: |
| ieee80211_tdls_recv_chan_switch_resp(ni, skb, rssi, &tdls); |
| break; |
| case IEEE80211_ACTION_TDLS_PEER_PSM_REQ: |
| break; |
| case IEEE80211_ACTION_TDLS_PEER_PSM_RESP: |
| break; |
| case IEEE80211_ACTION_TDLS_PEER_TRAF_RESP: |
| break; |
| case IEEE80211_ACTION_TDLS_DISC_REQ: |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, |
| "TDLS %s: process disc_req\n", __func__); |
| ieee80211_tdls_recv_disc_req(ni, skb, rssi, &tdls); |
| break; |
| default: |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, |
| "TDLS %s: unsupported TDLS action %u\n", __func__, ia->ia_action); |
| vap->iv_stats.is_rx_badsubtype++; |
| return; |
| } |
| |
| vap->iv_stats.is_rx_tdls++; |
| IEEE80211_NODE_STAT(ni, rx_tdls_action); |
| } |
| |
| void ieee80211_recv_meas_basic_report(struct ieee80211_node *ni, |
| struct ieee80211_ie_measrep_basic *meas_rep_basic) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| |
| if (meas_rep_basic->basic_report & IEEE80211_MEASURE_BASIC_REPORT_RADAR) { |
| if (meas_rep_basic->chan_num == ieee80211_chan2ieee(ic, ic->ic_bsschan)) { |
| ic->ic_radar_detected(ic, 0); |
| } |
| } |
| } |
| static void |
| ieee80211_input_qtnie_common(struct ieee80211_node *ni, struct ieee80211_ie_qtn *qtnie) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| |
| ni->ni_vsp_version = IEEE80211_QTN_VSP_V_NONE; |
| |
| if (IEEE80211_QTN_IE_GE_V3(qtnie) && (ic->ic_curmode >= IEEE80211_MODE_11NA)) { |
| ni->ni_implicit_ba = qtnie->qtn_ie_implicit_ba_tid_h; |
| ni->ni_implicit_ba_size = qtnie->qtn_ie_implicit_ba_size; |
| ni->ni_implicit_ba_size = ni->ni_implicit_ba_size << IEEE80211_QTN_IE_BA_SIZE_SH; |
| } |
| |
| if (IEEE80211_QTN_IE_GE_V4(qtnie)) { |
| ni->ni_vsp_version = qtnie->qtn_ie_vsp_version; |
| } |
| |
| if (IEEE80211_QTN_IE_GE_V5(qtnie)) { |
| ni->ni_ver_sw = ntohl(get_unaligned(&qtnie->qtn_ie_ver_sw)); |
| ni->ni_ver_hw = ntohs(get_unaligned(&qtnie->qtn_ie_ver_hw)); |
| ni->ni_ver_platform_id = ntohs(get_unaligned(&qtnie->qtn_ie_ver_platform_id)); |
| ni->ni_ver_timestamp = ntohl(get_unaligned(&qtnie->qtn_ie_ver_timestamp)); |
| ni->ni_ver_flags = ntohl(get_unaligned(&qtnie->qtn_ie_ver_flags)); |
| } else { |
| ni->ni_ver_sw = 0; |
| ni->ni_ver_hw = 0; |
| ni->ni_ver_platform_id = 0; |
| ni->ni_ver_timestamp = 0; |
| ni->ni_ver_flags = 0; |
| } |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "[%pM] QTN IE flags 0x%x ba %u sw " DBGFMT_BYTEFLD4_P |
| " hw 0x%x plat %u ts %u ver_flags 0x%08x\n", |
| ni->ni_macaddr, |
| qtnie->qtn_ie_my_flags, |
| ni->ni_implicit_ba_size, |
| DBGFMT_BYTEFLD4_V(ni->ni_ver_sw), |
| ni->ni_ver_hw, |
| ni->ni_ver_platform_id, |
| ni->ni_ver_timestamp, |
| ni->ni_ver_flags); |
| } |
| |
| static void |
| ieee80211_input_assoc_req_qtnie(struct ieee80211_node *ni, struct ieee80211vap *vap, |
| struct ieee80211_ie_qtn *qtnie) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| uint32_t ver = 0; |
| |
| if (qtnie == NULL) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "%s: No QTN IE in assoc req\n", __func__); |
| /* Flush any state from a previous association */ |
| FREE(ni->ni_qtn_assoc_ie, M_DEVBUF); |
| ni->ni_qtn_assoc_ie = NULL; |
| return; |
| } |
| |
| if (vap->iv_debug & IEEE80211_MSG_ASSOC) { |
| if (IEEE80211_QTN_IE_GE_V5(qtnie)) |
| ver = 5; |
| else if (IEEE80211_QTN_IE_GE_V4(qtnie)) |
| ver = 4; |
| else if (IEEE80211_QTN_IE_GE_V3(qtnie)) |
| ver = 3; |
| else if (IEEE80211_QTN_IE_GE_V2(qtnie)) |
| ver = 2; |
| else |
| ver = 1; |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "%s: Received QTN IE v%u in assoc req, flags=%02x %02x\n", |
| __func__, ver, qtnie->qtn_ie_flags, |
| IEEE80211_QTN_TYPE_ENVY_LEGACY(qtnie) ? 0x0 : qtnie->qtn_ie_my_flags); |
| } |
| |
| ieee80211_saveie(&ni->ni_qtn_assoc_ie, (u_int8_t *)qtnie); |
| |
| /* |
| * If the station requested bridge mode but it is not advertised, |
| * restart. This could happen if the client is using a stale |
| * beacon. |
| */ |
| if ((qtnie->qtn_ie_flags & IEEE80211_QTN_BRIDGEMODE) && |
| !(vap->iv_flags_ext & IEEE80211_FEXT_WDS)) { |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "%s: Bridge mode mismatch - restarting, flags=%02x\n", __func__, |
| qtnie->qtn_ie_flags); |
| ieee80211_node_leave(ni); |
| vap->iv_stats.is_rx_assoc_capmismatch++; |
| return; |
| } |
| |
| ni->ni_implicit_ba = 0; |
| |
| /* Implicit BA flags for the STA */ |
| if (IEEE80211_QTN_IE_GE_V2(qtnie) && (ic->ic_curmode >= IEEE80211_MODE_11NA)) { |
| ni->ni_implicit_ba_valid = 1; |
| ni->ni_implicit_ba = qtnie->qtn_ie_implicit_ba_tid; |
| } |
| ni->ni_lncb_4addr = 0; |
| |
| if (IEEE80211_QTN_TYPE_ENVY_LEGACY(qtnie)) { |
| return; |
| } |
| |
| /* See whether to do 4 address LNCB encapsulation */ |
| if (qtnie->qtn_ie_my_flags & IEEE80211_QTN_LNCB) { |
| ni->ni_lncb_4addr = 1; |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "Client " DBGMACVAR " supports 4 addr LNCB\n", DBGMACFMT(ni->ni_macaddr)); |
| } |
| if (!(vap->iv_flags_ext & IEEE80211_FEXT_WDS)) { |
| ni->ni_lncb_4addr = 0; |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "Client " DBGMACVAR " 4 addr flag cleared - I'm not a bridge\n", |
| DBGMACFMT(ni->ni_macaddr)); |
| } |
| |
| ieee80211_input_qtnie_common(ni, qtnie); |
| } |
| |
| static void |
| ieee80211_input_assoc_resp_qtnie(struct ieee80211_node *ni, struct ieee80211vap *vap, |
| struct ieee80211_ie_qtn *qtnie) |
| { |
| if (qtnie == NULL) |
| return; |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "%s: Received QTN IE v%u in assoc resp, flags=%02x %02x\n", |
| __func__, |
| IEEE80211_QTN_TYPE_ENVY(qtnie) ? 1 : 2, |
| qtnie->qtn_ie_flags, |
| IEEE80211_QTN_TYPE_ENVY_LEGACY(qtnie) ? 0x0 : qtnie->qtn_ie_my_flags); |
| |
| ni->ni_lncb_4addr = 0; |
| |
| if (IEEE80211_QTN_TYPE_ENVY_LEGACY(qtnie)) { |
| return; |
| } |
| |
| /* LNCB with 4 addresses can only be done when the AP is a bridge. */ |
| if (qtnie->qtn_ie_my_flags & IEEE80211_QTN_LNCB) { |
| ni->ni_lncb_4addr = 1; |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "AP " DBGMACVAR " supports 4 addr LNCB\n", |
| DBGMACFMT(ni->ni_macaddr)); |
| } |
| /* No 4 addr packets if not bridge mode */ |
| if (!(qtnie->qtn_ie_my_flags & IEEE80211_QTN_BRIDGEMODE)) { |
| ni->ni_lncb_4addr = 0; |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "AP " DBGMACVAR " is not a bridge - clearing LNCB flag\n", |
| DBGMACFMT(ni->ni_macaddr)); |
| } |
| |
| ieee80211_input_qtnie_common(ni, qtnie); |
| } |
| |
| int |
| ieee80211_input_tdls_qtnie(struct ieee80211_node *ni, struct ieee80211vap *vap, |
| struct ieee80211_ie_qtn *qtnie) |
| { |
| if (qtnie == NULL) { |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, |
| "%s: No QTN IE in TDLS action\n", __func__); |
| /* Flush any state from a previous association */ |
| FREE(ni->ni_qtn_assoc_ie, M_DEVBUF); |
| ni->ni_qtn_assoc_ie = NULL; |
| return 1; |
| } |
| |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG, |
| "%s: Received QTN IE v%u in TDLS action, flags=%02x %02x\n", |
| __func__, |
| IEEE80211_QTN_TYPE_ENVY(qtnie) ? 1 : 2, |
| qtnie->qtn_ie_flags, |
| IEEE80211_QTN_TYPE_ENVY_LEGACY(qtnie) ? 0x0 : qtnie->qtn_ie_my_flags); |
| |
| ieee80211_saveie(&ni->ni_qtn_assoc_ie, (u_int8_t *)qtnie); |
| |
| ni->ni_implicit_ba = 0; |
| /* Implicit BA flags for the STA */ |
| if (IEEE80211_QTN_IE_GE_V2(qtnie)) { |
| ni->ni_implicit_ba_valid = 1; |
| ni->ni_implicit_ba = qtnie->qtn_ie_implicit_ba_tid; |
| } |
| |
| if (IEEE80211_QTN_TYPE_ENVY_LEGACY(qtnie)) { |
| return 1; |
| } |
| |
| ieee80211_input_qtnie_common(ni, qtnie); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_QVSP |
| static void |
| ieee80211_input_assoc_resp_vspie(struct ieee80211vap *vap, struct ieee80211_ie_vsp *vspie, |
| uint8_t *efrm) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211_ie_vsp_item *item_p = &vspie->item[0]; |
| int i; |
| |
| if ((vspie == NULL) || !ic->ic_vsp_configure) { |
| return; |
| } |
| |
| for (i = 0; i < vspie->item_cnt; i++) { |
| if ((uint8_t *)item_p > efrm) { |
| printk(KERN_INFO "VSP: invalid count in assoc resp IE\n"); |
| return; |
| } |
| item_p++; |
| } |
| |
| item_p = &vspie->item[0]; |
| for (i = 0; i < vspie->item_cnt; i++) { |
| ic->ic_vsp_configure(ic, item_p->index, ntohl(item_p->value)); |
| item_p++; |
| } |
| } |
| |
| static void ieee80211_recv_action_vsp(struct ieee80211_node *ni, uint8_t *frm, uint8_t *efrm) |
| { |
| static const u_int8_t q_oui[3] = |
| {QTN_OUI & 0xff, (QTN_OUI >> 8) & 0xff, (QTN_OUI >> 16) & 0xff}; |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211_qvsp_act_header_s *qa = (struct ieee80211_qvsp_act_header_s *)frm; |
| |
| if (memcmp(q_oui, qa->oui, sizeof(q_oui)) || qa->type != QVSP_ACTION_TYPE_VSP) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, |
| "Not Quantenna VSP action frame (%02x%02x%02x %u %u)\n", |
| qa->oui[0], qa->oui[1], qa->oui[2], qa->type, qa->action); |
| return; |
| } |
| |
| switch (qa->action) { |
| case QVSP_ACTION_STRM_CTRL: { |
| struct ieee80211_qvsp_act_strm_ctrl_s *qsc = |
| (struct ieee80211_qvsp_act_strm_ctrl_s *)qa; |
| struct ieee80211_qvsp_strm_id *qsci = &qsc->strm_items[0]; |
| struct ieee80211_qvsp_strm_dis_attr attr; |
| int i; |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, "VSP: received strm ctrl frame\n", 0); |
| if (frm >= efrm) { |
| printk("VSP: strm ctrl frame overflow"); |
| return; |
| } |
| if (qsc->count == 0) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, |
| "VSP: invalid stream count (%u)\n", qsc->count); |
| return; |
| } |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, |
| "VSP: set state to %u for %u streams\n", |
| qsc->strm_state, qsc->count); |
| |
| if (!ic->ic_vsp_strm_state_set) { |
| return; |
| } |
| |
| attr.throt_policy = qsc->dis_attr.throt_policy; |
| attr.throt_rate = qsc->dis_attr.throt_rate; |
| attr.demote_rule = qsc->dis_attr.demote_rule; |
| attr.demote_state = qsc->dis_attr.demote_state; |
| for (i = 0; i < qsc->count; i++) { |
| if ((uint8_t *)qsci >= efrm) { |
| printk(KERN_WARNING "VSP: Frame overflow on input - discarding\n"); |
| return; |
| } |
| ic->ic_vsp_strm_state_set(ic, qsc->strm_state, qsci, &attr); |
| qsci++; |
| } |
| break; |
| } |
| case QVSP_ACTION_VSP_CTRL: { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, |
| "VSP: Received VSP_ACTION_VSP_CTRL frame\n", 0); |
| struct ieee80211_qvsp_act_vsp_ctrl_s *qsc = |
| (struct ieee80211_qvsp_act_vsp_ctrl_s *)qa; |
| struct ieee80211_qvsp_act_vsp_ctrl_item_s *qsci = &qsc->ctrl_items[0]; |
| int i; |
| |
| if (frm >= efrm) { |
| printk("VSP: ctrl frame overflow"); |
| return; |
| } |
| |
| if (qsc->count) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, |
| "VSP: %u config items\n", qsc->count); |
| } |
| |
| if (!ic->ic_vsp_configure) { |
| return; |
| } |
| |
| for (i = 0; i < qsc->count; i++) { |
| if ((uint8_t *)qsci >= efrm) { |
| printk(KERN_WARNING "VSP: Frame overflow on input - discarding\n"); |
| return; |
| } |
| ic->ic_vsp_configure(ic, ntohl(qsci->index), ntohl(qsci->value)); |
| qsci++; |
| } |
| break; |
| } |
| default: |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, |
| "VSP: Unsupported VSP action type: %d\n", qa->type); |
| break; |
| } |
| } |
| #endif |
| |
| void ieee80211_recv_action_sa_query(struct ieee80211_node *ni, |
| struct ieee80211_action *ia, |
| struct ieee80211_frame *wh, |
| u_int8_t *efrm) |
| { |
| struct ieee80211_action_sa_query *sa_query = (struct ieee80211_action_sa_query *)ia; |
| uint16_t tid = ntohs(sa_query->at_tid); |
| |
| switch (ia->ia_action) { |
| case IEEE80211_ACTION_W_SA_QUERY_REQ: |
| ieee80211_send_sa_query(ni, IEEE80211_ACTION_W_SA_QUERY_RESP, tid); |
| break; |
| case IEEE80211_ACTION_W_SA_QUERY_RESP: |
| if (tid != ni->ni_sa_query_tid || |
| (ni->ni_sa_query_timeout && time_after(jiffies, (ni->ni_sa_query_timeout + HZ)))) { |
| ni->ni_sa_query_timeout = jiffies; |
| } else { |
| ni->ni_sa_query_timeout = 0 ; |
| } |
| break; |
| default: |
| return; |
| } |
| } |
| |
| /* |
| * Handle an HT Action frame. |
| */ |
| void |
| ieee80211_action_ht(struct ieee80211_node *ni, struct sk_buff *skb, |
| struct ieee80211_frame *wh, int subtype, |
| struct ieee80211_action *ia, u_int8_t *frm, u_int8_t *efrm) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211_action_ht_txchwidth *iachwidth; |
| enum ieee80211_cwm_width chwidth; |
| u_int8_t new_val; |
| int new_mode; |
| |
| switch (ia->ia_action) { |
| case IEEE80211_ACTION_HT_TXCHWIDTH: |
| IEEE80211_VERIFY_LENGTH(efrm - frm, |
| sizeof(struct ieee80211_action_ht_txchwidth)); |
| |
| iachwidth = (struct ieee80211_action_ht_txchwidth *) (void*)frm; |
| chwidth = (iachwidth->at_chwidth == IEEE80211_A_HT_TXCHWIDTH_2040) ? |
| IEEE80211_CWM_WIDTH40 : IEEE80211_CWM_WIDTH20; |
| |
| /* Check for channel width change */ |
| if (chwidth != ni->ni_chwidth) { |
| ni->ni_newchwidth = 1; |
| } |
| |
| /* update node's recommended tx channel width */ |
| ni->ni_chwidth = chwidth; |
| break; |
| case IEEE80211_ACTION_HT_MIMOPWRSAVE: |
| /* |
| * Parsing of the input SM PS action frame. This moves the station in and out |
| * of SM Power Save and also changes the mode when enabled (dynamic, static). |
| */ |
| new_mode = IEEE80211_HTCAP_C_MIMOPWRSAVE_NONE; |
| IEEE80211_VERIFY_LENGTH(efrm - frm, |
| sizeof(struct ieee80211_action_ht_mimopowersave)); |
| frm += 2; /* action type, action category */ |
| new_val = *frm; |
| /* Bit 0 - enabled/disabled */ |
| if (new_val & 0x1) { |
| /* Bit 1 - static/dynamic */ |
| if (new_val & 0x2) { |
| new_mode = IEEE80211_HTCAP_C_MIMOPWRSAVE_DYNAMIC; |
| } else { |
| new_mode = IEEE80211_HTCAP_C_MIMOPWRSAVE_STATIC; |
| } |
| } else { |
| /* Disabled - don't care what the bit 1 value says */ |
| new_mode = IEEE80211_HTCAP_C_MIMOPWRSAVE_NONE; |
| } |
| /* Change the mode with the MUC if different from current mode */ |
| if (new_mode != ni->ni_htcap.pwrsave) { |
| /* Inform the MUC */ |
| if (ic->ic_smps != NULL) { |
| (*ic->ic_smps)(ni, new_mode); |
| } |
| ni->ni_htcap.pwrsave = new_mode; |
| } |
| break; |
| case IEEE80211_ACTION_HT_NCBEAMFORMING: |
| IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ht_bf)); |
| |
| /* Call the driver to do some stuff if it wants to */ |
| if (ic->ic_ncbeamforming != NULL) { |
| (*ic->ic_ncbeamforming)(ni, skb); |
| } |
| break; |
| default: |
| vap->iv_stats.is_rx_mgtdiscard++; |
| break; |
| } |
| } |
| |
| /* |
| * True if the station is probably an Intel 5100 or 5300 device. |
| */ |
| static inline int |
| ieee80211_is_intel_old(struct ieee80211_node *ni, uint16_t peer_cap) |
| { |
| return (ieee80211_node_is_intel(ni) && |
| !(peer_cap & IEEE80211_HTCAP_C_RXSTBC)); |
| } |
| |
| /* |
| * Intel client type identification: |
| * 620x: |
| * 2x2, MCS 32, no TX STBC support, but support RX STBC: Action: send HT20 Channel Width Notification |
| * 5100/5300: |
| * 2x2(5100) or 3x3 (5300), MCS 32, no TX STBC support and no RX STBC support: Action: send HT20 Channel Width Notification, |
| * restrict to use 2 TX Chains, and use LGI for TX. |
| * 6300: |
| * support 3x3, MCS 32, no TX STBC and SUPPORT RX STBC: treat it as normal client, Action: none |
| */ |
| static void |
| ieee80211_blacklist_ba(struct ieee80211_node *ni, u_int8_t tid) |
| { |
| struct shared_params *params = qtn_mproc_sync_shared_params_get(); |
| struct ieee80211com *ic = ni->ni_ic; |
| struct ieee80211vap *vap = ni->ni_vap; |
| u_int16_t peer_cap = IEEE80211_HTCAP_CAPABILITIES(&ni->ni_ie_htcap); |
| |
| if ((ni->ni_qtn_flags & QTN_IS_BCM_NODE) && (params->iot_tweaks & QTN_IOT_BCM_NO_3SS_MCS_TWEAK)) { |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS3] = 0x00; |
| } else if ((ni->ni_vendor == PEER_VENDOR_RLNK) && (params->iot_tweaks & QTN_IOT_RLNK_NO_3SS_MCS_TWEAK) && !(IEEE80211_NODE_IS_VHT(ni))) { |
| ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS3] = 0x00; |
| } |
| /* WAR: 11n LDPC in IOT mode is allowed only if: |
| iwpriv wifi0 set_ldpc_non_qtn 1 |
| Node is BRCM |
| BRCM node indicated support for LDPC */ |
| if (!(vap->iv_ht_flags & IEEE80211_HTF_LDPC_ALLOW_NON_QTN) && |
| (ni->ni_qtn_assoc_ie == NULL) && |
| !(ni->ni_qtn_flags & QTN_IS_BCM_NODE) && |
| (ni->ni_htcap.cap & IEEE80211_HTCAP_C_LDPCCODING)) { |
| /* Keep LDPC disabled for non-QTN, non-BRCM devices for now */ |
| ni->ni_htcap.cap &= ~IEEE80211_HTCAP_C_LDPCCODING; |
| } |
| |
| if ((ni->ni_qtn_flags & QTN_IS_BCM_NODE) && (params->iot_tweaks & QTN_IOT_BCM_TWEAK)) { |
| ieee80211_note(ni->ni_vap, "TX BA rejected for BCM client %s\n", |
| ether_sprintf(ni->ni_macaddr)); |
| ieee80211_send_delba(ni, tid, 0, IEEE80211_REASON_STA_NOT_USE); |
| ieee80211_node_tx_ba_set_state(ni, tid, IEEE80211_BA_BLOCKED, 0); |
| return; |
| } |
| |
| /* |
| * For Realtek devices, disable A-MSDU since we got better performance without A-MSDU, |
| * but this condition may be changed in future |
| */ |
| if ((ni->ni_qtn_flags & QTN_IS_REALTEK_NODE) && (params->iot_tweaks & QTN_IOT_RTK_NO_AMSDU_TWEAK)) { |
| ni->ni_ba_tx[tid].flags &= ~QTN_BA_ARGS_F_AMSDU; |
| } |
| |
| if (ieee80211_is_intel_old(ni, peer_cap)) { |
| /* |
| * Old Intel devices do not Ack BBF QoS null frames. This can |
| * cause unstability and disconnections. |
| */ |
| ni->ni_bbf_disallowed = 1; |
| } |
| |
| /* Try to identify problem peers - may cause other peer stations to be blacklisted */ |
| if ((((params->iot_tweaks & QTN_IOT_INTEL5100_TWEAK) && !(ni->ni_htcap.cap & IEEE80211_HTCAP_C_RXSTBC)) || |
| ((params->iot_tweaks & QTN_IOT_INTEL6200_TWEAK) && (ni->ni_htcap.cap & IEEE80211_HTCAP_C_RXSTBC))) && |
| (ni->ni_qtn_assoc_ie == NULL) && !(ni->ni_qtn_flags & QTN_IS_BCM_NODE) && |
| (ni->ni_htcap.mpduspacing == 5) && |
| !(peer_cap & IEEE80211_HTCAP_C_TXSTBC) && |
| (peer_cap & IEEE80211_HTCAP_C_SHORTGI20) && |
| ((ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM1] & 0x1) == 0x01) && /* Some Intel firmware versions support MCS 32 */ |
| (ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM2] == 0) && |
| (ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM3] == 0) && |
| (ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM4] == 0) && |
| (ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM5] == 0) && |
| (ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_UEQM6] == 0)) { |
| /* Currently disabling BBF to intel. With BBF, it periodically sends delba every 30 seconds */ |
| ni->ni_bbf_disallowed = 1; |
| /* TX BA does not work with Intel 5100 when running on HT40 mode */ |
| if (params->iot_tweaks & QTN_IOT_INTEL_SEND_NCW_ACTION) { |
| ieee80211_note(ni->ni_vap, "TX Notify Chan Width Action to STA %s\n", |
| ether_sprintf(ni->ni_macaddr)); |
| ic->ic_send_notify_chan_width_action(ni->ni_vap, ni, 0); |
| } else if (get_hardware_revision() <= HARDWARE_REVISION_RUBY_D) { |
| ieee80211_note(ni->ni_vap, "TX BA rejected for incompatible peer %s\n", |
| ether_sprintf(ni->ni_macaddr)); |
| ieee80211_send_delba(ni, tid, 0, IEEE80211_REASON_STA_NOT_USE); |
| ieee80211_node_tx_ba_set_state(ni, tid, IEEE80211_BA_BLOCKED, 0); |
| } |
| |
| if (!(peer_cap & IEEE80211_HTCAP_C_RXSTBC)) { |
| if (ni->ni_associd && (ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS3] == 0)) { |
| ni->ni_qtn_flags |= QTN_IS_INTEL_5100_NODE; |
| } else { |
| ni->ni_qtn_flags |= QTN_IS_INTEL_5300_NODE; |
| } |
| |
| /* tell Muc only to use two 2 TX chain for Intel 5x00 client |
| * and turn off SGI for TX to them |
| */ |
| if (params->iot_tweaks & QTN_IOT_INTEL_NOAGG2TXCHAIN_TWEAK) { |
| ni->ni_flags |= IEEE80211_NODE_2_TX_CHAINS; |
| ieee80211_note(ni->ni_vap, "STA %s is Intel %s: disable TX side of SGI and restrict to 2 TX chains\n", |
| ether_sprintf(ni->ni_macaddr), |
| (ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS3] == 0xFF) ? "5300" : "5100"); |
| /* disable 5100 aggregation */ |
| if ((ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS3] == 0) && ni->ni_associd) { |
| ieee80211_note(ni->ni_vap, "TX BA rejected for Intel 5100 %s\n", |
| ether_sprintf(ni->ni_macaddr)); |
| ieee80211_send_delba(ni, tid, 0, IEEE80211_REASON_STA_NOT_USE); |
| ieee80211_node_tx_ba_set_state(ni, tid, IEEE80211_BA_BLOCKED, 0); |
| } |
| } |
| } |
| } |
| } |
| |
| static inline u_int16_t lower_power_of_2(u_int16_t bsize) |
| { |
| if (bsize && !((bsize - 1) & bsize)) { |
| /* Already a power of 2 */ |
| return bsize; |
| } else if (!bsize) { |
| return 0; |
| } |
| |
| bsize--; |
| bsize |= bsize >> 1; |
| bsize |= bsize >> 2; |
| bsize |= bsize >> 4; |
| bsize |= bsize >> 8; |
| return (bsize - (bsize >> 1)); |
| } |
| |
| static __inline int ieee80211_action_ba_permitted(struct ieee80211_node *ni, u_int8_t tid) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| |
| if (vap->rx_ba_decline) { |
| return 0; |
| } |
| |
| if (!(vap->iv_ba_control & (1 << tid))) { |
| return 0; |
| } |
| |
| if (ni->ni_ba_rx[tid].type != IEEE80211_BA_IMMEDIATE) { |
| return 0; |
| } |
| |
| if ((get_hardware_revision() <= HARDWARE_REVISION_RUBY_D) && |
| ((ni->ni_qtn_flags & QTN_IS_INTEL_5300_NODE) || |
| (ni->ni_qtn_flags & QTN_IS_INTEL_5100_NODE))) { |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* |
| * Identify client during BA setup |
| * For some IOT client, such as Intel6205 with some version, we cannot identify them during |
| * association. But we can identify from BA setup. So it is necessary to update the node |
| * information into MuC/AuC then. |
| */ |
| static void |
| ieee80211_node_identify_ba(struct ieee80211com *ic, struct ieee80211_node *ni, uint8_t tid) |
| { |
| int changed = 0; |
| |
| if (!(ni->ni_qtn_flags & QTN_IS_INTEL_NODE) && |
| (ieee80211_node_is_intel(ni))) { |
| changed = 1; |
| ni->ni_vendor = PEER_VENDOR_INTEL; |
| } |
| |
| /* let it decide whether to use any tweaks such as disable BBF */ |
| ieee80211_blacklist_ba(ni, tid); |
| |
| /* update into MuC/AuC if vendor changed */ |
| if (changed) |
| ic->ic_node_update(ni); |
| } |
| |
| /* |
| * Handle a Block Acknowledgement Action frame. |
| */ |
| static void |
| ieee80211_action_ba(struct ieee80211_node *ni, struct ieee80211_frame *wh, int subtype, |
| struct ieee80211_action *ia, u_int8_t *frm, u_int8_t *efrm) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| u_int8_t tid; |
| u_int8_t initiator; |
| u_int16_t reason; |
| u_int16_t temp16; |
| struct ba_action_resp ba_req_resp; |
| struct ieee80211_action_ba_addba_resp *ba_resp; |
| struct ieee80211_action_data act; |
| #ifdef CONFIG_QVSP |
| struct ieee80211_ba_throt *ba_throt; |
| #endif |
| |
| switch (ia->ia_action) { |
| case IEEE80211_ACTION_BA_ADDBA_REQ: |
| IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ba_addba_req)); |
| frm += 2; /* action type = 1 octet and category = 1 octet */ |
| |
| temp16 = LE_READ_2((frm + 1)); /* ba parameter field is 1 octet ahead */ |
| tid = ((temp16) & IEEE80211_A_BA_TID_M) >> IEEE80211_A_BA_TID_S; |
| #ifdef CONFIG_QVSP |
| ba_throt = &ni->ni_ba_rx[tid].ba_throt; |
| if (ni->ni_vap->iv_opmode == IEEE80211_M_HOSTAP) { |
| if (ba_throt->throt_intv && |
| ba_throt->last_setup_jiffies && |
| time_before(jiffies, (ba_throt->last_setup_jiffies + |
| msecs_to_jiffies(ba_throt->throt_intv)))) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VSP, |
| "VSP: discard ADDBA REQ from node %u tid %d\n", |
| IEEE80211_AID(ni->ni_associd), tid); |
| break; |
| } |
| } |
| #endif |
| ni->ni_ba_rx[tid].dlg_in = *frm; |
| ni->ni_ba_rx[tid].type = (temp16 & IEEE80211_A_BA_IMMEDIATE) ? IEEE80211_BA_IMMEDIATE : |
| IEEE80211_BA_DELAYED; |
| ni->ni_ba_rx[tid].flags = (temp16 & IEEE80211_A_BA_AMSDU_SUPPORTED) ? QTN_BA_ARGS_F_AMSDU : 0; |
| ni->ni_ba_rx[tid].buff_size = |
| lower_power_of_2((((temp16) & IEEE80211_A_BA_BUFF_SIZE_M) >> IEEE80211_A_BA_BUFF_SIZE_S)); |
| #ifdef CONFIG_QVSP |
| ni->ni_ba_rx[tid].ba_throt.unthroted_win_size = ni->ni_ba_rx[tid].buff_size; |
| #endif |
| if (ni->ni_vap->iv_opmode == IEEE80211_M_WDS && tid == IEEE80211_WDS_LINK_MAINTAIN_BA_TID && |
| ni->ni_qtn_assoc_ie) { |
| /* For WDS, use larger buffer size for TID 0 to get more throughput */ |
| if ((ni->ni_ba_rx[tid].buff_size == 0) || |
| (ni->ni_ba_rx[tid].buff_size > IEEE80211_DEFAULT_BA_WINSIZE_H)) { |
| ni->ni_ba_rx[tid].buff_size = IEEE80211_DEFAULT_BA_WINSIZE_H; |
| } |
| } else { |
| if ((ni->ni_ba_rx[tid].buff_size == 0) || |
| (ni->ni_ba_rx[tid].buff_size > vap->iv_max_ba_win_size)) { |
| ni->ni_ba_rx[tid].buff_size = vap->iv_max_ba_win_size; |
| } |
| #ifdef CONFIG_QVSP |
| if (ni->ni_vap->iv_opmode == IEEE80211_M_HOSTAP) { |
| if (ba_throt->throt_win_size && |
| (ba_throt->throt_win_size < ni->ni_ba_rx[tid].buff_size)) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VSP, |
| "VSP: limit node %u tid %d BA winsize from %u to %u\n", |
| IEEE80211_AID(ni->ni_associd), tid, ni->ni_ba_rx[tid].buff_size, |
| ba_throt->throt_win_size); |
| ni->ni_ba_rx[tid].buff_size = ba_throt->throt_win_size; |
| } |
| } |
| #endif |
| } |
| |
| frm += 3; /* dialog = 1 octet and ba parameters = 2 octets */ |
| temp16 = LE_READ_2((frm)); |
| ni->ni_ba_rx[tid].timeout = temp16; |
| if(ni->ni_ba_rx[tid].timeout != 0) { |
| ni->ni_ba_rx[tid].timeout = 0; |
| } |
| |
| frm += 2; /* timeout = 2 octets */ |
| temp16 = LE_READ_2((frm)); |
| ni->ni_ba_rx[tid].frag = (temp16 & IEEE80211_A_BA_FRAG_M); |
| ni->ni_ba_rx[tid].seq = (temp16 & IEEE80211_A_BA_SEQ_M) >> IEEE80211_A_BA_SEQ_S; |
| |
| if (ieee80211_action_ba_permitted(ni, tid)) { |
| ni->ni_ba_rx[tid].state = IEEE80211_BA_ESTABLISHED; |
| ba_req_resp.reason = IEEE80211_STATUS_SUCCESS; |
| IEEE80211_NOTE(vap, IEEE80211_MSG_11N, ni, |
| "block ack requested by peer tid accepted: %u size %u seq %u", |
| tid, ni->ni_ba_rx[tid].buff_size, ni->ni_ba_rx[tid].seq); |
| if (ni->ni_vap->iv_opmode == IEEE80211_M_WDS) { |
| ic->ic_pm_reason = IEEE80211_PM_LEVEL_RCVD_ADDBA_REQ; |
| ieee80211_pm_queue_work(ic); |
| } |
| } else { |
| ni->ni_ba_rx[tid].state = IEEE80211_BA_BLOCKED; |
| ba_req_resp.reason = IEEE80211_STATUS_PEER_MECHANISM_REJECT; |
| IEEE80211_NOTE(vap, IEEE80211_MSG_11N, ni, |
| "block ack requested by peer tid denied: %u size %u seq %u", |
| tid, ni->ni_ba_rx[tid].buff_size, ni->ni_ba_rx[tid].seq); |
| } |
| |
| #ifdef CONFIG_QVSP |
| ba_throt->last_setup_jiffies = jiffies; |
| #endif |
| |
| /* Call the driver to inform the MuC */ |
| if (ic->ic_htaddba != NULL) { |
| (*ic->ic_htaddba)(ni, tid, 0); |
| } |
| |
| /* send the response */ |
| act.cat = IEEE80211_ACTION_CAT_BA; |
| act.action = IEEE80211_ACTION_BA_ADDBA_RESP; |
| ba_req_resp.type = ni->ni_ba_rx[tid].type; |
| ba_req_resp.tid = tid; |
| ba_req_resp.seq = ni->ni_ba_rx[tid].seq; |
| ba_req_resp.frag = ni->ni_ba_rx[tid].frag; |
| ba_req_resp.timeout = ni->ni_ba_rx[tid].timeout; |
| ba_req_resp.buff_size = ni->ni_ba_rx[tid].buff_size; |
| |
| act.params = (void *)&ba_req_resp; |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_ACTION, (int)&act); |
| break; |
| case IEEE80211_ACTION_BA_ADDBA_RESP: |
| IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ba_addba_resp)); |
| ba_resp = (struct ieee80211_action_ba_addba_resp *)(void*)frm; |
| frm += 2; /* action type = 1 octet and category = 1 octet */ |
| |
| temp16 = LE_READ_2((frm + 3)); /* parameter field is 3 octet ahead */ |
| tid = ((temp16) & IEEE80211_A_BA_TID_M) >> IEEE80211_A_BA_TID_S; |
| |
| if (ni->ni_ba_tx[tid].dlg_out != (*frm)) { |
| vap->iv_stats.is_rx_mgtdiscard++; |
| break; |
| } |
| |
| frm += 1; /* dialog = 1 octet */ |
| |
| temp16 = LE_READ_2(frm); |
| if (temp16 == 0) { |
| ieee80211_node_tx_ba_set_state(ni, tid, IEEE80211_BA_ESTABLISHED, 0); |
| if (ni->ni_vap->iv_opmode == IEEE80211_M_WDS) { |
| ic->ic_pm_reason = IEEE80211_PM_LEVEL_RCVD_ADDBA_RESP; |
| ieee80211_pm_queue_work(ic); |
| } |
| } else { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, wh->i_addr2, |
| "block ack not allowed by peer due to %d",temp16); |
| if (temp16 == IEEE80211_STATUS_PEER_MECHANISM_REJECT) { |
| ieee80211_node_tx_ba_set_state(ni, tid, IEEE80211_BA_BLOCKED, 0); |
| } else if (ni->ni_vap->iv_ba_control & (1 << tid)) { |
| ieee80211_node_tx_ba_set_state(ni, tid, IEEE80211_BA_FAILED, |
| IEEE80211_TX_BA_REQUEST_RETRY_TIMEOUT); |
| } else { |
| ieee80211_node_tx_ba_set_state(ni, tid, IEEE80211_BA_BLOCKED, 0); |
| } |
| if (ic->ic_htaddba != NULL) { |
| (*ic->ic_htaddba)(ni, tid, 1); |
| } |
| break; |
| } |
| |
| frm += 2; /* status = 2 octets */ |
| |
| temp16 = LE_READ_2(frm); |
| ni->ni_ba_tx[tid].type = (temp16 & IEEE80211_A_BA_IMMEDIATE)?1:0; |
| ni->ni_ba_tx[tid].buff_size = |
| lower_power_of_2((((temp16) & IEEE80211_A_BA_BUFF_SIZE_M) >> IEEE80211_A_BA_BUFF_SIZE_S)); |
| ni->ni_ba_tx[tid].flags = (temp16 & IEEE80211_A_BA_AMSDU_SUPPORTED) ? QTN_BA_ARGS_F_AMSDU : 0; |
| |
| if ((ni->ni_qtn_flags & QTN_IS_BCM_NODE) && !IEEE80211_NODE_IS_VHT(ni) && |
| !ni->ni_htcap.mcsset[IEEE80211_HT_MCSSET_20_40_NSS3]) { |
| /* |
| * IOT WAR: 1SS/2SS BCM devices that advertise window size as 32 have trouble |
| * handling SW retries in between next AMPDU. Based on experiments, the results |
| * are smooth if window size is set to 16. Scoreboard size in AuC should also be |
| * 16 for these devices. The fix is mainly targeted to BCM 2x2 11n based ipads. |
| */ |
| ni->ni_ba_tx[tid].buff_size = MIN(ni->ni_ba_tx[tid].buff_size, 16); |
| } |
| |
| if (ni->ni_qtn_flags & QTN_IS_GALAXY_NOTE_4_NODE) { |
| /* Disable AMSDU for these phones */ |
| ni->ni_ba_tx[tid].flags &= ~QTN_BA_ARGS_F_AMSDU; |
| } |
| |
| frm += 2; /* ba parameter = 2 octets */ |
| temp16 = LE_READ_2(frm); |
| ni->ni_ba_tx[tid].timeout = temp16; |
| |
| ieee80211_note_mac(vap, wh->i_addr2, |
| "block ack allowed by peer tid: %d size %d type 0x%x flags 0x%x to %d", |
| tid, ni->ni_ba_tx[tid].buff_size, |
| ni->ni_ba_tx[tid].type, ni->ni_ba_tx[tid].flags, ni->ni_ba_tx[tid].timeout); |
| |
| ieee80211_node_identify_ba(ic, ni, tid); |
| |
| /* Call the driver to do some stuff if it wants to */ |
| if (ic->ic_htaddba != NULL) { |
| (*ic->ic_htaddba)(ni, tid, 1); |
| } |
| break; |
| case IEEE80211_ACTION_BA_DELBA: |
| IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ba_delba)); |
| frm += 2; /* action type = 1 octet and category = 1 octet */ |
| |
| temp16 = LE_READ_2(frm); |
| tid = MS(temp16, IEEE80211_A_BA_DELBA_TID); |
| initiator = MS(temp16, IEEE80211_A_BA_INITIATOR); |
| frm += 2; |
| |
| reason = LE_READ_2(frm); |
| |
| if (tid < WME_NUM_TID) { |
| ieee80211_node_ba_del(ni, tid, !initiator, reason); |
| } |
| |
| break; |
| } |
| } |
| |
| void ieee80211_parse_measure_request(struct ieee80211_node *ni, |
| struct ieee80211_frame *wh, |
| u_int8_t category, |
| u_int8_t frame_token, |
| u_int8_t *frm, |
| u_int8_t *efrm) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211_ie_measure_comm *meas_comm; |
| struct ieee80211_global_measure_info *meas_info = &ic->ic_measure_info; |
| |
| if (sizeof(struct ieee80211_ie_measure_comm) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "no enough data for measurement common field"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| meas_comm = (struct ieee80211_ie_measure_comm*)frm; |
| frm += sizeof(*meas_comm); |
| |
| if (meas_comm->id != IEEE80211_ELEMID_MEASREQ) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, |
| "mgt", "%s", "measurement request ID mismatch\n"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| |
| switch (meas_comm->type) { |
| case IEEE80211_CCA_MEASTYPE_BASIC: |
| { |
| struct ieee80211_ie_measreq *meas_request; |
| |
| if (sizeof(struct ieee80211_ie_measreq) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, |
| "mgmt", "%s", "no enough data for measuremet request ie common field\n"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| meas_request = (struct ieee80211_ie_measreq *)frm; |
| frm += sizeof(struct ieee80211_ie_measreq); |
| |
| /* check current state */ |
| if (meas_info->status != MEAS_STATUS_IDLE) { |
| ieee80211_action_measurement_report_fail(ni, |
| meas_comm->type, |
| IEEE80211_CCA_REPMODE_REFUSE, |
| frame_token, |
| meas_comm->token); |
| return; |
| } |
| |
| /* send autonomous response if enable=1 report=1, which is not against the standard */ |
| if ((meas_comm->mode & IEEE80211_CCA_REQMODE_ENABLE) && |
| (meas_comm->mode & IEEE80211_CCA_REQMODE_REPORT)) |
| meas_info->frame_token = 0; |
| else |
| meas_info->frame_token = frame_token; |
| |
| meas_info->ni = ni; |
| meas_info->type = meas_comm->type; |
| |
| meas_info->param.basic.channel = meas_request->chan_num; |
| *((u_int32_t *)&meas_info->param.basic.tsf) = BE_READ_4((u_int8_t *)&meas_request->start_tsf); |
| *((u_int32_t *)&meas_info->param.basic.tsf + 1) = BE_READ_4((u_int8_t *)&meas_request->start_tsf + 4); |
| meas_info->param.basic.duration_tu = ntohs(meas_request->duration_tu); |
| |
| if (ieee80211_action_trigger_measurement(ic) != 0) { |
| ieee80211_action_measurement_report_fail(ni, |
| meas_comm->type, |
| IEEE80211_CCA_REPMODE_REFUSE, |
| frame_token, |
| meas_comm->token); |
| } |
| break; |
| } |
| case IEEE80211_CCA_MEASTYPE_CCA: |
| { |
| /* Quantenna CCA Extension */ |
| if (category == IEEE80211_ACTION_CAT_RM) { |
| if (ic->ic_scs.scs_stats_on) { |
| struct shared_params *sp = qtn_mproc_sync_shared_params_get(); |
| struct qtn_scs_info_set *scs_info_lh = sp->scs_info_lhost; |
| uint64_t tsf = 0; |
| uint16_t cca_try; |
| uint16_t cca_intf; |
| |
| ic->ic_get_tsf(&tsf); |
| |
| cca_try = (uint16_t)scs_info_lh->scs_info[scs_info_lh->valid_index].cca_try; |
| if (cca_try == 0) { |
| break; |
| } |
| |
| cca_intf = (uint16_t)scs_info_lh->scs_info[scs_info_lh->valid_index].cca_interference; |
| /* scale before sending */ |
| cca_intf = cca_intf * IEEE80211_SCS_CCA_INTF_SCALE / cca_try; |
| ieee80211_send_action_cca_report(ni, frame_token, cca_intf, |
| tsf, cca_try, |
| ic->ic_scs.scs_sp_err_smthed, |
| ic->ic_scs.scs_lp_err_smthed, |
| 0, NULL, 0); /*TODO: Do we need to send others time? */ |
| ieee80211_send_action_dfs_report(ni); |
| } |
| } else { |
| struct ieee80211_ie_measreq *meas_request; |
| |
| if (sizeof(struct ieee80211_ie_measreq) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, |
| "mgmt", "%s", "no enough data for measuremet request ie common field\n"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| meas_request = (struct ieee80211_ie_measreq *)frm; |
| frm += sizeof(struct ieee80211_ie_measreq); |
| |
| /* check current state */ |
| if (meas_info->status != MEAS_STATUS_IDLE) { |
| ieee80211_action_measurement_report_fail(ni, meas_comm->type, |
| IEEE80211_CCA_REPMODE_REFUSE, frame_token, meas_comm->token); |
| return; |
| } |
| |
| /* send autonomous response if enable=1 report=1, which is not against the standard */ |
| if ((meas_comm->mode & IEEE80211_CCA_REQMODE_ENABLE) && |
| (meas_comm->mode & IEEE80211_CCA_REQMODE_REPORT)) |
| meas_info->frame_token = 0; |
| else |
| meas_info->frame_token = frame_token; |
| |
| meas_info->ni = ni; |
| meas_info->type = meas_comm->type; |
| |
| meas_info->param.cca.channel = meas_request->chan_num; |
| *((u_int32_t *)&meas_info->param.cca.tsf) = BE_READ_4((u_int8_t *)&meas_request->start_tsf); |
| *((u_int32_t *)&meas_info->param.cca.tsf + 1) = BE_READ_4((u_int8_t *)&meas_request->start_tsf + 4); |
| meas_info->param.cca.duration_tu = ntohs(meas_request->duration_tu); |
| |
| if (ieee80211_action_trigger_measurement(ic) != 0) { |
| ieee80211_action_measurement_report_fail(ni, |
| meas_comm->type, |
| IEEE80211_CCA_REPMODE_REFUSE, |
| frame_token, |
| meas_comm->token); |
| } |
| } |
| break; |
| } |
| case IEEE80211_CCA_MEASTYPE_RPI: |
| { |
| struct ieee80211_ie_measreq *meas_request; |
| |
| if (sizeof(struct ieee80211_ie_measreq) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, |
| "mgmt", "%s", "no enough data for measuremet request ie common field\n"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| meas_request = (struct ieee80211_ie_measreq *)frm; |
| frm += sizeof(struct ieee80211_ie_measreq); |
| |
| /* check current state */ |
| if (meas_info->status != MEAS_STATUS_IDLE) { |
| ieee80211_action_measurement_report_fail(ni, |
| meas_comm->type, |
| IEEE80211_CCA_REPMODE_REFUSE, |
| frame_token, |
| meas_comm->token); |
| return; |
| } |
| |
| /* send autonomous response if enable=1 report=1, which is not against the standard */ |
| if ((meas_comm->mode & IEEE80211_CCA_REQMODE_ENABLE) && |
| (meas_comm->mode & IEEE80211_CCA_REQMODE_REPORT)) |
| meas_info->frame_token = 0; |
| else |
| meas_info->frame_token = frame_token; |
| |
| meas_info->ni = ni; |
| meas_info->type = meas_comm->type; |
| |
| meas_info->param.rpi.channel = meas_request->chan_num; |
| *((u_int32_t *)&meas_info->param.rpi.tsf) = BE_READ_4((u_int8_t *)&meas_request->start_tsf); |
| *((u_int32_t *)&meas_info->param.rpi.tsf + 1) = BE_READ_4((u_int8_t *)&meas_request->start_tsf + 4); |
| meas_info->param.rpi.duration_tu = ntohs(meas_request->duration_tu); |
| |
| if (ieee80211_action_trigger_measurement(ic) != 0) { |
| ieee80211_action_measurement_report_fail(ni, |
| meas_comm->type, |
| IEEE80211_CCA_REPMODE_REFUSE, |
| frame_token, |
| meas_comm->token); |
| } |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_STA: |
| { |
| struct ieee80211_ie_measreq_sta_stat *sta_stats_request; |
| u_int8_t duration; |
| u_int8_t group_id; |
| u_int8_t measure_token; |
| ieee80211_11k_sub_element_head se_head; |
| ieee80211_11k_sub_element *p_se; |
| |
| measure_token = meas_comm->token; |
| /* sta statistics request field */ |
| if (sizeof(struct ieee80211_ie_measreq_sta_stat) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, |
| "mgmt", "%s", "no enough data for sta stats request field\n"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| sta_stats_request = (struct ieee80211_ie_measreq_sta_stat*)frm; |
| frm += sizeof(struct ieee80211_ie_measreq_sta_stat); |
| |
| if (memcmp(vap->iv_myaddr, sta_stats_request->peer_mac, IEEE80211_ADDR_LEN)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s-mac:%s", "Peer mac address un-match.", |
| ether_sprintf(sta_stats_request->peer_mac)); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| |
| duration = sta_stats_request->duration_tu; |
| group_id = sta_stats_request->group_id; |
| |
| SLIST_INIT(&se_head); |
| /* sub element */ |
| while (frm < efrm) { |
| switch (frm[0]) { |
| case IEEE80211_ELEMID_VENDOR: |
| { |
| struct ieee80211_ie_qtn_rm_measure_sta *qtn_ie; |
| u_int8_t sequence; |
| u_int32_t vendor_flags; |
| int32_t tlv_cnt, i; |
| u_int8_t *tlv_frm; |
| |
| qtn_ie = (struct ieee80211_ie_qtn_rm_measure_sta *)frm; |
| if (!isqtnmrespoui(frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "RM", "%s: id %u type %u OUI %02x:%02x:%02x", |
| "specific IE incorrect", |
| qtn_ie->id, qtn_ie->type, qtn_ie->qtn_ie_oui[0], |
| qtn_ie->qtn_ie_oui[1], qtn_ie->qtn_ie_oui[2]); |
| vap->iv_stats.is_rx_action++; |
| break; |
| } |
| |
| sequence = qtn_ie->seq; |
| if (qtn_ie->type == QTN_OUI_RM_ALL) { |
| vendor_flags = BIT(RM_QTN_MAX + 1) - 1; |
| } else { |
| if ((qtn_ie->len != (qtn_ie->data[0] * 2) |
| + sizeof(struct ieee80211_ie_qtn_rm_measure_sta) - 1)){ |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "RM", "%s: id %u type %u len %u cnt %u", |
| "specific IE length-element count mis-match", |
| qtn_ie->id, qtn_ie->type, qtn_ie->len, qtn_ie->data[0]); |
| vap->iv_stats.is_rx_action++; |
| break; |
| } |
| |
| tlv_cnt = qtn_ie->data[0]; |
| tlv_frm = (u_int8_t *)&qtn_ie->data[1]; |
| vendor_flags = 0; |
| |
| for (i = 0; i < tlv_cnt; i++) { |
| switch (tlv_frm[0]) { |
| case RM_QTN_TX_STATS: |
| case RM_QTN_RX_STATS: |
| case RM_QTN_MAX_QUEUED: |
| case RM_QTN_LINK_QUALITY: |
| case RM_QTN_RSSI_DBM: |
| case RM_QTN_BANDWIDTH: |
| case RM_QTN_SNR: |
| case RM_QTN_TX_PHY_RATE: |
| case RM_QTN_RX_PHY_RATE: |
| case RM_QTN_CCA: |
| case RM_QTN_BR_IP: |
| case RM_QTN_RSSI: |
| case RM_QTN_HW_NOISE: |
| case RM_QTN_SOC_MACADDR: |
| case RM_QTN_SOC_IPADDR: |
| case RM_QTN_RESET_CNTS: |
| case RM_QTN_RESET_QUEUED: |
| { |
| vendor_flags |= BIT(tlv_frm[0]); |
| tlv_frm += 2; |
| break; |
| } |
| default: |
| tlv_frm += 2; |
| break; |
| } |
| } |
| } |
| |
| p_se = (ieee80211_11k_sub_element *)kmalloc(sizeof(*p_se) + sizeof(struct stastats_subele_vendor), GFP_ATOMIC); |
| if (p_se != NULL) { |
| struct stastats_subele_vendor *vendor; |
| |
| p_se->sub_id = IEEE80211_ELEMID_VENDOR; |
| vendor = (struct stastats_subele_vendor *)p_se->data; |
| vendor->flags = vendor_flags; |
| vendor->sequence = sequence; |
| SLIST_INSERT_HEAD(&se_head, p_se, next); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| frm += 2 + frm[1]; |
| } |
| |
| ieee80211_send_rm_rep_stastats(ni, |
| 0, |
| frame_token, |
| meas_comm->token, |
| sta_stats_request->group_id, |
| ntohs(sta_stats_request->duration_tu), |
| (void *)&se_head); |
| |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_CH_LOAD: |
| { |
| struct ieee80211_ie_measreq_chan_load *cl; |
| |
| if (sizeof(struct ieee80211_ie_measreq_chan_load) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, |
| "mgmt", "%s", "no enough data for channel load request field\n"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| cl = (struct ieee80211_ie_measreq_chan_load *)frm; |
| frm += sizeof(struct ieee80211_ie_measreq_chan_load); |
| |
| /* check current state */ |
| if (meas_info->status != MEAS_STATUS_IDLE) { |
| ieee80211_action_measurement_report_fail(ni, |
| meas_comm->type, |
| IEEE80211_CCA_REPMODE_REFUSE, |
| frame_token, |
| meas_comm->token); |
| return; |
| } |
| |
| /* send autonomous response if enable=1 report=1, which is not against the standard */ |
| if ((meas_comm->mode & IEEE80211_CCA_REQMODE_ENABLE) && |
| (meas_comm->mode & IEEE80211_CCA_REQMODE_REPORT)) |
| meas_info->frame_token = 0; |
| else |
| meas_info->frame_token = frame_token; |
| |
| meas_info->ni = ni; |
| meas_info->type = meas_comm->type; |
| |
| meas_info->param.chan_load.op_class = cl->operating_class; |
| meas_info->param.chan_load.channel = cl->channel_num; |
| meas_info->param.chan_load.duration_tu = ntohs(cl->duration_tu); |
| meas_info->param.chan_load.upper_interval = ntohs(cl->random_interval_tu); |
| |
| if (ieee80211_action_trigger_measurement(ic) != 0) { |
| ieee80211_action_measurement_report_fail(ni, |
| meas_comm->type, |
| IEEE80211_CCA_REPMODE_REFUSE, |
| frame_token, |
| meas_comm->token); |
| } |
| |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_NOISE: |
| { |
| struct ieee80211_ie_measreq_noise_his *nh; |
| |
| if (sizeof(struct ieee80211_ie_measreq_noise_his) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, |
| "mgmt", "%s", "no enough data for noise hisgram request field\n"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| nh = (struct ieee80211_ie_measreq_noise_his *)frm; |
| frm += sizeof(struct ieee80211_ie_measreq_noise_his); |
| |
| /* check current state */ |
| if (meas_info->status != MEAS_STATUS_IDLE) { |
| ieee80211_action_measurement_report_fail(ni, |
| meas_comm->type, |
| IEEE80211_CCA_REPMODE_REFUSE, |
| frame_token, |
| meas_comm->token); |
| return; |
| } |
| |
| /* send autonomous response if enable=1 report=1, which is not against the standard */ |
| if ((meas_comm->mode & IEEE80211_CCA_REQMODE_ENABLE) && |
| (meas_comm->mode & IEEE80211_CCA_REQMODE_REPORT)) |
| meas_info->frame_token = 0; |
| else |
| meas_info->frame_token = frame_token; |
| |
| meas_info->ni = ni; |
| meas_info->type = meas_comm->type; |
| |
| meas_info->param.noise_his.op_class = nh->operating_class; |
| meas_info->param.noise_his.duration_tu = ntohs(nh->duration_tu); |
| meas_info->param.noise_his.upper_interval= ntohs(nh->random_interval_tu); |
| meas_info->param.noise_his.channel = nh->channel_num; |
| |
| if (ieee80211_action_trigger_measurement(ic) != 0) { |
| ieee80211_action_measurement_report_fail(ni, |
| meas_comm->type, |
| IEEE80211_CCA_REPMODE_REFUSE, |
| frame_token, |
| meas_comm->token); |
| } |
| |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_BEACON: |
| { |
| struct ieee80211_ie_measreq_beacon *beacon; |
| u_int8_t wildcard_bssid[IEEE80211_ADDR_LEN] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; |
| u_int8_t parent_tsf[4] = {0}; |
| |
| if (sizeof(struct ieee80211_ie_measreq_beacon) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, |
| "mgmt", "%s", "no enough data for beacon report request field\n"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| beacon = (struct ieee80211_ie_measreq_beacon *)frm; |
| frm += sizeof(struct ieee80211_ie_measreq_beacon); |
| |
| /* |
| * as described in 10.11.9.1: |
| * if the STA has no beacon information avaiilable then the STA may |
| * either refuse the request or send an empty Beacon Report. |
| * here we choose to refuse this request if there's no candidates |
| * */ |
| if (memcmp(beacon->bssid, wildcard_bssid, IEEE80211_ADDR_LEN) == 0) { |
| /* if request wildcard bssid, at least one BSS could be reported */ |
| ieee80211_send_rm_rep_beacon(ni, 0, frame_token, |
| meas_comm->token, beacon->operating_class, |
| beacon->channel_num, beacon->duration_tu, |
| 0, 0, 0, ni->ni_vap->iv_bss->ni_bssid, 255, parent_tsf); |
| } else { |
| ieee80211_action_measurement_report_fail(ni, meas_comm->type, |
| IEEE80211_CCA_REPMODE_REFUSE, |
| frame_token, meas_comm->token); |
| } |
| |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_FRAME: |
| { |
| struct ieee80211_ie_measreq_frame *frame; |
| struct frame_report_subele_frame_count *entry; |
| ieee80211_11k_sub_element_head se_head; |
| ieee80211_11k_sub_element *p_se; |
| |
| if (sizeof(struct ieee80211_ie_measreq_frame) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, |
| "mgmt", "%s", "no enough data for frame report request field\n"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| frame = (struct ieee80211_ie_measreq_frame *)frm; |
| frm += sizeof(struct ieee80211_ie_measreq_frame); |
| |
| /* currently only frame count report is required */ |
| if (frame->frame_request_type != FRAME_COUNT_REPORT) { |
| ieee80211_action_measurement_report_fail(ni, |
| meas_comm->type, IEEE80211_CCA_REPMODE_REFUSE, |
| frame_token, meas_comm->token); |
| break; |
| } |
| |
| SLIST_INIT(&se_head); |
| p_se = (ieee80211_11k_sub_element *)kmalloc(sizeof(ieee80211_11k_sub_element) + |
| sizeof(struct frame_report_subele_frame_count), GFP_ATOMIC); |
| if (p_se != NULL) { |
| p_se->sub_id = IEEE80211_FRAME_REPORT_SUBELE_FRAME_COUNT_REPORT; |
| entry = (struct frame_report_subele_frame_count *)p_se->data; |
| if (ni->ni_vap->iv_opmode == IEEE80211_M_HOSTAP) { |
| memcpy(entry->ta, ni->ni_macaddr, IEEE80211_ADDR_LEN); |
| } else { |
| memcpy(entry->ta, ni->ni_vap->iv_bss->ni_macaddr, IEEE80211_ADDR_LEN); |
| } |
| memcpy(entry->bssid, ni->ni_vap->iv_bss->ni_macaddr, IEEE80211_ADDR_LEN); |
| entry->phy_type = 0; |
| entry->avg_rcpi = 0; |
| entry->last_rsni = 0; |
| entry->last_rcpi = 0; |
| entry->antenna_id = 255; |
| entry->frame_count = 1; |
| SLIST_INSERT_HEAD(&se_head, p_se, next); |
| } |
| |
| ieee80211_send_rm_rep_frame(ni, 0, |
| frame_token, meas_comm->token, |
| 0, frame->channel_num, |
| frame->duration_tu, |
| (void *)&se_head); |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_CATEGORY: |
| { |
| struct ieee80211_ie_measreq_trans_stream_cat *cat; |
| struct ieee80211_meas_report_ctrl ctrl; |
| struct ieee80211_action_data action_data; |
| int32_t i; |
| |
| if (sizeof(struct ieee80211_ie_measreq_trans_stream_cat) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, |
| "mgmt", "%s", "no enough data for transmit stream/category request field\n"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| cat = (struct ieee80211_ie_measreq_trans_stream_cat *)frm; |
| frm += sizeof(struct ieee80211_ie_measreq_trans_stream_cat); |
| |
| memset(&ctrl, 0, sizeof(ctrl)); |
| ctrl.meas_type = IEEE80211_RM_MEASTYPE_CATEGORY; |
| ctrl.report_mode = 0; |
| ctrl.token = frame_token; |
| ctrl.meas_token = meas_comm->token; |
| ctrl.autonomous = 0; |
| |
| ctrl.u.tran_stream_cat.duration_tu = cat->duration_tu; |
| memcpy(ctrl.u.tran_stream_cat.peer_sta, cat->peer_sta_addr, IEEE80211_ADDR_LEN); |
| ctrl.u.tran_stream_cat.tid = cat->tid; |
| ctrl.u.tran_stream_cat.reason = 0; |
| ctrl.u.tran_stream_cat.tran_msdu_cnt = 0; |
| ctrl.u.tran_stream_cat.msdu_discard_cnt = 0; |
| ctrl.u.tran_stream_cat.msdu_fail_cnt = 0; |
| ctrl.u.tran_stream_cat.msdu_mul_retry_cnt = 0; |
| ctrl.u.tran_stream_cat.qos_lost_cnt = 0; |
| ctrl.u.tran_stream_cat.avg_queue_delay = 0; |
| ctrl.u.tran_stream_cat.avg_tran_delay = 0; |
| ctrl.u.tran_stream_cat.bin0_range = cat->bin0_range; |
| |
| for (i = 0; i < ARRAY_SIZE(ctrl.u.tran_stream_cat.bins); i++) |
| ctrl.u.tran_stream_cat.bins[i] = 0; |
| |
| action_data.cat = IEEE80211_ACTION_CAT_RM; |
| action_data.action = IEEE80211_ACTION_R_MEASUREMENT_REPORT; |
| action_data.params = &ctrl; |
| |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_ACTION, (int)&action_data); |
| |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_MUL_DIAG: |
| { |
| struct ieee80211_ie_measreq_multicast_diag *mul_diag; |
| |
| if (sizeof(struct ieee80211_ie_measreq_multicast_diag) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, |
| "mgmt", "%s", "no enough data for multicast diagnostics request field\n"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| mul_diag = (struct ieee80211_ie_measreq_multicast_diag *)frm; |
| frm += sizeof(struct ieee80211_ie_measreq_multicast_diag); |
| |
| ieee80211_send_rm_rep_multicast_diag(ni, 0, frame_token, meas_comm->token, |
| mul_diag->duration_tu, mul_diag->group_mac_addr, |
| 0, 0, 0, 0, 0); |
| |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_LCI: |
| case IEEE80211_RM_MEASTYPE_LOC_CIVIC: |
| case IEEE80211_RM_MEASTYPE_LOC_ID: |
| ieee80211_action_measurement_report_fail(ni, |
| meas_comm->type, IEEE80211_CCA_REPMODE_REFUSE, |
| frame_token, meas_comm->token); |
| break; |
| default: |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, |
| "mgmt", "%s", "unsupported type\n"); |
| vap->iv_stats.is_rx_action++; |
| break; |
| } |
| } |
| |
| /* Quantenna CCA Extension */ |
| void ieee80211_parse_qtn_measure_report(struct ieee80211_node *ni, |
| struct ieee80211_ie_measure_comm *meas_comm, |
| u_int8_t *efrm) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| struct cca_rm_rep_data *cca_report; |
| uint8_t *qtn_extra_ie; |
| uint8_t *qtn_extra_ie_end; |
| struct ieee80211_ie_qtn_scs *qtn_scs_ie; |
| |
| if (!ic->ic_scs.scs_stats_on) |
| return; |
| |
| cca_report = (struct cca_rm_rep_data*)meas_comm->data; |
| ni->ni_recent_cca_intf = (cca_report->busy_frac * IEEE80211_SCS_CCA_INTF_SCALE / IEEE80211_11K_CCA_INTF_SCALE); |
| ni->ni_recent_cca_intf_jiffies = jiffies; |
| qtn_scs_ie = (struct ieee80211_ie_qtn_scs*)(cca_report + 1); |
| if (((efrm - (uint8_t*)qtn_scs_ie) >= QTN_SCS_IE_LEN_MIN) && |
| (qtn_scs_ie->id == IEEE80211_ELEMID_VENDOR) && |
| (qtn_scs_ie->len >= QTN_SCS_IE_LEN_MIN - 2) && |
| (is_qtn_scs_oui((uint8_t*)qtn_scs_ie))) { |
| if ((qtn_scs_ie->scs_ie_type == QTN_SCS_IE_TYPE_STA_INTF_RPT) && |
| (qtn_scs_ie->len >= (QTN_SCS_IE_STA_INTF_RPT_LEN_MIN - 2))) { |
| ic->ic_sta_cc = 1; |
| ni->ni_recent_sp_fail = le32toh(qtn_scs_ie->u.cca_info.sp_fail); |
| ni->ni_recent_lp_fail = le32toh(qtn_scs_ie->u.cca_info.lp_fail); |
| ni->ni_recent_others_time = le16toh(qtn_scs_ie->u.cca_info.others_time); |
| if (ni->ni_recent_others_time > ni->ni_recent_others_time_smth) { |
| ni->ni_recent_others_time_smth = ni->ni_recent_others_time; |
| } else { |
| ni->ni_recent_others_time_smth = IEEE80211_SCS_SMOOTH( |
| ni->ni_recent_others_time_smth, |
| ni->ni_recent_others_time, |
| IEEE80211_SCS_SMTH_RBS_TIME); |
| } |
| |
| if ((qtn_scs_ie->extra_ie_len != 0) && |
| ((qtn_scs_ie->extra_ie_len + sizeof(struct ieee80211_ie_qtn_scs)) <= |
| (qtn_scs_ie->len + 2))) { |
| qtn_extra_ie = qtn_scs_ie->extra_ie; |
| qtn_extra_ie_end = qtn_scs_ie->extra_ie + qtn_scs_ie->extra_ie_len; |
| |
| while (qtn_extra_ie < qtn_extra_ie_end) { |
| ieee80211_scs_update_tdls_stats(ic, (struct ieee80211_tdls_scs_stats *)qtn_extra_ie); |
| qtn_extra_ie += sizeof(struct ieee80211_tdls_scs_stats); |
| } |
| } |
| } else if ((qtn_scs_ie->scs_ie_type == QTN_SCS_IE_TYPE_STA_DFS_RPT) && |
| (qtn_scs_ie->len >= (QTN_SCS_IE_STA_DFS_RPT_LEN_MIN - 2))) { |
| /* let AP ignore the interference from DFS report */ |
| ni->ni_recent_cca_intf = SCS_CCA_INTF_INVALID; |
| ni->ni_qtn_dfs_enabled = le16toh(qtn_scs_ie->u.dfs_info.dfs_enabled); |
| ni->ni_txpower = le16toh(qtn_scs_ie->u.dfs_info.max_txpower); |
| } else if ((qtn_scs_ie->scs_ie_type == QTN_SCS_IE_TYPE_STA_FAT_RPT) && |
| (qtn_scs_ie->len >= (QTN_SCS_IE_STA_FAT_RPT_LEN_MIN - 2))) { |
| ni->ni_recent_cca_idle = le16toh(qtn_scs_ie->u.fat_info.free_airtime); |
| } |
| } |
| |
| switch (qtn_scs_ie->scs_ie_type) { |
| case QTN_SCS_IE_TYPE_STA_INTF_RPT: |
| SCSDBG(SCSLOG_NOTICE, "CCA: SCS IE type %u: rx cca_intf %u " |
| "with busy_fraction %u report from STA 0x%x, " |
| "pmbl_error=%u %u " |
| "others_time=%u " |
| "others_time_smth=%u\n", |
| qtn_scs_ie->scs_ie_type, ni->ni_recent_cca_intf, |
| cca_report->busy_frac, ni->ni_associd, |
| ni->ni_recent_sp_fail, ni->ni_recent_lp_fail, |
| ni->ni_recent_others_time, ni->ni_recent_others_time_smth); |
| break; |
| case QTN_SCS_IE_TYPE_STA_DFS_RPT: |
| SCSDBG(SCSLOG_NOTICE, "DFS: SCS IE type %u, DFS %s, TX power %d, report from STA 0x%x\n", |
| qtn_scs_ie->scs_ie_type, |
| ni->ni_qtn_dfs_enabled ? "enabled" : "disabled", |
| ni->ni_txpower, |
| ni->ni_associd); |
| break; |
| case QTN_SCS_IE_TYPE_STA_FAT_RPT: |
| SCSDBG(SCSLOG_NOTICE, "CCA: SCS IE type %u: rx cca_intf %u " |
| "with busy_fraction %u report from STA 0x%x, " |
| "cca_idle=%d\n", |
| qtn_scs_ie->scs_ie_type, ni->ni_recent_cca_intf, |
| cca_report->busy_frac, ni->ni_associd, |
| ni->ni_recent_cca_idle); |
| break; |
| default: |
| SCSDBG(SCSLOG_NOTICE, "CCA: received cca_intf %u with busy_fraction %u report from STA 0x%x, " |
| "scs IE not present or invalid (%d)\n", |
| ni->ni_recent_cca_intf, cca_report->busy_frac, |
| ni->ni_associd, qtn_scs_ie->scs_ie_type); |
| break; |
| } |
| } |
| |
| void ieee80211_parse_measure_report(struct ieee80211_node *ni, |
| struct ieee80211_frame *wh, |
| u_int8_t category, |
| u_int8_t frame_token, |
| u_int8_t *frm, |
| u_int8_t *efrm) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_ie_measure_comm *meas_comm; |
| |
| #define QTN_MREPORT_SUBIE_LEN(dlen) \ |
| ((uint8_t)(offsetof(struct ieee80211_ie_qtn_rm_measure_sta, data) - \ |
| offsetof(struct ieee80211_ie_qtn_rm_measure_sta, qtn_ie_oui) + (dlen))) |
| |
| if (sizeof(struct ieee80211_ie_measure_comm) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "no enough data for measurement common field"); |
| /* this counter is used incorrectly throughout the module, but not currently in use */ |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| meas_comm = (struct ieee80211_ie_measure_comm*)frm; |
| frm += sizeof(*meas_comm); |
| |
| if (meas_comm->id != IEEE80211_ELEMID_MEASREP) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, |
| "mgt", "%s", "measurement report ID mismatch\n"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| |
| /* autonomous report frame should be handled seperately */ |
| if (meas_comm->token == 0) { |
| switch (meas_comm->type) { |
| case IEEE80211_CCA_MEASTYPE_BASIC: |
| { |
| struct ieee80211_ie_measrep_basic *meas_rep_basic; |
| |
| if (meas_comm->mode == 0) { |
| if (sizeof(struct ieee80211_ie_measrep_basic) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "no enough data for measurement report field"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| meas_rep_basic = (struct ieee80211_ie_measrep_basic *)(frm); |
| frm += sizeof(struct ieee80211_ie_measrep_basic); |
| ieee80211_recv_meas_basic_report(ni, meas_rep_basic); |
| } |
| break; |
| } |
| case IEEE80211_CCA_MEASTYPE_CCA: { |
| if (category == IEEE80211_ACTION_CAT_RM) |
| ieee80211_parse_qtn_measure_report(ni, meas_comm, efrm); |
| break; |
| } |
| default: |
| printk("unsupport autonomous report, type=%d\n", meas_comm->type); |
| break; |
| } |
| |
| return; |
| } |
| |
| ni->ni_meas_info.ni_meas_rep_mode = meas_comm->mode; |
| ni->ni_meas_info.ni_meas_rep_time = jiffies; |
| |
| /* normal measurement report */ |
| switch (meas_comm->type) { |
| case IEEE80211_CCA_MEASTYPE_BASIC: |
| { |
| struct ieee80211_ie_measrep_basic *meas_rep_basic; |
| |
| if (meas_comm->mode == 0) { |
| if (sizeof(struct ieee80211_ie_measrep_basic) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "no enough data for basic report field"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| meas_rep_basic = (struct ieee80211_ie_measrep_basic *)(frm); |
| frm += sizeof(struct ieee80211_ie_measrep_basic); |
| |
| ni->ni_meas_info.rep.basic = meas_rep_basic->basic_report; |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DOTH, |
| "receive MEAS Report:type=basic, report_mode=%u, channel=%d, tsf=%llu, duration=%u, data=%u\n", |
| ni->ni_meas_info.ni_meas_rep_mode, |
| meas_rep_basic->chan_num, |
| meas_rep_basic->start_tsf, |
| meas_rep_basic->duration_tu, |
| ni->ni_meas_info.rep.basic); |
| } |
| break; |
| } |
| case IEEE80211_CCA_MEASTYPE_CCA: |
| { |
| struct ieee80211_ie_measrep_cca *meas_rep_cca; |
| |
| if (meas_comm->mode == 0) { |
| if (sizeof(struct ieee80211_ie_measrep_cca) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "no enough data for cca report field"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| meas_rep_cca = (struct ieee80211_ie_measrep_cca *)(frm); |
| frm += sizeof(struct ieee80211_ie_measrep_cca); |
| |
| ni->ni_meas_info.rep.cca = meas_rep_cca->cca_report; |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DOTH, |
| "receive MEAS Report:type=cca, report_mode=%u, channel=%d, tsf=%llu, duration=%u, data=%u\n", |
| ni->ni_meas_info.ni_meas_rep_mode, |
| meas_rep_cca->chan_num, |
| meas_rep_cca->start_tsf, |
| meas_rep_cca->duration_tu, |
| ni->ni_meas_info.rep.cca); |
| } |
| break; |
| } |
| case IEEE80211_CCA_MEASTYPE_RPI: |
| { |
| struct ieee80211_ie_measrep_rpi *meas_rep_rpi; |
| |
| if (meas_comm->mode == 0) { |
| if (sizeof(struct ieee80211_ie_measrep_rpi) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "no enough data for rpi report field"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| meas_rep_rpi = (struct ieee80211_ie_measrep_rpi *)(frm); |
| frm += sizeof(struct ieee80211_ie_measrep_rpi); |
| |
| memcpy(ni->ni_meas_info.rep.rpi, meas_rep_rpi->rpi_report, sizeof(ni->ni_meas_info.rep.rpi)); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DOTH, |
| "receive MEAS Report:type=rpi, report_mode=%u, " |
| "channel=%d, tsf=%llu, duration=%u, data=%u %u %u %u %u %u %u %u\n", |
| ni->ni_meas_info.ni_meas_rep_mode, |
| meas_rep_rpi->chan_num, |
| meas_rep_rpi->start_tsf, |
| meas_rep_rpi->duration_tu, |
| ni->ni_meas_info.rep.rpi[0], |
| ni->ni_meas_info.rep.rpi[1], |
| ni->ni_meas_info.rep.rpi[2], |
| ni->ni_meas_info.rep.rpi[3], |
| ni->ni_meas_info.rep.rpi[4], |
| ni->ni_meas_info.rep.rpi[5], |
| ni->ni_meas_info.rep.rpi[6], |
| ni->ni_meas_info.rep.rpi[7]); |
| } |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_STA: |
| { |
| const int32_t sta_stats_groupid_len[] = { |
| [0] = sizeof(struct ieee80211_rm_sta_stats_group0), |
| [1] = sizeof(struct ieee80211_rm_sta_stats_group1), |
| [2 ... 9] = sizeof(struct ieee80211_rm_sta_stats_group2to9), |
| [10] = sizeof(struct ieee80211_rm_sta_stats_group10), |
| [11] = sizeof(struct ieee80211_rm_sta_stats_group11), |
| [12] = sizeof(struct ieee80211_rm_sta_stats_group12), |
| [13] = sizeof(struct ieee80211_rm_sta_stats_group13), |
| [14] = sizeof(struct ieee80211_rm_sta_stats_group14), |
| [15] = sizeof(struct ieee80211_rm_sta_stats_group15), |
| [16] = sizeof(struct ieee80211_rm_sta_stats_group16), |
| }; |
| struct ieee80211_ie_measrep_sta_stat *report = NULL; |
| int status = 0; |
| |
| if (sizeof(struct ieee80211_ie_measrep_sta_stat) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "RM", "insufficient data for %s", "sta stats report common field"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| report = (struct ieee80211_ie_measrep_sta_stat *)frm; |
| frm += sizeof(struct ieee80211_ie_measrep_sta_stat); |
| |
| /* handle with group ID used for Cisco */ |
| if (report->group_id == 221) { |
| if ((&report->data[0] + sizeof(ni->ni_rm_sta_grp221)) > efrm) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "RM", "insufficient data for %s", "group 221 STA stats"); |
| vap->iv_stats.is_rx_action++; |
| } else { |
| memcpy(&ni->ni_rm_sta_grp221, &report->data[0], sizeof(ni->ni_rm_sta_grp221)); |
| } |
| return; |
| } |
| |
| if (report->group_id >= ARRAY_SIZE(sta_stats_groupid_len)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "RM", "invalid group id %d", report->group_id); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| |
| if (sta_stats_groupid_len[report->group_id] > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "RM", "insufficient data for %s", "sta stats group result field"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| frm += sta_stats_groupid_len[report->group_id]; |
| |
| /* optional sub element */ |
| while ((frm + IEEE80211_RM_MEAS_SUBTYPE_LEN_MIN) <= efrm) { |
| switch (frm[0]) { |
| case IEEE80211_ELEMID_VENDOR: |
| { |
| struct ieee80211_ie_qtn_rm_measure_sta *qtn_comm; |
| struct ieee80211_ie_qtn_rm_sta_all *remote; |
| int i, cnt; |
| |
| if ((frm + sizeof(*qtn_comm)) > efrm) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "RM", "insufficient data for %s", "vendor subelement"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| |
| qtn_comm = (struct ieee80211_ie_qtn_rm_measure_sta*)frm; |
| if (!isqtnmrespoui(frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "RM", "%s: id %u type %u OUI %02x:%02x:%02x", |
| "specific IE incorrect", |
| qtn_comm->id, qtn_comm->type, qtn_comm->qtn_ie_oui[0], |
| qtn_comm->qtn_ie_oui[1], qtn_comm->qtn_ie_oui[2]); |
| vap->iv_stats.is_rx_action++; |
| break; |
| } |
| |
| remote = &ni->ni_qtn_rm_sta_all; /* record the remote statistics to node */ |
| if (qtn_comm->type == QTN_OUI_RM_ALL) { |
| if ((qtn_comm->data + sizeof(*remote)) > efrm || |
| qtn_comm->len != QTN_MREPORT_SUBIE_LEN(sizeof(*remote))) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "RM", "%s", "QTN all group IE bad len"); |
| vap->iv_stats.is_rx_action++; |
| break; |
| } |
| |
| memcpy(remote, qtn_comm->data, sizeof(*remote)); |
| |
| remote->tx_stats.tx_bytes = ntohll(remote->tx_stats.tx_bytes); |
| remote->tx_stats.tx_pkts = ntohl(remote->tx_stats.tx_pkts); |
| remote->tx_stats.tx_discard = ntohl(remote->tx_stats.tx_discard); |
| remote->tx_stats.tx_err = ntohl(remote->tx_stats.tx_err); |
| remote->tx_stats.tx_ucast = ntohl(remote->tx_stats.tx_ucast); |
| remote->tx_stats.tx_mcast = ntohl(remote->tx_stats.tx_mcast); |
| remote->tx_stats.tx_bcast = ntohl(remote->tx_stats.tx_bcast); |
| |
| remote->rx_stats.rx_bytes = ntohll(remote->rx_stats.rx_bytes); |
| remote->rx_stats.rx_pkts = ntohl(remote->rx_stats.rx_pkts); |
| remote->rx_stats.rx_discard = ntohl(remote->rx_stats.rx_discard); |
| remote->rx_stats.rx_err = ntohl(remote->rx_stats.rx_err); |
| remote->rx_stats.rx_ucast = ntohl(remote->rx_stats.rx_ucast); |
| remote->rx_stats.rx_mcast = ntohl(remote->rx_stats.rx_mcast); |
| remote->rx_stats.rx_bcast = ntohl(remote->rx_stats.rx_bcast); |
| |
| remote->max_queued = ntohl(remote->max_queued); |
| remote->link_quality = ntohl(remote->link_quality); |
| remote->rssi_dbm = ntohl(remote->rssi_dbm); |
| remote->bandwidth = ntohl(remote->bandwidth); |
| remote->snr = ntohl(remote->snr); |
| remote->tx_phy_rate = ntohl(remote->tx_phy_rate); |
| remote->rx_phy_rate = ntohl(remote->rx_phy_rate); |
| remote->cca = ntohl(remote->cca); |
| remote->br_ip = ntohl(remote->br_ip); |
| |
| for (i = 0; i <= RM_QTN_MAX; i++ ) { |
| ni->ni_last_update[i] = jiffies; |
| } |
| } else { |
| u_int8_t *vendor_frm; |
| uint8_t sie_type; |
| uint8_t sie_len; |
| |
| vendor_frm = (u_int8_t*)qtn_comm->data; |
| cnt = *vendor_frm++; |
| |
| if (cnt == 0) { |
| status = -EPROTONOSUPPORT; |
| } else if (cnt > (RM_QTN_CTRL_END + 1)) { |
| cnt = RM_QTN_CTRL_END + 1; |
| } |
| |
| for (i = 0; i < cnt; i++) { |
| if ((vendor_frm + IEEE80211_RM_MEAS_SUBTYPE_LEN_MIN) > efrm) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "RM", "insufficient data for %s", "QTN spcial group IE"); |
| vap->iv_stats.is_rx_action++; |
| break; |
| } |
| |
| sie_type = *vendor_frm++; |
| sie_len = *vendor_frm++; |
| |
| if ((sie_type <= RM_QTN_CTRL_END && sie_type != RM_QTN_UNKNOWN && |
| sie_len != ieee80211_meas_sta_qtn_report_subtype_len[sie_type]) || |
| (vendor_frm + sie_len) > efrm) { |
| /* Skip the whole IE in case a single bad sub-IE encountered */ |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "RM", "%s: %d bytes (must be %d)", |
| "QTN spcial group IE bad len", sie_len, |
| ieee80211_meas_sta_qtn_report_subtype_len[sie_type]); |
| vap->iv_stats.is_rx_action++; |
| break; |
| } |
| |
| switch (sie_type) { |
| case RM_QTN_TX_STATS: |
| { |
| memcpy(&remote->tx_stats, vendor_frm, sie_len); |
| remote->tx_stats.tx_bytes = ntohll(remote->tx_stats.tx_bytes); |
| remote->tx_stats.tx_pkts = ntohl(remote->tx_stats.tx_pkts); |
| remote->tx_stats.tx_discard = ntohl(remote->tx_stats.tx_discard); |
| remote->tx_stats.tx_err = ntohl(remote->tx_stats.tx_err); |
| remote->tx_stats.tx_ucast = ntohl(remote->tx_stats.tx_ucast); |
| remote->tx_stats.tx_mcast = ntohl(remote->tx_stats.tx_mcast); |
| remote->tx_stats.tx_bcast = ntohl(remote->tx_stats.tx_bcast); |
| ni->ni_last_update[RM_QTN_TX_STATS] = jiffies; |
| break; |
| } |
| case RM_QTN_RX_STATS: |
| { |
| memcpy(&remote->rx_stats, vendor_frm, sie_len); |
| remote->rx_stats.rx_bytes = ntohll(remote->rx_stats.rx_bytes); |
| remote->rx_stats.rx_pkts = ntohl(remote->rx_stats.rx_pkts); |
| remote->rx_stats.rx_discard = ntohl(remote->rx_stats.rx_discard); |
| remote->rx_stats.rx_err = ntohl(remote->rx_stats.rx_err); |
| remote->rx_stats.rx_ucast = ntohl(remote->rx_stats.rx_ucast); |
| remote->rx_stats.rx_mcast = ntohl(remote->rx_stats.rx_mcast); |
| remote->rx_stats.rx_bcast = ntohl(remote->rx_stats.rx_bcast); |
| ni->ni_last_update[RM_QTN_RX_STATS] = jiffies; |
| break; |
| } |
| case RM_QTN_MAX_QUEUED: |
| { |
| memcpy(&remote->max_queued, vendor_frm, sie_len); |
| remote->max_queued = ntohl(remote->max_queued); |
| break; |
| } |
| case RM_QTN_LINK_QUALITY: |
| { |
| memcpy(&remote->link_quality, vendor_frm, sie_len); |
| remote->link_quality = ntohl(remote->link_quality); |
| break; |
| } |
| case RM_QTN_RSSI_DBM: |
| { |
| memcpy(&remote->rssi_dbm, vendor_frm, sie_len); |
| remote->rssi_dbm = ntohl(remote->rssi_dbm); |
| break; |
| } |
| case RM_QTN_BANDWIDTH: |
| { |
| memcpy(&remote->bandwidth, vendor_frm, sie_len); |
| remote->bandwidth = ntohl(remote->bandwidth); |
| break; |
| } |
| case RM_QTN_SNR: |
| { |
| memcpy(&remote->snr, vendor_frm, sie_len); |
| remote->snr = ntohl(remote->snr); |
| break; |
| } |
| case RM_QTN_TX_PHY_RATE: |
| { |
| memcpy(&remote->tx_phy_rate, vendor_frm, sie_len); |
| remote->tx_phy_rate = ntohl(remote->tx_phy_rate); |
| break; |
| } |
| case RM_QTN_RX_PHY_RATE: |
| { |
| memcpy(&remote->rx_phy_rate, vendor_frm, sie_len); |
| remote->rx_phy_rate = ntohl(remote->rx_phy_rate); |
| break; |
| } |
| case RM_QTN_CCA: |
| { |
| memcpy(&remote->cca, vendor_frm, sie_len); |
| remote->cca = ntohl(remote->cca); |
| break; |
| } |
| case RM_QTN_BR_IP: |
| { |
| memcpy(&remote->br_ip, vendor_frm, sie_len); |
| remote->br_ip = ntohl(remote->br_ip); |
| break; |
| } |
| case RM_QTN_RSSI: |
| { |
| memcpy(&remote->rssi, vendor_frm, sie_len); |
| remote->rssi = ntohl(remote->rssi); |
| break; |
| } |
| case RM_QTN_HW_NOISE: |
| { |
| memcpy(&remote->hw_noise, vendor_frm, sie_len); |
| remote->hw_noise = ntohl(remote->hw_noise); |
| break; |
| } |
| case RM_QTN_SOC_MACADDR: |
| { |
| memcpy(&remote->soc_macaddr, vendor_frm, sie_len); |
| break; |
| } |
| case RM_QTN_SOC_IPADDR: |
| { |
| memcpy(&remote->soc_ipaddr, vendor_frm, sie_len); |
| remote->soc_ipaddr = ntohl(remote->soc_ipaddr); |
| break; |
| } |
| /* for control IE below */ |
| case RM_QTN_RESET_CNTS: |
| { |
| memcpy(&status, vendor_frm, sie_len); |
| status = ntohl(status); |
| break; |
| } |
| default: |
| /* Just skip unknown subelement types - for compatibility reasons */ |
| break; |
| } |
| vendor_frm += sie_len; |
| } |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| frm += 2 + frm[1]; |
| } |
| |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_CH_LOAD: |
| { |
| struct ieee80211_ie_measrep_chan_load *cl; |
| |
| if (meas_comm->mode == 0) { |
| if (sizeof(struct ieee80211_ie_measrep_chan_load) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "no enough data for channel load report field"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| cl = (struct ieee80211_ie_measrep_chan_load *)frm; |
| frm += sizeof(struct ieee80211_ie_measrep_chan_load); |
| |
| ni->ni_meas_info.rep.chan_load = cl->channel_load; |
| } |
| |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_NOISE: |
| { |
| struct ieee80211_ie_measrep_noise_his *nh; |
| |
| if (meas_comm->mode == 0) { |
| if (sizeof(struct ieee80211_ie_measrep_noise_his) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "no enough data for noise histogram report field"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| nh = (struct ieee80211_ie_measrep_noise_his *)frm; |
| frm += sizeof(struct ieee80211_ie_measrep_noise_his); |
| |
| ni->ni_meas_info.rep.noise_his.antenna_id = nh->antenna_id; |
| ni->ni_meas_info.rep.noise_his.anpi = nh->anpi; |
| memcpy(ni->ni_meas_info.rep.noise_his.ipi, nh->ipi, sizeof(ni->ni_meas_info.rep.noise_his.ipi)); |
| } |
| |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_BEACON: |
| { |
| struct ieee80211_ie_measrep_beacon *beacon; |
| |
| if (meas_comm->mode == 0) { |
| if (sizeof(struct ieee80211_ie_measrep_beacon) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "no enough data for beacon report response field"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| beacon = (struct ieee80211_ie_measrep_beacon *)frm; |
| frm += sizeof(struct ieee80211_ie_measrep_beacon); |
| |
| ni->ni_meas_info.rep.beacon.reported_frame_info = beacon->reported_frame_info; |
| ni->ni_meas_info.rep.beacon.rcpi = beacon->rcpi; |
| ni->ni_meas_info.rep.beacon.rsni = beacon->rsni; |
| memcpy(ni->ni_meas_info.rep.beacon.bssid, beacon->bssid, IEEE80211_ADDR_LEN); |
| ni->ni_meas_info.rep.beacon.antenna_id = beacon->antenna_id; |
| ni->ni_meas_info.rep.beacon.parent_tsf = BE_READ_4(beacon->parent_tsf); |
| } |
| |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_FRAME: |
| { |
| if (meas_comm->mode == 0) { |
| if (sizeof(struct ieee80211_ie_measrep_frame) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "no enough data for frame report response field"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| frm += sizeof(struct ieee80211_ie_measrep_frame); |
| |
| ni->ni_meas_info.rep.frame_count.sub_ele_flag = 0; |
| if ((efrm - frm) >= sizeof(struct ieee80211_subie_section_frame_entry)) { |
| struct ieee80211_subie_section_frame_entry *entry; |
| |
| entry = (struct ieee80211_subie_section_frame_entry *)frm; |
| frm += sizeof(struct ieee80211_subie_section_frame_entry); |
| ni->ni_meas_info.rep.frame_count.sub_ele_flag = 1; |
| memcpy(ni->ni_meas_info.rep.frame_count.ta, entry->transmit_address, IEEE80211_ADDR_LEN); |
| memcpy(ni->ni_meas_info.rep.frame_count.bssid, entry->bssid, IEEE80211_ADDR_LEN); |
| ni->ni_meas_info.rep.frame_count.phy_type = entry->phy_type; |
| ni->ni_meas_info.rep.frame_count.avg_rcpi = entry->avg_rcpi; |
| ni->ni_meas_info.rep.frame_count.last_rsni = entry->last_rsni; |
| ni->ni_meas_info.rep.frame_count.last_rcpi = entry->last_rcpi; |
| ni->ni_meas_info.rep.frame_count.antenna_id = entry->anntenna_id; |
| ni->ni_meas_info.rep.frame_count.frame_count = ntohs(entry->frame_cnt); |
| } |
| } |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_CATEGORY: |
| { |
| if (meas_comm->mode == 0) { |
| struct ieee80211_ie_measrep_trans_stream_cat *cat; |
| |
| if (sizeof(struct ieee80211_ie_measrep_trans_stream_cat) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "no enough data for transmit stream category response field"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| cat = (struct ieee80211_ie_measrep_trans_stream_cat *)frm; |
| frm += sizeof(struct ieee80211_ie_measrep_trans_stream_cat); |
| |
| ni->ni_meas_info.rep.tran_stream_cat.reason = cat->reason; |
| ni->ni_meas_info.rep.tran_stream_cat.tran_msdu_cnt = ntohl(cat->tran_msdu_cnt); |
| ni->ni_meas_info.rep.tran_stream_cat.msdu_discard_cnt = ntohl(cat->msdu_discarded_cnt); |
| ni->ni_meas_info.rep.tran_stream_cat.msdu_fail_cnt = ntohl(cat->msdu_failed_cnt); |
| ni->ni_meas_info.rep.tran_stream_cat.msdu_mul_retry_cnt = ntohl(cat->msdu_mul_retry_cnt); |
| ni->ni_meas_info.rep.tran_stream_cat.qos_lost_cnt= ntohl(cat->qos_cf_lost_cnt); |
| ni->ni_meas_info.rep.tran_stream_cat.avg_queue_delay= ntohl(cat->avg_queue_delay); |
| ni->ni_meas_info.rep.tran_stream_cat.avg_tran_delay= ntohl(cat->avg_trans_delay); |
| ni->ni_meas_info.rep.tran_stream_cat.bin0_range= cat->bin0_range; |
| ni->ni_meas_info.rep.tran_stream_cat.bins[0]= ntohl(cat->bin0); |
| ni->ni_meas_info.rep.tran_stream_cat.bins[1]= ntohl(cat->bin1); |
| ni->ni_meas_info.rep.tran_stream_cat.bins[2]= ntohl(cat->bin2); |
| ni->ni_meas_info.rep.tran_stream_cat.bins[3]= ntohl(cat->bin3); |
| ni->ni_meas_info.rep.tran_stream_cat.bins[4]= ntohl(cat->bin4); |
| ni->ni_meas_info.rep.tran_stream_cat.bins[5]= ntohl(cat->bin5); |
| } |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_MUL_DIAG: |
| { |
| if (meas_comm->mode == 0) { |
| struct ieee80211_ie_measrep_multicast_diag *mul_diag; |
| |
| if (sizeof(struct ieee80211_ie_measrep_multicast_diag) > (efrm - frm)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "no enough data for multicast diagnostics response field"); |
| vap->iv_stats.is_rx_action++; |
| return; |
| } |
| mul_diag = (struct ieee80211_ie_measrep_multicast_diag *)frm; |
| frm += sizeof(struct ieee80211_ie_measrep_multicast_diag); |
| |
| ni->ni_meas_info.rep.multicast_diag.reason = mul_diag->reason; |
| ni->ni_meas_info.rep.multicast_diag.mul_rec_msdu_cnt = ntohl(mul_diag->mul_rx_msdu_cnt); |
| ni->ni_meas_info.rep.multicast_diag.first_seq_num = ntohs(mul_diag->first_seq_num); |
| ni->ni_meas_info.rep.multicast_diag.last_seq_num = ntohs(mul_diag->last_seq_num); |
| ni->ni_meas_info.rep.multicast_diag.mul_rate= ntohs(mul_diag->mul_rate); |
| } |
| |
| break; |
| } |
| case IEEE80211_RM_MEASTYPE_LCI: |
| case IEEE80211_RM_MEASTYPE_LOC_CIVIC: |
| case IEEE80211_RM_MEASTYPE_LOC_ID: |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "unsupported measurement frame, drop it"); |
| vap->iv_stats.is_rx_action++; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void ieee80211_recv_action_measure_11h(struct ieee80211_node *ni, |
| struct ieee80211_action *ia, |
| struct ieee80211_frame *wh, |
| u_int8_t *efrm) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| u_int8_t *meas_ie_frm; |
| struct ieee80211_action_sm_measurement_header *sm_header; |
| |
| if ((efrm - (u_int8_t *)ia) < sizeof(struct ieee80211_action_sm_measurement_header)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "11h measurement frame header too small"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| sm_header = (struct ieee80211_action_sm_measurement_header *)ia; |
| meas_ie_frm = (u_int8_t *)(sm_header + 1); |
| |
| if (sm_header->ia_action == IEEE80211_ACTION_S_MEASUREMENT_REQUEST) { |
| while (meas_ie_frm < efrm) { |
| ieee80211_parse_measure_request(ni, |
| wh, |
| sm_header->ia_category, |
| sm_header->am_token, |
| meas_ie_frm, |
| meas_ie_frm + meas_ie_frm[1] + 2); |
| meas_ie_frm += meas_ie_frm[1] + 2; |
| } |
| } else { /* 11h measurement report */ |
| while (meas_ie_frm < efrm) { |
| ieee80211_parse_measure_report(ni, |
| wh, |
| sm_header->ia_category, |
| sm_header->am_token, |
| meas_ie_frm, |
| meas_ie_frm + meas_ie_frm[1] + 2); |
| meas_ie_frm += meas_ie_frm[1] + 2; |
| } |
| |
| ieee80211_ppqueue_remove_with_response(&ni->ni_vap->iv_ppqueue, |
| ni, |
| ia->ia_category, |
| ia->ia_action, |
| sm_header->am_token); |
| } |
| } |
| |
| void ieee80211_recv_action_measure_11k(struct ieee80211_node *ni, |
| struct ieee80211_action *ia, |
| struct ieee80211_frame *wh, |
| u_int8_t *efrm) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| u_int8_t *meas_ie_frm; |
| struct ieee80211_action_radio_measure_request *rm_request; |
| struct ieee80211_action_radio_measure_report *rm_report; |
| |
| if (ia->ia_action == IEEE80211_ACTION_R_MEASUREMENT_REQUEST) { |
| if ((efrm - (u_int8_t *)ia) < sizeof(struct ieee80211_action_radio_measure_request)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "11k measurement request frame header too small"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| rm_request = (struct ieee80211_action_radio_measure_request *)ia; |
| meas_ie_frm = (u_int8_t *)rm_request->am_data; |
| |
| /* repeat one time */ |
| while (meas_ie_frm < efrm) { |
| ieee80211_parse_measure_request(ni, |
| wh, |
| ia->ia_category, |
| rm_request->am_token, |
| meas_ie_frm, |
| meas_ie_frm + meas_ie_frm[1] + 2); |
| meas_ie_frm += meas_ie_frm[1] + 2; |
| } |
| } else { |
| if ((efrm - (u_int8_t *)ia) < sizeof(struct ieee80211_action_radio_measure_report)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "11k measurement report frame header too small"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| rm_report = (struct ieee80211_action_radio_measure_report *)ia; |
| meas_ie_frm = (u_int8_t *)rm_report->am_data; |
| |
| while (meas_ie_frm < efrm) { |
| ieee80211_parse_measure_report(ni, |
| wh, |
| ia->ia_category, |
| rm_report->am_token, |
| meas_ie_frm, |
| meas_ie_frm + meas_ie_frm[1] + 2); |
| meas_ie_frm += meas_ie_frm[1] + 2; |
| } |
| ieee80211_ppqueue_remove_with_response(&ni->ni_vap->iv_ppqueue, |
| ni, |
| ia->ia_category, |
| ia->ia_action, |
| rm_report->am_token); |
| } |
| } |
| |
| void ieee80211_recv_action_link_measure_request(struct ieee80211_node *ni, |
| struct ieee80211_action *ia, |
| struct ieee80211_frame *wh, |
| u_int8_t *efrm) |
| { |
| struct ieee80211_action_rm_link_measure_request *request; |
| struct ieee80211_action_data action_data; |
| struct ieee80211_link_measure_report report; |
| struct ieee80211vap *vap = ni->ni_vap; |
| |
| if ((efrm - (u_int8_t *)ia) < sizeof(struct ieee80211_action_rm_link_measure_request)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "link measurement request too small"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| request = (struct ieee80211_action_rm_link_measure_request *)ia; |
| |
| report.token = request->token; |
| report.tpc_report.tx_power = ni->ni_ic->ic_get_local_txpow(ni->ni_ic); |
| ni->ni_ic->ic_get_local_link_margin(ni, &report.tpc_report.link_margin); |
| report.recv_antenna_id = 255; |
| report.tran_antenna_id = 255; |
| report.rcpi = 0; |
| report.rsni = 0; |
| |
| action_data.cat = IEEE80211_ACTION_CAT_RM; |
| action_data.action = IEEE80211_ACTION_R_LINKMEASURE_REPORT; |
| action_data.params = &report; |
| |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_ACTION, (int)&action_data); |
| } |
| |
| void ieee80211_recv_action_link_measure_report(struct ieee80211_node *ni, |
| struct ieee80211_action *ia, |
| struct ieee80211_frame *wh, |
| u_int8_t *efrm) |
| { |
| struct ieee80211_action_rm_link_measure_report *report; |
| struct ieee80211vap *vap = ni->ni_vap; |
| |
| if ((efrm - (u_int8_t *)ia) < sizeof(struct ieee80211_action_rm_link_measure_report)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "link measurement report too small"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| report = (struct ieee80211_action_rm_link_measure_report *)ia; |
| |
| ni->ni_lm.tpc_report.link_margin = report->tpc_report.link_margin; |
| ni->ni_lm.tpc_report.tx_power = report->tpc_report.tran_power; |
| ni->ni_lm.recv_antenna_id = report->recv_antenna_id; |
| ni->ni_lm.tran_antenna_id = report->tran_antenna_id; |
| ni->ni_lm.rcpi = report->rcpi; |
| ni->ni_lm.rsni = report->rsni; |
| |
| ieee80211_ppqueue_remove_with_response(&ni->ni_vap->iv_ppqueue, |
| ni, |
| ia->ia_category, |
| ia->ia_action, |
| report->token); |
| } |
| |
| struct ieee80211_nbr_entry { |
| struct ieee80211_neighbor_report_request_item item; |
| uint8_t channel_util; |
| TAILQ_ENTRY(ieee80211_nbr_entry) next; |
| }; |
| |
| struct neighbor_list_head { |
| struct ieee80211vap *vap; |
| TAILQ_HEAD(, ieee80211_nbr_entry) list; |
| }; |
| |
| static int |
| neighbor_scanentry_cb(void *arg, const struct ieee80211_scan_entry *se) |
| { |
| struct ieee80211_nbr_entry *entry = NULL; |
| struct neighbor_list_head *list = (struct neighbor_list_head *)arg; |
| struct ieee80211vap *vap = list->vap; |
| struct ieee80211_scan_ssid *vssid = &vap->iv_des_ssid[0]; |
| |
| if (!memcmp(&se->se_ssid[2], vssid->ssid, IEEE80211_NWID_LEN) ) { |
| entry = (struct ieee80211_nbr_entry *)kmalloc(sizeof(*entry), GFP_ATOMIC); |
| if (entry != NULL) { |
| memcpy(entry->item.bssid, se->se_bssid, IEEE80211_ADDR_LEN); |
| entry->item.channel = se->se_chan->ic_ieee; |
| entry->item.phy_type = vap->iv_ic->ic_phytype; |
| entry->item.operating_class = 0; |
| entry->item.bssid_info = (BSSID_INFO_AP_UNKNOWN |
| | BSSID_INFO_SECURITY_COPY /* not sure */ |
| | BSSID_INFO_KEY_SCOPE_COPY); /* not sure */ |
| |
| if (se->se_capinfo & IEEE80211_CAPINFO_SPECTRUM_MGMT) |
| entry->item.bssid_info |= BSSID_INFO_CAP_SPECTRUM_MANAGEMENT; |
| if (se->se_capinfo & IEEE80211_CAPINFO_WME) |
| entry->item.bssid_info |= BSSID_INFO_CAP_QOS; |
| if (se->se_capinfo & IEEE80211_CAPINFO_APSD) |
| entry->item.bssid_info |= BSSID_INFO_CAP_APSD; |
| if (se->se_capinfo & IEEE80211_CAPINFO_RM) |
| entry->item.bssid_info |= BSSID_INFO_CAP_RADIO_MEASUREMENT; |
| if (se->se_capinfo & IEEE80211_CAPINFO_DELAYED_BA) |
| entry->item.bssid_info |= BSSID_INFO_CAP_DELAYED_BA; |
| if (se->se_capinfo & IEEE80211_CAPINFO_IMMEDIATE_BA) |
| entry->item.bssid_info |= BSSID_INFO_CAP_IMMEDIATE_BA; |
| if (se->se_md_ie) |
| entry->item.bssid_info |= BSSID_INFO_MOBILITY_DOMAIN; |
| if (se->se_htcap_ie) |
| entry->item.bssid_info |= BSSID_INFO_HIGH_THROUGHPUT; |
| if (se->se_vhtcap_ie) |
| entry->item.bssid_info |= BSSID_INFO_VERY_HIGH_THROUGHPUT; |
| if (se->se_md_ie) { |
| struct ieee80211_md_ie *md = (struct ieee80211_md_ie *)se->se_md_ie; |
| if (vap->iv_mdid == md->md_info) |
| entry->item.bssid_info |= BSSID_INFO_MOBILITY_DOMAIN; |
| } |
| entry->channel_util = se->se_bss_load_ie ? *(se->se_bss_load_ie + 4) : 0; |
| if (se->se_bss_load_ie) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, |
| "sta count %d chutil %d bssid info %x bssid: %pM\n", |
| *(se->se_bss_load_ie + 2), *(se->se_bss_load_ie + 4), |
| entry->item.bssid_info, entry->item.bssid); |
| } |
| TAILQ_INSERT_TAIL(&list->list, entry, next); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void |
| ieee80211_parse_scan_cache_and_generate_neighbor_report_list(struct ieee80211com *ic, |
| struct neighbor_list_head *list) |
| { |
| ieee80211_scan_iterate(ic, neighbor_scanentry_cb, list); |
| } |
| |
| int |
| ieee80211_create_neighbor_reports(struct ieee80211_node *ni, |
| struct ieee80211_neighbor_report_request_item **item_table, |
| int num_items, |
| int sort_on_chan_util) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| struct neighbor_list_head list; |
| struct ieee80211_nbr_entry *entry = NULL; |
| struct ieee80211_nbr_entry *tentry = NULL; |
| uint8_t bss_num = 0; |
| int i; |
| struct ieee80211_neighbor_report_request_item *cache = NULL; |
| struct ieee80211_neighbor_report_request_item *tcache = NULL; |
| |
| *item_table = NULL; |
| cache = kmalloc(sizeof(struct ieee80211_neighbor_report_request_item) * num_items, |
| GFP_ATOMIC); |
| if (!cache ) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, "Failed to alloc size %d\n", num_items); |
| return 0; |
| } |
| TAILQ_INIT(&list.list); |
| list.vap = vap; |
| ieee80211_parse_scan_cache_and_generate_neighbor_report_list(ic, &list); |
| |
| bss_num = 0; |
| for ( i = 0; i < num_items; i++ ) { |
| uint8_t util = 255; |
| tentry = NULL; |
| /* get entry based on least channel utilization */ |
| TAILQ_FOREACH(entry, &list.list, next) { |
| if (entry->channel_util < util) { |
| tentry = entry; |
| util = entry->channel_util; |
| } |
| } |
| if (!tentry) |
| break; |
| TAILQ_REMOVE(&list.list, tentry, next); |
| if (bss_num < num_items) { |
| tcache = cache + bss_num; |
| memcpy(tcache->bssid, tentry->item.bssid, IEEE80211_ADDR_LEN); |
| tcache->bssid_info = htonl(tentry->item.bssid_info); |
| tcache->channel = tentry->item.channel; |
| tcache->operating_class = tentry->item.operating_class; |
| tcache->phy_type = tentry->item.phy_type; |
| bss_num++; |
| } |
| } |
| *item_table = cache; |
| |
| entry = TAILQ_FIRST(&list.list); |
| while (entry != NULL) { |
| tentry = TAILQ_NEXT(entry, next); |
| kfree(entry); |
| entry = tentry; |
| } |
| |
| return bss_num; |
| } |
| |
| #define NEIGH_REPORTS_MAX 32 |
| |
| void |
| ieee80211_beacon_request_callback_success(void *ctx) |
| { |
| struct ieee80211_node *ni = ctx; |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_neighbor_report_request_item *item_cache, *item_table[NEIGH_REPORTS_MAX]; |
| int num_of_items = 0; |
| int i; |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, "beacon report received for sta: %pM\n", |
| ni->ni_macaddr); |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, "beacon report rcpi %d rsni BSSID: %pM\n", |
| ni->ni_meas_info.rep.beacon.rcpi, |
| ni->ni_meas_info.rep.beacon.rsni, |
| ni->ni_meas_info.rep.beacon.bssid ); |
| num_of_items = ieee80211_create_neighbor_reports(ni, &item_cache, NEIGH_REPORTS_MAX, 1); |
| if (num_of_items > 0) { |
| for (i = 0; i < num_of_items; i++) { |
| /* mark reachability flag mathching becon report entry */ |
| if (!memcmp(ni->ni_meas_info.rep.beacon.bssid, item_cache->bssid, |
| IEEE80211_ADDR_LEN)) |
| item_cache->bssid_info |= BSSID_INFO_AP_REACHABLE; |
| item_table[i] = item_cache++; |
| } |
| } |
| ieee80211_send_neighbor_report_response(ni, ni->pending_beacon_req_token, num_of_items, |
| item_table); |
| if (item_cache) { |
| kfree(item_cache); |
| } |
| } |
| |
| void |
| ieee80211_beacon_request_callback_fail(void *ctx) |
| { |
| struct ieee80211_node *ni = ctx; |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_neighbor_report_request_item *item_cache, *item_table[NEIGH_REPORTS_MAX]; |
| int num_of_items = 0; |
| int i; |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, "beacon report failed for sta: %pM\n", |
| ni->ni_macaddr); |
| num_of_items = ieee80211_create_neighbor_reports(ni, &item_cache, NEIGH_REPORTS_MAX, 1); |
| if (num_of_items > 0) { |
| for (i = 0; i < num_of_items; i++) |
| item_table[i] = item_cache++; |
| } |
| ieee80211_send_neighbor_report_response(ni, ni->pending_beacon_req_token, num_of_items, |
| item_table); |
| if (item_cache) { |
| kfree(item_cache); |
| } |
| } |
| |
| #define BEACON_MEASURE_MODE_PASSIVE 0 |
| #define BEACON_MEASURE_MODE_ACTIVE 1 |
| #define BEACON_MEASURE_MODE_TABLE 2 |
| #define BEACON_MEASURE_TIME 20 /* 20 msec */ |
| #define BEACON_MEASURE_REQUEST_TIMEOUT 40 /* 40 msec */ |
| |
| void |
| ieee80211_create_and_send_neighbor_report_using_beacon_report(struct ieee80211_node *ni, |
| uint8_t token) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_scan_ssid *vssid = &vap->iv_des_ssid[0]; |
| |
| ni->pending_beacon_req_token = token; |
| /* send beacon report request - beacon table mode */ |
| ieee80211_send_rm_req_beacon(ni, 0, 0, BEACON_MEASURE_TIME, BEACON_MEASURE_MODE_TABLE, NULL, |
| &vssid->ssid[0], vssid->len, BEACON_MEASURE_REQUEST_TIMEOUT, |
| ieee80211_beacon_request_callback_success, |
| ieee80211_beacon_request_callback_fail); |
| |
| } |
| |
| void |
| ieee80211_recv_action_neighbor_report_request(struct ieee80211_node *ni, |
| struct ieee80211_action *ia, |
| struct ieee80211_frame *wh, |
| u_int8_t *efrm) |
| { |
| struct ieee80211_action_rm_neighbor_report_request *request; |
| struct ieee80211vap *vap = ni->ni_vap; |
| |
| if (vap->iv_opmode != IEEE80211_M_HOSTAP) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "wrong mode"); |
| vap->iv_stats.is_rx_mgtdiscard++; |
| return; |
| } |
| |
| if ((efrm - (u_int8_t *)ia) < sizeof(struct ieee80211_action_rm_neighbor_report_request)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "neighbor report request too small"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| request = (struct ieee80211_action_rm_neighbor_report_request *)ia; |
| |
| if (ni->ni_rrm_capability & IEEE80211_NODE_BEACON_TABLE_REPORT_CAPABLE) { |
| ieee80211_create_and_send_neighbor_report_using_beacon_report(ni, request->token); |
| } else { |
| struct ieee80211_neighbor_report_request_item *item_cache = NULL; |
| struct ieee80211_neighbor_report_request_item *item_table[NEIGH_REPORTS_MAX]; |
| int num_of_items = 0; |
| int i; |
| |
| num_of_items = ieee80211_create_neighbor_reports(ni, &item_cache, |
| NEIGH_REPORTS_MAX, 1); |
| if (num_of_items > 0) { |
| for (i = 0; i < num_of_items; i++) |
| item_table[i] = item_cache++; |
| } |
| ieee80211_send_neighbor_report_response(ni, request->token, |
| num_of_items, item_table); |
| if (item_cache) { |
| kfree(item_cache); |
| } |
| } |
| } |
| |
| void ieee80211_recv_action_neighbor_report_response(struct ieee80211_node *ni, |
| struct ieee80211_action *ia, |
| struct ieee80211_frame *wh, |
| u_int8_t *efrm) |
| { |
| struct ieee80211_action_rm_neighbor_report_response *response; |
| struct ieee80211_ie_neighbor_report *ie; |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_neighbor_report_item *item; |
| u_int8_t *frm = (u_int8_t *)ia; |
| u_int8_t i; |
| |
| if ((efrm - frm) < sizeof(struct ieee80211_action_rm_neighbor_report_response)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "neighbor report response too small"); |
| vap->iv_stats.is_rx_elem_toosmall++; |
| return; |
| } |
| response = (struct ieee80211_action_rm_neighbor_report_response *)frm; |
| frm += sizeof(struct ieee80211_action_rm_neighbor_report_response); |
| |
| if (ni->ni_neighbor.report_count != 0) { |
| for (i = 0; i < ni->ni_neighbor.report_count; i++) { |
| kfree(ni->ni_neighbor.item_table[i]); |
| ni->ni_neighbor.item_table[i] = NULL; |
| } |
| ni->ni_neighbor.report_count = 0; |
| } |
| |
| while ((frm < efrm) && (ni->ni_neighbor.report_count < IEEE80211_RM_NEIGHBOR_REPORT_ITEM_MAX)) { |
| ie = (struct ieee80211_ie_neighbor_report *)frm; |
| if ((efrm - frm) < (ie->len + 2)) |
| break; |
| |
| item = (struct ieee80211_neighbor_report_item *)kmalloc(sizeof(*item), GFP_ATOMIC); |
| if (item == NULL) |
| break; |
| |
| memcpy(item->bssid, ie->bssid, IEEE80211_ADDR_LEN); |
| item->bssid_info = ntohl(ie->bssid_info); |
| item->operating_class = ie->operating_class; |
| item->channel = ie->channel; |
| item->phy_type = ie->phy_type; |
| ni->ni_neighbor.item_table[ni->ni_neighbor.report_count++] = item; |
| frm += ie->len + 2; |
| } |
| |
| ieee80211_ppqueue_remove_with_response(&ni->ni_vap->iv_ppqueue, |
| ni, |
| ia->ia_category, |
| ia->ia_action, |
| response->token); |
| } |
| |
| void ieee80211_recv_action_11k(struct ieee80211_node *ni, |
| struct ieee80211_action *ia, |
| struct ieee80211_frame *wh, |
| u_int8_t *efrm) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| |
| switch (ia->ia_action) { |
| case IEEE80211_ACTION_R_MEASUREMENT_REQUEST: |
| case IEEE80211_ACTION_R_MEASUREMENT_REPORT: |
| ieee80211_recv_action_measure_11k(ni, ia, wh, efrm); |
| break; |
| case IEEE80211_ACTION_R_LINKMEASURE_REQUEST: |
| ieee80211_recv_action_link_measure_request(ni, ia, wh, efrm); |
| break; |
| case IEEE80211_ACTION_R_LINKMEASURE_REPORT: |
| ieee80211_recv_action_link_measure_report(ni, ia, wh, efrm); |
| break; |
| case IEEE80211_ACTION_R_NEIGHBOR_REQUEST: |
| ieee80211_recv_action_neighbor_report_request(ni, ia, wh, efrm); |
| break; |
| case IEEE80211_ACTION_R_NEIGHBOR_REPORT: |
| ieee80211_recv_action_neighbor_report_response(ni, ia, wh, efrm); |
| break; |
| default: |
| vap->iv_stats.is_rx_mgtdiscard++; |
| break; |
| } |
| } |
| |
| /* |
| * Check whether non-ERP protection required or not |
| * @scan OBSS beacon data |
| * @return TRUE if non-ERP protection is required, FALSE if not required |
| */ |
| static __inline int is_non_erp_prot_required(struct ieee80211_scanparams *scan) |
| { |
| int i; |
| |
| #define IS_B_RATE(x) ( (((x) & ~0x80) == 0x02) || (((x) & ~0x80) == 0x04) || \ |
| (((x) & ~0x80) == 0x0b) || (((x) & ~0x80) == 0x16) ) |
| |
| /* If non erp sta present in obss */ |
| if (scan->erp & IEEE80211_ERP_NON_ERP_PRESENT) { |
| return 1; |
| } |
| |
| /* If beacon is from b-only ap where supported rates are |
| * 1, 2, 5.5 and 11 Mbps |
| */ |
| if (!scan->xrates && (scan->rates[1] <= 4)) { |
| /* Check all the rates in the supported rate IE. If |
| * any of the rate is not the B rate, then return 0, |
| * otherwise return 1 |
| */ |
| for (i = 0; i < scan->rates[1]; i++) { |
| if (!IS_B_RATE(scan->rates[i+2])) break; |
| } |
| if (i == scan->rates[1]) { |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Sets the station's VHT capability based on received assoc resp frame |
| * If peer AP is non-VHT TX AMSDU will be disabled in station |
| */ |
| static void |
| ieee80211_input_sta_vht_set(struct ieee80211_node *ni, |
| struct ieee80211vap *vap, uint8_t *vhtcap, |
| uint8_t *vhtop, int vht_is_allowed) |
| { |
| struct ieee80211com *ic = vap->iv_ic; |
| /* 802.11ac */ |
| if (vhtcap && IS_IEEE80211_DUALBAND_VHT_ENABLED(ic) && vht_is_allowed) { |
| ni->ni_flags |= IEEE80211_NODE_VHT; |
| ieee80211_parse_vhtcap(ni, vhtcap); |
| |
| if (vhtop) |
| ieee80211_parse_vhtop(ni, vhtop); |
| |
| ieee80211_param_to_qdrv(vap, IEEE80211_PARAM_TX_AMSDU, QTN_TX_AMSDU_ADAPTIVE, NULL, 0); |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "VHT Enabled: ni_flags = 0x%04x\n", |
| ni->ni_flags); |
| } else { |
| ni->ni_flags &= ~IEEE80211_NODE_VHT; |
| /* WAR: Livebox AP is stricter in handling TX AMSDU packets */ |
| if(ic->ic_flags_qtn & QTN_NODE_11N_TXAMSDU_OFF) |
| ieee80211_param_to_qdrv(vap, IEEE80211_PARAM_TX_AMSDU, QTN_TX_AMSDU_DISABLED, NULL, 0); |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "VHT Disabled: ni_flags = 0x%04x\n", |
| ni->ni_flags); |
| } |
| } |
| |
| static int is_assoc_limit_reached(struct ieee80211com *ic, struct ieee80211vap *vap) |
| { |
| int reserved = 0; |
| int grp = vap->iv_ssid_group; |
| int i; |
| int r = 0; |
| |
| if ((ic->ic_sta_assoc - ic->ic_wds_links) >= ic->ic_sta_assoc_limit |
| || (ic->ic_ssid_grp[grp].assocs >= ic->ic_ssid_grp[grp].limit)) { |
| return 1; |
| } |
| |
| /* Get the total reservation count */ |
| for (i = 0; i < IEEE80211_MAX_BSS_GROUP; i++) { |
| if (i == grp) { |
| continue; |
| } |
| |
| r = ic->ic_ssid_grp[i].reserve - ic->ic_ssid_grp[i].assocs; |
| if (r > 0) { |
| reserved += r; |
| } |
| } |
| |
| if ((ic->ic_sta_assoc - ic->ic_wds_links) >= (ic->ic_sta_assoc_limit - reserved)) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * This function is used to verify the HESSID and Access Network Type. |
| * Probe Response is sent only if these parameters are matched or is Wildcard |
| **/ |
| static int |
| ieee80211_verify_interworking(struct ieee80211vap *vap, u_int8_t *interw) |
| { |
| struct ieee80211_ie *ie = (struct ieee80211_ie *)interw; |
| u_int8_t interworking_len = ie->len; |
| const u_int8_t *hessid; |
| u_int8_t an_type; /* Access Network Type */ |
| |
| /* |
| * Interworking Element |
| * El.ID | Length | AccessNetworkOpt | VenueInfo | HESSID |
| * 1Byte | 1Byte | 1 Byte | 2B (Optional) | 6B (Optional) |
| */ |
| |
| #define INTERWORKING_ANT_WILDCARD 15 |
| if (interworking_len >= 1) { |
| an_type = ie->info[0] & 0x0f; |
| if (an_type != INTERWORKING_ANT_WILDCARD && |
| an_type != vap->interw_info.an_type) { |
| return -1; |
| } |
| } |
| |
| if (interworking_len == 7 || interworking_len == 9) { |
| if (interworking_len == 7) |
| hessid = &ie->info[1]; |
| else |
| hessid = &ie->info[3]; |
| |
| if (!IEEE80211_ADDR_NULL(vap->interw_info.hessid)) { |
| if (!IEEE80211_ADDR_BCAST(hessid) && |
| !IEEE80211_ADDR_EQ(hessid, vap->interw_info.hessid)) { |
| return -1; |
| } |
| } else if (!IEEE80211_ADDR_EQ(hessid, vap->iv_bss->ni_bssid) && |
| !IEEE80211_ADDR_BCAST(hessid)) { |
| return -1; |
| } |
| } |
| #undef INTERWORKING_ANT_WILDCARD |
| |
| return 0; |
| } |
| |
| static int ieee80211_input_mac_reserved(struct ieee80211vap *vap, struct ieee80211com *ic, |
| struct ieee80211_node *ni, struct ieee80211_frame *wh) |
| { |
| if (ic->ic_mac_reserved(wh->i_addr2)) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_AUTH, |
| "%s: reject auth req from reserved mac %pM\n", __func__, |
| wh->i_addr2); |
| ieee80211_send_error(ni, wh->i_addr2, |
| IEEE80211_FC0_SUBTYPE_DEAUTH, |
| IEEE80211_REASON_UNSPECIFIED); |
| return 1; |
| } |
| |
| return 0; |
| } |
| static void |
| ieee80211_extdr_cac_check(struct ieee80211com *ic, struct ieee80211vap *vap, struct ieee80211_frame *wh) |
| { |
| struct ieee80211vap *tmp_vap; |
| |
| if (IEEE80211_VAP_WDS_IS_RBS(vap) || IEEE80211_COM_WDS_IS_RBS(ic)) { |
| if (IEEE80211_ADDR_EQ(ic->ic_extender_mbs_bssid, wh->i_addr2)) |
| ic->ic_complete_cac(); |
| } else if (ieee80211_is_repeater(ic)) { |
| tmp_vap = TAILQ_FIRST(&ic->ic_vaps); |
| if (!tmp_vap) |
| return; |
| |
| if (tmp_vap->iv_state == IEEE80211_S_RUN || |
| tmp_vap->iv_state == IEEE80211_S_AUTH || |
| tmp_vap->iv_state == IEEE80211_S_ASSOC) { |
| if (IEEE80211_ADDR_EQ(wh->i_addr2, tmp_vap->iv_bss->ni_macaddr)) |
| ic->ic_complete_cac(); |
| } |
| } |
| } |
| /* |
| * This function is used to check VHT and HT capabilites presence. |
| * returns 1 if VHT or HT capablities are present. |
| * returns 0 if VHT or HT capablities are not present. |
| **/ |
| uint8_t |
| ieee80211_phy_mode_allowed(struct ieee80211vap *vap, uint8_t *vhtcap, uint8_t *htcap) |
| { |
| |
| uint8_t retVal = 0; |
| |
| switch (vap->iv_11ac_and_11n_flag) { |
| case IEEE80211_11AC_ONLY: |
| if (vhtcap != NULL) { |
| retVal = 1; |
| } |
| break; |
| case IEEE80211_11N_ONLY: |
| if (htcap != NULL) { |
| retVal = 1; |
| } |
| break; |
| default: |
| retVal = 1; |
| break; |
| } |
| |
| return retVal; |
| } |
| |
| /* |
| * In case ocac_rx_state is either in BACKOFF or ONGOING state and has not been updated for atleast |
| * 1 sec, we should reset it. |
| * |
| * One reason could be that we are no longer able to hear the AP that sent that beacon. We may or |
| * may not hear again from that AP and hence we should forget about that AP and hence reset |
| * ocac_rx_state |
| */ |
| static void iee80211_check_ocac_rx_state(struct ieee80211com *ic) |
| { |
| uint64_t delta, now = jiffies; |
| |
| if (ic->ic_opmode != IEEE80211_M_HOSTAP) |
| return; |
| |
| spin_lock(&ic->ic_ocac.ocac_lock); |
| |
| if (ic->ic_ocac.ocac_rx_state.state == OCAC_STATE_NONE) { |
| spin_unlock(&ic->ic_ocac.ocac_lock); |
| return; |
| } |
| |
| /* To handle (one time) overflow */ |
| if (now >= ic->ic_ocac.ocac_rx_state.timestamp) |
| delta = now - ic->ic_ocac.ocac_rx_state.timestamp; |
| else |
| delta = ic->ic_ocac.ocac_rx_state.timestamp - now; |
| |
| if (delta >= HZ) |
| memset(&ic->ic_ocac.ocac_rx_state, 0, sizeof(ic->ic_ocac.ocac_rx_state)); |
| |
| spin_unlock(&ic->ic_ocac.ocac_lock); |
| } |
| |
| /* |
| * Used to save the OCAC State IE from a received beacon frame, if relevant |
| * |
| * Should be called only if we are an AP |
| * |
| * ocac_rx_state is reset: |
| * - on channel change |
| * - when we are in BACKOFF or ONGOING state and we do not receive a beacon that updates |
| * ocac_rx_state for atleast 1 sec |
| * - TBD: is there any other case? |
| */ |
| static void ieee80211_save_rx_ocac_state_ie(struct ieee80211com *ic, uint8_t *ta, uint8_t state, |
| uint8_t param) |
| { |
| #define QTN_IS_EQ_TA(ta1, ta2) !memcmp((ta1), (ta2), IEEE80211_ADDR_LEN) |
| |
| #define QTN_RESET_RX_STATE memset(old, 0, sizeof(*old)) |
| |
| #define QTN_SAVE_RX_STATE memcpy(old, &new, sizeof(*old)) |
| |
| struct ieee80211_ocac_rx_state new; |
| struct ieee80211_ocac_rx_state *old = &ic->ic_ocac.ocac_rx_state; |
| |
| if (ic->ic_opmode != IEEE80211_M_HOSTAP) |
| return; |
| |
| memcpy(new.ta, ta, IEEE80211_ADDR_LEN); |
| new.state = state; |
| new.param = param; |
| new.timestamp = jiffies; |
| |
| spin_lock(&ic->ic_ocac.ocac_lock); |
| |
| switch (old->state) { |
| case OCAC_STATE_NONE: |
| switch (new.state) { |
| case OCAC_STATE_NONE: |
| break; |
| |
| case OCAC_STATE_BACKOFF: |
| case OCAC_STATE_ONGOING: |
| QTN_SAVE_RX_STATE; |
| break; |
| } |
| |
| break; |
| |
| case OCAC_STATE_BACKOFF: |
| switch (new.state) { |
| case OCAC_STATE_NONE: |
| if (QTN_IS_EQ_TA(new.ta, old->ta)) |
| QTN_RESET_RX_STATE; |
| break; |
| |
| case OCAC_STATE_BACKOFF: |
| if (new.param < old->param) |
| QTN_SAVE_RX_STATE; |
| break; |
| |
| case OCAC_STATE_ONGOING: |
| QTN_SAVE_RX_STATE; |
| break; |
| } |
| |
| break; |
| |
| case OCAC_STATE_ONGOING: |
| switch (new.state) { |
| case OCAC_STATE_NONE: |
| if (QTN_IS_EQ_TA(new.ta, old->ta)) |
| QTN_RESET_RX_STATE; |
| break; |
| |
| case OCAC_STATE_BACKOFF: |
| if (QTN_IS_EQ_TA(new.ta, old->ta)) |
| QTN_SAVE_RX_STATE; |
| break; |
| |
| case OCAC_STATE_ONGOING: |
| break; |
| } |
| |
| break; |
| } |
| |
| spin_unlock(&ic->ic_ocac.ocac_lock); |
| } |
| |
| int ieee80211_handle_csa_invalid_channel(struct ieee80211_node *ni, struct ieee80211_frame *wh) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| |
| if (vap->iv_opmode == IEEE80211_M_HOSTAP) |
| return -1; |
| if (!ni->ni_associd) |
| return -1; |
| if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid)) |
| return -1; |
| |
| ieee80211_new_state(vap, IEEE80211_S_INIT, IEEE80211_REASON_DISASSOC_BAD_SUPP_CHAN); |
| |
| return 0; |
| } |
| |
| /* |
| * Context: SoftIRQ |
| */ |
| void |
| ieee80211_recv_mgmt(struct ieee80211_node *ni, struct sk_buff *skb, |
| int subtype, int rssi, u_int32_t rstamp) |
| { |
| #define ISPROBE(_st) ((_st) == IEEE80211_FC0_SUBTYPE_PROBE_RESP) |
| #define ISREASSOC(_st) ((_st) == IEEE80211_FC0_SUBTYPE_REASSOC_RESP) |
| #define IEEE80211_OPMODE_NOTIFY_INVALID 0xFF |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211com *ic = vap->iv_ic; |
| struct ieee80211_frame *wh; |
| u_int8_t *frm, *efrm; |
| u_int8_t *ssid, *rates, *xrates, *wpa, *rsn, *osen, *wme, *ath, *htcap = NULL, *htinfo = NULL; |
| u_int8_t *mdie = NULL; |
| u_int8_t *ftie = NULL; |
| u_int8_t *rlnk = NULL; |
| u_int8_t *vhtcap = NULL, *vhtop = NULL; |
| u_int8_t *extcap = NULL; |
| u_int8_t *wscie = NULL; |
| uint8_t *opmode_notif_ie = NULL; |
| u_int8_t *interw = NULL; |
| u_int8_t *obss_scan = NULL; |
| struct ieee80211_ie_qtn *qtnie = NULL; |
| #ifdef CONFIG_QVSP |
| struct ieee80211_ie_vsp *vspie = NULL; |
| #endif |
| u_int8_t *rrm_enabled = NULL; |
| struct ieee80211_ie_qtn_pairing *qtn_pairing_ie = NULL; |
| struct ieee80211_qtn_ext_role *qtn_ext_role = NULL; |
| struct ieee80211_qtn_ext_bssid *qtn_ext_bssid = NULL; |
| struct ieee80211_qtn_ext_state *qtn_ext_state = NULL; |
| |
| u_int8_t rate; |
| int reassoc; |
| int resp; |
| int node_reference_held = 0; |
| u_int8_t qosinfo; |
| u_int8_t beacon_update_required = 0; |
| void *bcmie = NULL; |
| void *rtkie = NULL; |
| struct ieee80211_ie_power_capability *pwr_cap; |
| uint8_t *supp_chan_ie = NULL; |
| int8_t min_txpwr, max_txpwr; |
| int8_t local_max_txpwr; |
| int arg; |
| int8_t non_erp_present = 0; |
| int sta_pure_tkip = 0; |
| |
| wh = (struct ieee80211_frame *) skb->data; |
| frm = (u_int8_t *)&wh[1]; |
| efrm = skb->data + skb->len; |
| |
| /* forward management frame to application */ |
| if (vap->iv_opmode != IEEE80211_M_MONITOR) |
| forward_mgmt_to_app(vap, subtype, skb, wh); |
| if (vap->iv_bss) |
| sta_pure_tkip = (vap->iv_bss->ni_rsn.rsn_ucastcipher == IEEE80211_CIPHER_TKIP); |
| |
| switch (subtype) { |
| case IEEE80211_FC0_SUBTYPE_PROBE_RESP: |
| case IEEE80211_FC0_SUBTYPE_BEACON: { |
| struct ieee80211_scanparams scan; |
| |
| /* Check if we need to reset ocac_rx_state */ |
| iee80211_check_ocac_rx_state(ic); |
| |
| /* |
| * When STA disconnects and boots up as AP, the DEAUTH/DISASSOC frame sent may get |
| * lost and AP can't create a WDS link with it since it's still "associated". |
| * This may be recovered by force leaving "STA" once we detect it became AP. |
| */ |
| if ((ni->ni_associd != 0) && (ni->ni_node_type == IEEE80211_NODE_TYPE_STA) && |
| ieee80211_node_is_qtn(ni)) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "force leave STA %pM as it became AP\n", ni->ni_macaddr); |
| ieee80211_node_leave(ni); |
| return; |
| } |
| |
| if (ieee80211_beacon_should_discard(ni)) { |
| vap->iv_stats.is_rx_mgtdiscard++; |
| return; |
| } |
| |
| /* |
| * beacon/probe response frame format |
| * [8] time stamp |
| * [2] beacon interval |
| * [2] capability information |
| * [tlv] ssid |
| * [tlv] supported rates |
| * [tlv] country information |
| * [tlv] parameter set (FH/DS) |
| * [tlv] erp information |
| * [tlv] extended supported rates |
| * [tlv] power constraint |
| * [tlv] WME |
| * [tlv] WPA or RSN |
| * [tlv] Atheros Advanced Capabilities |
| * [tlv] Quantenna flags |
| */ |
| IEEE80211_VERIFY_LENGTH(efrm - frm, 12); |
| memset(&scan, 0, sizeof(scan)); |
| scan.tstamp = frm; |
| frm += 8; |
| scan.bintval = le16toh(*(__le16 *)frm); |
| frm += 2; |
| scan.capinfo = le16toh(*(__le16 *)frm); |
| frm += 2; |
| scan.bchan = ieee80211_chan2ieee(ic, ic->ic_curchan); |
| |
| ni->ni_flags &= ~IEEE80211_NODE_TPC; |
| while (frm < efrm) { |
| /* Agere element in beacon */ |
| if ((*frm == IEEE80211_ELEMID_AGERE1) || |
| (*frm == IEEE80211_ELEMID_AGERE2)) { |
| frm = efrm; |
| continue; |
| } |
| |
| IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1]); |
| switch (*frm) { |
| case IEEE80211_ELEMID_SSID: |
| scan.ssid = frm; |
| break; |
| case IEEE80211_ELEMID_RATES: |
| scan.rates = frm; |
| break; |
| case IEEE80211_ELEMID_COUNTRY: |
| scan.country = frm; |
| break; |
| case IEEE80211_ELEMID_PWRCNSTR: |
| scan.pwr_constraint = frm; |
| ni->ni_flags |= IEEE80211_NODE_TPC; |
| break; |
| case IEEE80211_ELEMID_FHPARMS: |
| if (ic->ic_phytype == IEEE80211_T_FH) { |
| scan.fhdwell = LE_READ_2(&frm[2]); |
| scan.chan = IEEE80211_FH_CHAN(frm[4], frm[5]); |
| scan.fhindex = frm[6]; |
| } |
| break; |
| case IEEE80211_ELEMID_DSPARMS: |
| /* |
| * XXX hack this since depending on phytype |
| * is problematic for multi-mode devices. |
| */ |
| if (ic->ic_phytype != IEEE80211_T_FH) |
| scan.chan = frm[2]; |
| break; |
| case IEEE80211_ELEMID_TIM: |
| /* XXX ATIM? */ |
| scan.tim = frm; |
| scan.timoff = frm - skb->data; |
| break; |
| case IEEE80211_ELEMID_IBSSPARMS: |
| break; |
| case IEEE80211_ELEMID_XRATES: |
| scan.xrates = frm; |
| break; |
| case IEEE80211_ELEMID_ERP: |
| if (frm[1] != 1) { |
| IEEE80211_DISCARD_IE(vap, |
| IEEE80211_MSG_ELEMID, wh, "ERP", |
| "bad len %u", frm[1]); |
| vap->iv_stats.is_rx_elem_toobig++; |
| break; |
| } |
| scan.erp = frm[2]; |
| break; |
| case IEEE80211_ELEMID_RSN: |
| scan.rsn = frm; |
| break; |
| case IEEE80211_ELEMID_OPMOD_NOTIF: |
| opmode_notif_ie = frm; |
| break; |
| case IEEE80211_ELEMID_OBSS_SCAN: |
| scan.obss_scan = frm; |
| break; |
| case IEEE80211_ELEMID_BSS_LOAD: |
| scan.bssload = frm; |
| break; |
| case IEEE80211_ELEMID_VENDOR: |
| if (iswpaoui(frm)) |
| scan.wpa = frm; |
| else if (iswmeparam(frm) || iswmeinfo(frm)) |
| scan.wme = frm; |
| else if (iswscoui(frm)) |
| scan.wsc = frm; |
| else if (isatherosoui(frm)) |
| scan.ath = frm; |
| else if (isqtnie(frm)) |
| scan.qtn = frm; |
| else if (is_qtn_ext_role_oui(frm)) |
| qtn_ext_role = (struct ieee80211_qtn_ext_role *)frm; |
| else if (is_qtn_ext_bssid_oui(frm)) |
| qtn_ext_bssid = (struct ieee80211_qtn_ext_bssid *)frm; |
| else if (is_qtn_ext_state_oui(frm)) |
| qtn_ext_state = (struct ieee80211_qtn_ext_state *)frm; |
| else if (isqtnpairoui(frm)) |
| scan.pairing_ie = frm; |
| else if (isbrcmvhtoui(frm)) { |
| scan.vhtcap = ieee80211_get_vhtcap_from_brcmvht(ni, frm); |
| scan.vhtop = ieee80211_get_vhtop_from_brcmvht(ni, frm); |
| } |
| #ifdef CONFIG_QVSP |
| else if (isqtnwmeie(frm)) { |
| /* override standard WME IE */ |
| struct ieee80211_ie_qtn_wme *qwme = (struct ieee80211_ie_qtn_wme *)frm; |
| IEEE80211_NOTE(vap, IEEE80211_MSG_WME | IEEE80211_MSG_ELEMID, ni, |
| "%s: found QTN WME IE, version %u\n", |
| __func__, qwme->qtn_wme_ie_version); |
| scan.wme = (uint8_t *)&qwme->qtn_wme_ie; |
| } |
| #endif |
| /* Extract the OCAC State IE, if present */ |
| else if (is_qtn_ocac_state_ie(frm)) |
| ieee80211_save_rx_ocac_state_ie(ic, wh->i_addr3, frm[6], |
| frm[7]); |
| break; |
| case IEEE80211_ELEMID_CHANSWITCHANN: |
| scan.csa = frm; |
| break; |
| case IEEE80211_ELEMID_MEASREQ: |
| scan.measreq = frm; |
| break; |
| case IEEE80211_ELEMID_HTCAP: |
| scan.htcap = frm; |
| break; |
| case IEEE80211_ELEMID_HTINFO: |
| scan.htinfo = frm; |
| // As DS_PARAM IE is optional for 5 GHZ, double check here |
| scan.chan = frm[2]; |
| break; |
| case IEEE80211_ELEMID_VHTCAP: |
| scan.vhtcap = frm; |
| break; |
| case IEEE80211_ELEMID_VHTOP: |
| scan.vhtop = frm; |
| break; |
| /* Explicitly ignore some unhandled elements */ |
| case IEEE80211_ELEMID_TPCREP: |
| break; |
| case IEEE80211_ELEMID_EXTCAP: |
| extcap = frm; |
| break; |
| case IEEE80211_ELEMID_MOBILITY_DOMAIN: |
| scan.mdie = frm; |
| break; |
| default: |
| IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID, |
| wh, "unhandled", |
| "id %u, len %u", *frm, frm[1]); |
| vap->iv_stats.is_rx_elem_unknown++; |
| break; |
| } |
| frm += frm[1] + 2; |
| } |
| if (frm > efrm) |
| return; |
| IEEE80211_VERIFY_ELEMENT(scan.rates, IEEE80211_RATE_MAXSIZE); |
| IEEE80211_VERIFY_ELEMENT(scan.ssid, IEEE80211_NWID_LEN); |
| #if IEEE80211_CHAN_MAX < 255 |
| if (scan.chan > IEEE80211_CHAN_MAX) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID, |
| wh, ieee80211_mgt_subtype_name[subtype >> |
| IEEE80211_FC0_SUBTYPE_SHIFT], |
| "invalid channel %u", scan.chan); |
| vap->iv_stats.is_rx_badchan++; |
| return; |
| } |
| #endif |
| |
| /* beacon channel could be different with current channel, recorrect it */ |
| if (is_channel_valid(scan.chan)) { |
| scan.rxchan = findchannel_any(ic, scan.chan, ic->ic_des_mode); |
| if (!is_ieee80211_chan_valid(scan.rxchan)) |
| scan.rxchan = ic->ic_curchan; |
| } else { |
| scan.rxchan = ic->ic_curchan; |
| } |
| |
| /* Pure legacy 5GHz APs should not have a channel check. */ |
| /* Exception to this is 'BG' APs. */ |
| if ((ic->ic_phytype == IEEE80211_T_OFDM) && |
| (scan.htcap == NULL) && (scan.htinfo == NULL) |
| && !(scan.chan)) { |
| scan.chan = scan.bchan = 0; |
| } |
| |
| if (scan.chan != scan.bchan && |
| ic->ic_phytype != IEEE80211_T_FH) { |
| |
| /* The frame may have been received on the previous channel if the |
| * RX channel has been changed recently. |
| */ |
| u_int8_t older_chan = scan.bchan; |
| scan.rxchan = ic->ic_prevchan; |
| scan.bchan = ieee80211_chan2ieee(ic, ic->ic_prevchan); |
| #ifdef QTN_BG_SCAN |
| if (scan.chan != scan.bchan) { |
| if ((ic->ic_flags_qtn & IEEE80211_QTN_BGSCAN) && ic->ic_scanchan) { |
| scan.rxchan = ic->ic_scanchan; |
| scan.bchan = ieee80211_chan2ieee(ic, ic->ic_scanchan); |
| } |
| } |
| #endif /* QTN_BG_SCAN */ |
| #ifdef QSCS_ENABLED |
| if (scan.chan != scan.bchan) { |
| if (ic->ic_scs.scs_smpl_enable) { |
| scan.rxchan = &ic->ic_channels[ic->ic_scs.scs_des_smpl_chan]; |
| scan.bchan = ieee80211_chan2ieee(ic, scan.rxchan); |
| } |
| } |
| #endif /* QSCS_ENABLED */ |
| if (scan.chan != scan.bchan) { |
| /* |
| * Frame was received on a channel different from the |
| * one indicated in the DS params element id; |
| * silently discard it. |
| * |
| * NB: this can happen due to signal leakage. |
| * But we should take it for FH phy because |
| * the rssi value should be correct even for |
| * different hop pattern in FH. |
| */ |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID, |
| wh, ieee80211_mgt_subtype_name[subtype >> |
| IEEE80211_FC0_SUBTYPE_SHIFT], |
| "for off-channel (cur chan:%u, bcn chan:%u last chan:%u)\n", |
| older_chan, scan.chan, scan.bchan); |
| vap->iv_stats.is_rx_chanmismatch++; |
| return; |
| } else { |
| IEEE80211_DPRINTF(vap, |
| IEEE80211_MSG_ELEMID, |
| "accepted late bcn (cur chan:%u, bcn chan:%u last chan:%u)\n", |
| older_chan, scan.chan, scan.bchan); |
| } |
| |
| } |
| |
| if ((vap->iv_opmode == IEEE80211_M_STA) && (scan.rxchan == ic->ic_curchan)) { |
| del_timer_sync(&ic->sta_dfs_info.sta_silence_timer); |
| ic->ic_enable_xmit(ic); |
| } |
| |
| if ((ic->ic_flags & IEEE80211_F_DOTH) && (ic->ic_flags_ext & IEEE80211_FEXT_TPC) && |
| (scan.country != NULL) && (scan.pwr_constraint != NULL)) { |
| ieee80211_parse_local_max_txpwr(vap, &scan); |
| } |
| |
| ieee80211_extender_process(ni, qtn_ext_role, qtn_ext_bssid, qtn_ext_state, &scan, wh, rssi); |
| |
| if (IEEE80211_IS_CHAN_CAC_IN_PROGRESS(ic->ic_curchan) && scan.csa == NULL) |
| ieee80211_extdr_cac_check(ic, vap, wh); |
| |
| /* IEEE802.11 does not specify the allowed range for |
| * beacon interval. We discard any beacons with a |
| * beacon interval outside of an arbitrary range in |
| * order to protect against attack. |
| * |
| * NB: Discarding beacon directly maybe not a good solution. |
| * It will lead to some IOT issues with AP whose beacon interval is not in this range, |
| * although most of AP will not set beacon interval out of this range. |
| * |
| */ |
| if (!(IEEE80211_BINTVAL_MIN <= scan.bintval && |
| scan.bintval <= IEEE80211_BINTVAL_MAX)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_SCAN, |
| wh, "beacon", "invalid beacon interval (%u)", |
| scan.bintval); |
| return; |
| } |
| |
| /* |
| * Count frame now that we know it's to be processed. |
| */ |
| if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) |
| IEEE80211_NODE_STAT(ni, rx_beacons); |
| else |
| IEEE80211_NODE_STAT(ni, rx_proberesp); |
| |
| if (ic->ic_flags_qtn & IEEE80211_QTN_MONITOR) { |
| ni->ni_intval = scan.bintval; |
| if (scan.csa) { |
| ieee80211_parse_csaie(ni, scan.csa, scan.csa_tsf, wh); |
| } |
| return; |
| } |
| |
| /* |
| * When operating in station mode, check for state updates. |
| * Be careful to ignore beacons received while doing a |
| * background scan. We consider only 11g/WMM stuff right now. |
| */ |
| if (vap->iv_opmode == IEEE80211_M_STA && |
| ni->ni_associd != 0 && |
| IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid)) { |
| /* record tsf of last beacon */ |
| memcpy(ni->ni_tstamp.data, scan.tstamp, |
| sizeof(ni->ni_tstamp)); |
| if (ni->ni_intval != scan.bintval) { |
| IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, |
| "beacon interval divergence: was %u, now %u", |
| ni->ni_intval, scan.bintval); |
| if (!ni->ni_intval_end) { |
| int msecs = 0; /* silence compiler */ |
| ni->ni_intval_cnt = 0; |
| ni->ni_intval_old = ni->ni_intval; |
| msecs = (ni->ni_intval_old * 1024 * 10) / 1000; |
| ni->ni_intval_end = jiffies + msecs_to_jiffies(msecs); |
| IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, |
| "scheduling beacon interval measurement for %u msecs", |
| msecs); |
| } |
| if (scan.bintval > ni->ni_intval) { |
| ni->ni_intval = scan.bintval; |
| vap->iv_flags_ext |= IEEE80211_FEXT_APPIE_UPDATE; |
| } |
| /* XXX statistic */ |
| } |
| if (ni->ni_intval_end) { |
| if (scan.bintval == ni->ni_intval_old) |
| ni->ni_intval_cnt++; |
| if (!time_before(jiffies, ni->ni_intval_end)) { |
| IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, |
| "beacon interval measurement finished, old value repeated: %u times", |
| ni->ni_intval_cnt); |
| ni->ni_intval_end = 0; |
| if (ni->ni_intval_cnt == 0) { |
| IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, |
| "reprogramming bmiss timer from %u to %u", |
| ni->ni_intval_old, scan.bintval); |
| ni->ni_intval = scan.bintval; |
| vap->iv_flags_ext |= IEEE80211_FEXT_APPIE_UPDATE; |
| } else { |
| IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, |
| "ignoring the divergence (maybe someone tried to spoof the AP?)", 0); |
| } |
| } |
| /* XXX statistic */ |
| } |
| |
| |
| /* update transmit power if necessary */ |
| if ((ic->ic_flags & IEEE80211_F_DOTH) && |
| (ic->ic_flags_ext & IEEE80211_FEXT_TPC) && |
| (scan.pwr_constraint != NULL) && |
| (vap->iv_local_max_txpow != scan.local_max_txpwr)) { |
| if ((scan.local_max_txpwr >= ni->ni_chan->ic_maxpower_normal) && |
| (vap->iv_local_max_txpow < ni->ni_chan->ic_maxpower_normal)) { |
| vap->iv_local_max_txpow = ni->ni_chan->ic_maxpower_normal; |
| } else if ((scan.local_max_txpwr <= ni->ni_chan->ic_minpower_normal) && |
| (vap->iv_local_max_txpow > ni->ni_chan->ic_minpower_normal)) { |
| vap->iv_local_max_txpow = ni->ni_chan->ic_minpower_normal; |
| } else if (scan.local_max_txpwr < ni->ni_chan->ic_maxpower_normal && |
| (scan.local_max_txpwr > ni->ni_chan->ic_minpower_normal)){ |
| vap->iv_local_max_txpow = scan.local_max_txpwr; |
| } else { |
| /* do nothing */ |
| } |
| ieee80211_update_tx_power(ic, vap->iv_local_max_txpow); |
| } |
| |
| if (ni->ni_erp != scan.erp) { |
| IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, |
| "erp change: was 0x%x, now 0x%x", |
| ni->ni_erp, scan.erp); |
| if (IEEE80211_BG_PROTECT_ENABLED(ic) && (scan.erp & IEEE80211_ERP_USE_PROTECTION)) { |
| ic->ic_flags |= IEEE80211_F_USEPROT; |
| /* tell Muc to use ERP cts-to-self mechanism now */ |
| ic->ic_set_11g_erp(vap, 1); |
| } else { |
| ic->ic_flags &= ~IEEE80211_F_USEPROT; |
| /* tell Muc to turn off ERP now */ |
| ic->ic_set_11g_erp(vap, 0); |
| } |
| ni->ni_erp = scan.erp; |
| /* XXX statistic */ |
| } |
| if ((ni->ni_capinfo ^ scan.capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME) { |
| IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, |
| "capabilities change: was 0x%x, now 0x%x", |
| ni->ni_capinfo, scan.capinfo); |
| /* |
| * NB: we assume short preamble doesn't |
| * change dynamically |
| */ |
| ieee80211_set_shortslottime(ic, |
| IEEE80211_IS_CHAN_A(ic->ic_bsschan) || |
| (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); |
| ni->ni_capinfo = scan.capinfo; |
| /* XXX statistic */ |
| } |
| if (scan.wme != NULL && |
| (ni->ni_flags & IEEE80211_NODE_QOS)) { |
| int _retval; |
| if ((_retval = ieee80211_parse_wmeparams(vap, scan.wme, wh, &qosinfo)) >= 0) { |
| if (qosinfo & WME_CAPINFO_UAPSD_EN) |
| ni->ni_flags |= IEEE80211_NODE_UAPSD; |
| if (_retval > 0) |
| ieee80211_wme_updateparams(vap, 0); |
| } |
| } else { |
| ni->ni_flags &= ~IEEE80211_NODE_UAPSD; |
| } |
| if (scan.ath != NULL) |
| ieee80211_parse_athParams(ni, scan.ath); |
| if (scan.csa != NULL) { |
| if (QTN_CSAIE_ERR_CHAN_NOT_SUPP == ieee80211_parse_csaie(ni, scan.csa, scan.csa_tsf, wh)) |
| if (!ieee80211_handle_csa_invalid_channel(ni, wh)) |
| return; |
| } |
| /* 11n */ |
| if (scan.htcap) { |
| ieee80211_parse_htcap(ni, scan.htcap); |
| } |
| if (scan.htinfo) { |
| ieee80211_parse_htinfo(ni, scan.htinfo); |
| if ((ic->ic_opmode == IEEE80211_M_STA) && |
| (ic->ic_20_40_coex_enable) && |
| IEEE80211_IS_11NG_40(ic) && vap->iv_bss && |
| IEEE80211_ADDR_EQ(ni->ni_macaddr, vap->iv_bss->ni_macaddr) && |
| (!ni->ni_htinfo.choffset)) { |
| ieee80211_change_bw(vap, BW_HT20, 0); |
| ic->ic_coex_stats_update(ic, WLAN_COEX_STATS_BW_SCAN); |
| } |
| } |
| /* 802.11ac */ |
| if (scan.vhtcap && IS_IEEE80211_DUALBAND_VHT_ENABLED(ic)) { |
| ieee80211_check_and_parse_vhtcap(ni, scan.vhtcap); |
| } |
| if (scan.vhtop && IS_IEEE80211_DUALBAND_VHT_ENABLED(ic)) { |
| ieee80211_parse_vhtop(ni, scan.vhtop); |
| } |
| if (scan.measreq) { |
| ieee80211_parse_measinfo(ni, scan.measreq); |
| } |
| |
| if (scan.obss_scan) { |
| memset(&ni->ni_obss_ie, 0, sizeof(struct ieee80211_obss_scan_ie)); |
| memcpy(&ni->ni_obss_ie, scan.obss_scan, |
| sizeof(struct ieee80211_obss_scan_ie)); |
| } |
| |
| if (scan.tim != NULL) { |
| /* |
| * Check the TIM. For now we drop out of |
| * power save mode for any reason. |
| */ |
| struct ieee80211_tim_ie *tim = |
| (struct ieee80211_tim_ie *) scan.tim; |
| int aid = IEEE80211_AID(ni->ni_associd); |
| int ix = aid / NBBY; |
| int min = tim->tim_bitctl & ~1; |
| int max = tim->tim_len + min - 4; |
| if (min <= ix && ix <= max && |
| isset(tim->tim_bitmap - min, aid)) { |
| ieee80211_sta_pwrsave(vap, 0); |
| vap->iv_ap_buffered = 1; |
| } else { |
| vap->iv_ap_buffered = 0; |
| } |
| vap->iv_dtim_count = tim->tim_count; |
| } |
| |
| ieee80211_update_tbtt(vap, ni); |
| |
| /* WDS/Repeater: re-schedule software beacon timer for STA */ |
| if (vap->iv_state == IEEE80211_S_RUN && |
| vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS) { |
| #if defined(QBMPS_ENABLE) |
| if (vap->iv_swbmiss_bmps_warning) { |
| /* if previously BMPS detects swbmiss */ |
| /* it will disable power-saving temporary */ |
| /* to help beacon RX */ |
| /* now it is time to reenable power-saving */ |
| vap->iv_swbmiss_bmps_warning = 0; |
| ic->ic_pm_reason = IEEE80211_PM_LEVEL_SWBCN_MISS; |
| ieee80211_pm_queue_work(ic); |
| } |
| #endif |
| vap->iv_swbmiss_warnings = IEEE80211_SWBMISS_WARNINGS; |
| mod_timer(&vap->iv_swbmiss, jiffies + vap->iv_swbmiss_period); |
| } |
| |
| if (opmode_notif_ie) { |
| struct ieee80211_ie_vhtop_notif *ie = |
| (struct ieee80211_ie_vhtop_notif *)opmode_notif_ie; |
| int cur_opnode; |
| uint8_t opmode; |
| |
| ieee80211_param_from_qdrv(vap, IEEE80211_PARAM_NODE_OPMODE, &cur_opnode, NULL, NULL); |
| opmode = recalc_opmode(ni, ie->vhtop_notif_mode); |
| if (cur_opnode != opmode) { |
| ieee80211_param_to_qdrv(ni->ni_vap, IEEE80211_PARAM_NODE_OPMODE, |
| opmode, ni->ni_macaddr, IEEE80211_ADDR_LEN); |
| ni->ni_vhtop_notif_mode = ie->vhtop_notif_mode; |
| } |
| } |
| |
| /* |
| * If scanning, pass the info to the scan module. |
| * Otherwise, check if it's the right time to do |
| * a background scan. Background scanning must |
| * be enabled and we must not be operating in the |
| * turbo phase of dynamic turbo mode. Then, |
| * it's been a while since the last background |
| * scan and if no data frames have come through |
| * recently, kick off a scan. Note that this |
| * is the mechanism by which a background scan |
| * is started _and_ continued each time we |
| * return on-channel to receive a beacon from |
| * our ap. |
| */ |
| if ((ic->ic_flags & IEEE80211_F_SCAN) |
| #ifdef QTN_BG_SCAN |
| || (ic->ic_flags_qtn & IEEE80211_QTN_BGSCAN) |
| #endif /* QTN_BG_SCAN */ |
| ) { |
| ieee80211_add_scan(vap, &scan, wh, |
| subtype, rssi, rstamp); |
| } else if (contbgscan(vap) || startbgscan(vap)) { |
| ieee80211_bg_scan(vap); |
| } |
| if (extcap != NULL) |
| ieee80211_parse_extcap(ni, extcap, wh->i_addr3); |
| return; |
| } |
| |
| /* |
| * If scanning, pass information to the scan module. |
| */ |
| if ((ic->ic_flags & IEEE80211_F_SCAN) |
| #ifdef QTN_BG_SCAN |
| || (ic->ic_flags_qtn & IEEE80211_QTN_BGSCAN) |
| #endif /* QTN_BG_SCAN */ |
| ) { |
| /* In two cases station scan list will be updated. |
| * 1. when 11ac_and 11n flag is set and only vht or ht capabilities |
| * are present in Beacon/Probe response. |
| * 2. When 11ac_and_11n flag is not set. |
| */ |
| if (ieee80211_phy_mode_allowed(vap, scan.vhtcap, scan.htcap)) { |
| ieee80211_add_scan(vap, &scan, wh, subtype, rssi, rstamp); |
| } |
| |
| return; |
| } |
| |
| #ifdef QSCS_ENABLED |
| if (ic->ic_scs.scs_smpl_enable) |
| ieee80211_add_scs_off_chan(vap, &scan, wh, subtype, rssi, rstamp); |
| #endif |
| |
| if (vap->iv_opmode == IEEE80211_M_WDS) { |
| int return_val = 0; |
| |
| ieee80211_update_wds_peer_node(ni, &scan); |
| |
| if (unlikely(scan.csa != NULL) && IEEE80211_VAP_WDS_IS_RBS(vap)) |
| return_val = ieee80211_parse_csaie(ni, scan.csa, scan.csa_tsf, wh); |
| if (QTN_CSAIE_ERR_CHAN_NOT_SUPP == return_val) |
| if (!ieee80211_handle_csa_invalid_channel(ni, wh)) |
| return; |
| |
| return; |
| } |
| |
| /* check beacon for non-ERP and non-HT non member protection mode |
| * non-ERP protection: OBSS non-ERP protection is set when |
| * a) non-ERP present bit is set in the OBSS AP |
| * b) B only AP is present |
| * non-HT non member protection is set when there is non HT BSS. |
| * Timer is used to reset the protection parameters after last |
| * non-ERP AP or non-HT BSS goes away. |
| */ |
| if (vap->iv_opmode == IEEE80211_M_HOSTAP) { |
| uint8_t nonht_obss; |
| struct ieee80211_ie_htinfo *ht = (struct ieee80211_ie_htinfo *) scan.htinfo; |
| nonht_obss = ((scan.htcap == NULL) || (ht == NULL) || |
| ((ht->hi_byte2 & IEEE80211_HTINFO_OPMODE_HT_PROT_MIXED) == |
| IEEE80211_HTINFO_OPMODE_HT_PROT_MIXED)); |
| |
| if (nonht_obss) { |
| if ((ic->ic_curmode == IEEE80211_MODE_11NG_HT40PM) && |
| (ic->ic_20_40_coex_enable)) { |
| ieee80211_change_bw(vap, BW_HT20, 0); |
| ic->ic_coex_stats_update(ic, WLAN_COEX_STATS_BW_SCAN); |
| } |
| |
| /*Legacy AP is present */ |
| if (ic->ic_non_ht_non_member == 0) { |
| /* First non-HT AP beacon received */ |
| beacon_update_required = 1; |
| ic->ic_non_ht_non_member = 1; |
| vap->iv_ht_flags |= IEEE80211_HTF_HTINFOUPDATE; |
| } |
| } |
| |
| /* Check non-ERP protection in 2 GHz band */ |
| if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { |
| non_erp_present = is_non_erp_prot_required(&scan); |
| |
| if (IEEE80211_BG_PROTECT_ENABLED(ic) |
| && non_erp_present |
| && !(ic->ic_flags & IEEE80211_F_USEPROT)) { |
| /* First OBSS non-ERP AP beacon received */ |
| /* Set Use_Protection in ERP IE */ |
| ic->ic_flags |= IEEE80211_F_USEPROT; |
| |
| /* To call ieee80211_add_erp function */ |
| ic->ic_flags_ext |= IEEE80211_FEXT_ERPUPDATE; |
| beacon_update_required = 1; |
| /* tell Muc to use ERP cts-to-self mechanism now */ |
| ic->ic_set_11g_erp(vap, 1); |
| } |
| } |
| |
| if (vap->iv_state == IEEE80211_S_RUN) { |
| /* Update beacon */ |
| if(beacon_update_required) |
| ic->ic_beacon_update(vap); |
| |
| if (nonht_obss) { |
| mod_timer(&vap->iv_swbmiss, |
| jiffies + vap->iv_swbmiss_period); |
| } |
| |
| if (non_erp_present) { |
| mod_timer(&vap->iv_swberp, |
| jiffies + vap->iv_swberp_period); |
| } |
| } |
| } |
| |
| if ((vap->iv_opmode == IEEE80211_M_IBSS) && |
| (scan.capinfo & IEEE80211_CAPINFO_IBSS)) { |
| if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) { |
| /* Create a new entry in the neighbor table. */ |
| ni = ieee80211_add_neighbor(vap, wh, &scan); |
| node_reference_held = 1; |
| } else { |
| /* |
| * Copy data from beacon to neighbor table. |
| * Some of this information might change after |
| * ieee80211_add_neighbor(), so we just copy |
| * everything over to be safe. |
| */ |
| ni->ni_esslen = scan.ssid[1]; |
| memcpy(ni->ni_essid, scan.ssid + 2, scan.ssid[1]); |
| IEEE80211_ADDR_COPY(ni->ni_bssid, wh->i_addr3); |
| memcpy(ni->ni_tstamp.data, scan.tstamp, |
| sizeof(ni->ni_tstamp)); |
| ni->ni_intval = IEEE80211_BINTVAL_SANITISE(scan.bintval); |
| ni->ni_capinfo = scan.capinfo; |
| ni->ni_chan = ic->ic_curchan; |
| ni->ni_fhdwell = scan.fhdwell; |
| ni->ni_fhindex = scan.fhindex; |
| ni->ni_erp = scan.erp; |
| ni->ni_timoff = scan.timoff; |
| if (scan.wme != NULL) |
| ieee80211_saveie(&ni->ni_wme_ie, scan.wme); |
| if (scan.wpa != NULL) |
| ieee80211_saveie(&ni->ni_wpa_ie, scan.wpa); |
| if (scan.rsn != NULL) |
| ieee80211_saveie(&ni->ni_rsn_ie, scan.rsn); |
| if (scan.wsc != NULL) |
| ieee80211_saveie(&ni->ni_wsc_ie, scan.wsc); |
| if (scan.ath != NULL) |
| ieee80211_saveath(ni, scan.ath); |
| |
| /* NB: must be after ni_chan is setup */ |
| ieee80211_setup_rates(ni, scan.rates, |
| scan.xrates, IEEE80211_F_DOSORT); |
| } |
| if (ni != NULL) { |
| ni->ni_rssi = rssi; |
| ni->ni_rstamp = rstamp; |
| ni->ni_last_rx = jiffies; |
| if (node_reference_held) { |
| ieee80211_free_node(ni); |
| } |
| } |
| } |
| break; |
| } |
| |
| case IEEE80211_FC0_SUBTYPE_PROBE_REQ: { |
| if (vap->iv_opmode == IEEE80211_M_STA || |
| vap->iv_opmode == IEEE80211_M_AHDEMO || |
| vap->iv_opmode == IEEE80211_M_WDS || |
| vap->iv_state != IEEE80211_S_RUN || |
| vap->is_block_all_assoc) { |
| vap->iv_stats.is_rx_mgtdiscard++; |
| return; |
| } |
| |
| #if defined(PLATFORM_QFDR) |
| if (ic->ic_reject_auth & QFDR_F_IGNORE_PROBE_REQ) |
| return; |
| #endif |
| |
| if (vap->iv_acl != NULL && !vap->iv_acl->iac_check(vap, wh->i_addr2)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_ACL, |
| wh, "Probe Req", "%s", "disallowed by ACL"); |
| vap->iv_stats.is_rx_acl++; |
| return; |
| } |
| |
| /* |
| * prreq frame format |
| * [tlv] ssid |
| * [tlv] supported rates |
| * [tlv] extended supported rates |
| * [tlv] Atheros Advanced Capabilities |
| */ |
| ssid = rates = xrates = ath = NULL; |
| while (frm < efrm) { |
| IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1]); |
| switch (*frm) { |
| case IEEE80211_ELEMID_SSID: |
| /* WAR: Null-paddings are interpreted as null SSID IEs */ |
| ssid = ssid ? ssid : frm; |
| break; |
| case IEEE80211_ELEMID_RATES: |
| rates = frm; |
| break; |
| case IEEE80211_ELEMID_HTCAP: |
| htcap = frm; |
| break; |
| case IEEE80211_ELEMID_VHTCAP: |
| vhtcap = frm; |
| break; |
| case IEEE80211_ELEMID_XRATES: |
| xrates = frm; |
| break; |
| case IEEE80211_ELEMID_INTERWORKING: |
| interw = frm; |
| break; |
| case IEEE80211_ELEMID_VENDOR: |
| if (isatherosoui(frm)) |
| ath = frm; |
| /* XXX Atheros OUI support */ |
| break; |
| } |
| frm += frm[1] + 2; |
| } |
| if (frm > efrm) |
| return; |
| |
| |
| IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE); |
| IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN); |
| if (ieee80211_verify_ssid(vap, ni, wh, ssid, subtype) == |
| IEEE80211_VERIFY_SSID_ACTION_RETURN) { |
| return; |
| } |
| |
| if (vap->interworking && interw != NULL) { |
| if (ieee80211_verify_interworking(vap, interw)) |
| return; |
| } |
| |
| if (ni == vap->iv_bss) { |
| if (vap->iv_opmode == IEEE80211_M_IBSS) { |
| /* |
| * XXX Cannot tell if the sender is operating |
| * in ibss mode. But we need a new node to |
| * send the response so blindly add them to the |
| * neighbor table. |
| */ |
| ni = ieee80211_fakeup_adhoc_node(vap, |
| wh->i_addr2); |
| } else { |
| ni = ieee80211_tmp_node(vap, wh->i_addr2); |
| } |
| if (ni == NULL) { |
| return; |
| } |
| node_reference_held = 1; |
| } |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, wh->i_addr2, |
| "%s", "recv probe req"); |
| ni->ni_rssi = rssi; |
| ni->ni_rstamp = rstamp; |
| ni->ni_last_rx = jiffies; |
| rate = ieee80211_setup_rates(ni, rates, xrates, |
| IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | |
| IEEE80211_F_DONEGO | IEEE80211_F_DODEL); |
| if (rate & IEEE80211_RATE_BASIC) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_XRATE, |
| wh, ieee80211_mgt_subtype_name[subtype >> |
| IEEE80211_FC0_SUBTYPE_SHIFT], |
| "%s", "recv'd rate set invalid"); |
| } else { |
| |
| #if defined(CONFIG_QTN_BSA_SUPPORT) |
| if (vap->bsa_status == BSA_STATUS_ACTIVE) { |
| ieee80211_bsa_probe_event_send(vap, skb, wh->i_addr3,wh->i_addr2, |
| rssi); |
| if (ieee80211_bsa_macfilter_check(vap, wh->i_addr2)) |
| return; |
| } |
| #endif |
| IEEE80211_SEND_MGMT(ni, |
| IEEE80211_FC0_SUBTYPE_PROBE_RESP, |
| ssid[1] == 0); |
| } |
| if (node_reference_held) { |
| ieee80211_free_node(ni); |
| } else if (ath != NULL) |
| ieee80211_saveath(ni, ath); |
| break; |
| } |
| |
| case IEEE80211_FC0_SUBTYPE_AUTH: { |
| u_int16_t algo, seq, status; |
| /* |
| * auth frame format |
| * [2] algorithm |
| * [2] sequence |
| * [2] status |
| * [tlv*] challenge |
| */ |
| IEEE80211_VERIFY_LENGTH(efrm - frm, 6); |
| algo = le16toh(*(__le16 *)frm); |
| seq = le16toh(*(__le16 *)(frm + 2)); |
| status = le16toh(*(__le16 *)(frm + 4)); |
| |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH, wh->i_addr2, |
| "recv auth frame with algorithm %d seq %d", algo, seq); |
| |
| if (vap->iv_opmode == IEEE80211_M_HOSTAP) { |
| if (!(ic->ic_flags_ext & IEEE80211_FEXT_REPEATER) |
| && (ic->ic_curchan->ic_flags & IEEE80211_CHAN_DFS) |
| && (!IEEE80211_IS_CHAN_CACDONE(ic->ic_curchan))) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH, wh->i_addr2, |
| "During DFS CAC period(channel %3d), reject auth frame", ic->ic_curchan->ic_ieee); |
| return; |
| } |
| if (unlikely(ieee80211_input_mac_reserved(vap, ic, ni, wh))) |
| return; |
| if (vap->is_block_all_assoc) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH, |
| wh, "auth", "%s", "Dropped due to BSS is set to block all assoc"); |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_STATUS_DENIED); |
| return; |
| } |
| |
| #if defined(PLATFORM_QFDR) |
| if (ic->ic_reject_auth & QFDR_F_REJECT_AUTH) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH, |
| wh, "auth", "%s", "Rejected auth frame"); |
| ieee80211_send_error(ni, wh->i_addr2, |
| IEEE80211_FC0_SUBTYPE_AUTH, |
| (seq+1) | (IEEE80211_STATUS_TOOMANY << 16)); |
| return; |
| } |
| #endif /* PLATFORM_QFDR */ |
| |
| #if defined(CONFIG_QTN_BSA_SUPPORT) |
| if (vap->bsa_status == BSA_STATUS_ACTIVE) { |
| if (ieee80211_bsa_macfilter_check(vap, wh->i_addr2)) { |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_STATUS_DENIED); |
| return; |
| } |
| } |
| #endif |
| } |
| |
| /* Consult the ACL policy module if set up */ |
| if (vap->iv_acl != NULL && !vap->iv_acl->iac_check(vap, wh->i_addr2)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_ACL, |
| wh, "auth", "%s", "disallowed by ACL"); |
| vap->iv_stats.is_rx_acl++; |
| ieee80211_eventf(vap->iv_dev, "%s[WLAN access denied] from MAC: %pM", QEVT_ACL_PREFIX, wh->i_addr2); |
| return; |
| } else { |
| ieee80211_eventf(vap->iv_dev, "%s[WLAN access allowed] from MAC: %pM", QEVT_ACL_PREFIX, wh->i_addr2); |
| } |
| if (vap->iv_flags & IEEE80211_F_COUNTERM) { |
| IEEE80211_DISCARD(vap, |
| IEEE80211_MSG_AUTH | IEEE80211_MSG_CRYPTO, |
| wh, "auth", "%s", "TKIP countermeasures enabled"); |
| vap->iv_stats.is_rx_auth_countermeasures++; |
| if (vap->iv_opmode == IEEE80211_M_HOSTAP) { |
| /* This will include broadcast deauth frame queued on BSS node */ |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_MIC_FAILURE); |
| } |
| return; |
| } |
| |
| if (algo == IEEE80211_AUTH_ALG_SHARED) |
| ieee80211_auth_shared(ni, wh, frm + 6, efrm, rssi, |
| rstamp, seq, status); |
| else if (algo == IEEE80211_AUTH_ALG_OPEN) { |
| ieee80211_auth_open(ni, wh, rssi, rstamp, seq, status); |
| if (vap->iv_mdid) |
| forward_mgmt_to_app_for_further_processing(vap, subtype, skb, wh); |
| } else if (algo == IEEE80211_AUTH_ALG_FT) { |
| uint8_t cap = 0; |
| uint16_t mdid = 0; |
| frm += 6; |
| /* Parse the auth req frame */ |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_AUTH, |
| "[%pM] FT auth request\n", wh->i_addr2); |
| while (frm < efrm) { |
| IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1]); |
| switch (*frm) { |
| case IEEE80211_ELEMID_MOBILITY_DOMAIN: |
| if (frm[1] != IEEE80211_MDIE_LEN) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH, wh, |
| "auth", "wrong len of MDID in auth req %d", |
| frm[1]); |
| return; |
| } |
| mdid = le16toh(*(u_int16_t *)(&frm[2])); |
| if (mdid != ni->ni_vap->iv_mdid) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH, wh, |
| "wrong", "mdid in auth %d, expected %d", |
| mdid, ni->ni_vap->iv_mdid); |
| |
| return; |
| } |
| /* extract the ft policy and ft capability */ |
| cap = frm[4]; |
| ni->ni_ft_capability = cap; |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_AUTH, |
| "[%pM] MDIE in the auth with the cap as %d, mdid %d \n", |
| wh->i_addr2, cap, mdid); |
| break; |
| default: |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG,"[%pM] Received IE %d\n", |
| wh->i_addr2,*frm); |
| break; |
| } |
| frm += frm[1] + 2; |
| } |
| if (mdid == ni->ni_vap->iv_mdid) { |
| if (ni == vap->iv_bss) { |
| ni = ieee80211_dup_bss(vap, wh->i_addr2); |
| ni->ni_node_type = IEEE80211_NODE_TYPE_STA; |
| ieee80211_free_node(ni); |
| } |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_AUTH, |
| "[%pM] FT auth request sending up\n", wh->i_addr2); |
| forward_mgmt_to_app_for_further_processing(vap, subtype, skb, wh); |
| } |
| } else { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "auth", "unsupported alg %d", algo); |
| vap->iv_stats.is_rx_auth_unsupported++; |
| if (vap->iv_opmode == IEEE80211_M_HOSTAP) { |
| /* XXX not right */ |
| ieee80211_send_error(ni, wh->i_addr2, |
| IEEE80211_FC0_SUBTYPE_AUTH, |
| (seq+1) | (IEEE80211_STATUS_ALG << 16)); |
| } |
| return; |
| } |
| ieee80211_off_channel_suspend(vap, IEEE80211_OFFCHAN_TIMEOUT_AUTH); |
| ni->ni_used_auth_algo = algo; |
| break; |
| } |
| |
| case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: |
| case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: { |
| uint16_t capinfo; |
| uint16_t bintval; |
| struct ieee80211_rsnparms rsn_parm; |
| uint8_t reason; |
| int error = 0; |
| uint8_t interworking_ie_present = 0; |
| struct ieee80211_20_40_coex_param *coex = NULL; |
| |
| enum ieee80211_verify_ssid_action ssid_verify_action; |
| |
| if (vap->iv_opmode != IEEE80211_M_HOSTAP || |
| vap->iv_state != IEEE80211_S_RUN) { |
| vap->iv_stats.is_rx_mgtdiscard++; |
| return; |
| } |
| if (subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) { |
| reassoc = 1; |
| resp = IEEE80211_FC0_SUBTYPE_REASSOC_RESP; |
| } else { |
| reassoc = 0; |
| resp = IEEE80211_FC0_SUBTYPE_ASSOC_RESP; |
| } |
| |
| #if defined(CONFIG_QTN_BSA_SUPPORT) |
| if (vap->bsa_status == BSA_STATUS_ACTIVE) { |
| if (ieee80211_bsa_macfilter_check(vap, wh->i_addr2)) { |
| IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_DENIED); |
| return; |
| } |
| } |
| #endif |
| |
| if (vap->is_block_all_assoc) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, ieee80211_mgt_subtype_name[subtype >> |
| IEEE80211_FC0_SUBTYPE_SHIFT], |
| "%s", "BSS is blocked for all association request"); |
| vap->iv_stats.is_rx_assoc_bss++; |
| IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_DENIED); |
| return; |
| } |
| /* |
| * asreq frame format |
| * [2] capability information |
| * [2] listen interval |
| * [6*] current AP address (reassoc only) |
| * [tlv] ssid |
| * [tlv] supported rates |
| * [tlv] extended supported rates |
| * [tlv] power capability |
| * [tlv] supported channels |
| * [tlv] wpa or RSN |
| * [tlv] WME |
| * [tlv] Atheros Advanced Capabilities |
| * [tlv] Quantenna flags |
| */ |
| IEEE80211_VERIFY_LENGTH(efrm - frm, (reassoc ? 10 : 4)); |
| if (!IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_bss->ni_bssid)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, ieee80211_mgt_subtype_name[subtype >> |
| IEEE80211_FC0_SUBTYPE_SHIFT], |
| "%s", "wrong bssid"); |
| vap->iv_stats.is_rx_assoc_bss++; |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_ASSOC_FAILS, 1); |
| return; |
| } |
| if (vap->iv_pmf && |
| RSN_IS_MFP(ni->ni_rsn.rsn_caps) && |
| (ni->ni_associd) && |
| (!ni->ni_sa_query_timeout)) { |
| |
| IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_PMF_REJECT_RETRY); |
| ieee80211_send_sa_query(ni, IEEE80211_ACTION_W_SA_QUERY_REQ, ++ni->ni_sa_query_tid); |
| return; |
| } |
| capinfo = le16toh(*(__le16 *)frm); |
| frm += 2; |
| bintval = le16toh(*(__le16 *)frm); |
| frm += 2; |
| if (reassoc) |
| frm += 6; /* ignore current AP info */ |
| ssid = rates = xrates = wpa = rsn = osen = wme = ath = NULL; |
| pwr_cap = NULL; |
| ni->ni_flags &= ~IEEE80211_NODE_TPC; |
| while (frm < efrm) { |
| IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1]); |
| switch (*frm) { |
| case IEEE80211_ELEMID_SSID: |
| ssid = frm; |
| break; |
| case IEEE80211_ELEMID_RATES: |
| rates = frm; |
| break; |
| case IEEE80211_ELEMID_XRATES: |
| xrates = frm; |
| break; |
| case IEEE80211_ELEMID_PWRCAP: |
| pwr_cap = (struct ieee80211_ie_power_capability *)frm; |
| ni->ni_flags |= IEEE80211_NODE_TPC; |
| break; |
| case IEEE80211_ELEMID_SUPPCHAN: |
| supp_chan_ie = frm; |
| break; |
| case IEEE80211_ELEMID_INTERWORKING: |
| interworking_ie_present = 1; |
| break; |
| /* XXX verify only one of RSN and WPA ie's? */ |
| case IEEE80211_ELEMID_RSN: |
| if (vap->iv_flags & IEEE80211_F_WPA2) |
| rsn = frm; |
| else |
| IEEE80211_DPRINTF(vap, |
| IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, |
| "[%s] ignoring RSN IE in association request\n", |
| ether_sprintf(wh->i_addr2)); |
| break; |
| case IEEE80211_ELEMID_HTCAP: |
| htcap = frm; |
| break; |
| case IEEE80211_ELEMID_VHTCAP: |
| vhtcap = frm; |
| break; |
| case IEEE80211_ELEMID_VHTOP: |
| vhtop = frm; |
| break; |
| case IEEE80211_ELEMID_OPMOD_NOTIF: |
| opmode_notif_ie = frm; |
| break; |
| case IEEE80211_ELEMID_20_40_BSS_COEX: |
| coex = (struct ieee80211_20_40_coex_param *)frm; |
| break; |
| case IEEE80211_ELEMID_VENDOR: |
| /* don't override RSN element |
| * XXX: actually the driver should report both WPA versions, |
| * so wpa_supplicant can choose and also detect downgrade attacks |
| */ |
| if (iswpaoui(frm) && !wpa) { |
| if (vap->iv_flags & IEEE80211_F_WPA1) |
| wpa = frm; |
| else |
| IEEE80211_DPRINTF(vap, |
| IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, |
| "[%s] ignoring WPA IE in association request\n", |
| ether_sprintf(wh->i_addr2)); |
| } else if (isosenie(frm)) { |
| osen = frm; |
| } else if (iswmeinfo(frm)) { |
| wme = frm; |
| } else if (isatherosoui(frm)) { |
| ath = frm; |
| } else if (isbroadcomoui(frm)) { |
| bcmie = frm; |
| if (isbrcmvhtoui(frm)) |
| vhtcap = ieee80211_get_vhtcap_from_brcmvht(ni, frm); |
| } else if (isbroadcomoui2(frm)) { |
| bcmie = frm; |
| } else if (isrealtekoui(frm)) { |
| rtkie = frm; |
| } else if (isqtnie(frm)) { |
| qtnie = (struct ieee80211_ie_qtn *)frm; |
| /* For now just get the first WSC IE until we can handle multiple of these */ |
| } else if (iswscoui(frm) && !wscie) { |
| wscie = frm; |
| } else if (isqtnpairoui(frm)) { |
| qtn_pairing_ie = (struct ieee80211_ie_qtn_pairing *)frm; |
| } else if (isrlnkoui(frm)) { |
| rlnk = frm; |
| } |
| break; |
| case IEEE80211_ELEMID_EXTCAP: |
| extcap = frm; |
| if (extcap != NULL) { |
| ieee80211_parse_extcap(ni, extcap, wh->i_addr2); |
| ieee80211_parse_80211_v(ni, extcap); |
| } |
| break; |
| case IEEE80211_ELEMID_RRM_ENABLED: |
| rrm_enabled = frm; |
| break; |
| case IEEE80211_ELEMID_MOBILITY_DOMAIN: |
| mdie = frm; |
| break; |
| case IEEE80211_ELEMID_FTIE: |
| ftie = frm; |
| break; |
| } |
| frm += frm[1] + 2; |
| } |
| if (frm > efrm) |
| return; |
| IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE); |
| IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN); |
| |
| ssid_verify_action = ieee80211_verify_ssid(vap, ni, wh, ssid, subtype); |
| switch (ssid_verify_action) { |
| case IEEE80211_VERIFY_SSID_ACTION_NODE_DEL_AND_RETURN: |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_IE_INVALID); |
| ieee80211_node_leave(ni); |
| return; |
| case IEEE80211_VERIFY_SSID_ACTION_RETURN: |
| return; |
| case IEEE80211_VERIFY_SSID_ACTION_NO: |
| default: |
| break; |
| } |
| |
| if (ni == vap->iv_bss) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_ASSOC, wh->i_addr2, |
| "deny %s request, sta not authenticated", |
| reassoc ? "reassoc" : "assoc"); |
| ieee80211_send_error(ni, wh->i_addr2, |
| IEEE80211_FC0_SUBTYPE_DEAUTH, |
| IEEE80211_REASON_ASSOC_NOT_AUTHED); |
| vap->iv_stats.is_rx_assoc_notauth++; |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_ASSOC_FAILS, 1); |
| return; |
| } |
| |
| if (is_assoc_limit_reached(ic, vap)) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_ASSOC, wh->i_addr2, |
| "%s request denied, assoc limit %d reached, sta cnt %d", |
| reassoc ? "reassoc" : "assoc", |
| ic->ic_ssid_grp[vap->iv_ssid_group].limit, |
| ic->ic_ssid_grp[vap->iv_ssid_group].assocs); |
| IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_TOOMANY); |
| ieee80211_node_leave(ni); |
| vap->iv_stats.is_rx_assoc_toomany++; |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_ASSOC_FAILS, 1); |
| |
| return; |
| } |
| |
| #if defined(PLATFORM_QFDR) |
| if (ic->ic_reject_auth & QFDR_F_REJECT_AUTH) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_ASSOC, wh->i_addr2, |
| "%s request rejected", |
| reassoc ? "reassoc" : "assoc"); |
| IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_TOOMANY); |
| ieee80211_node_leave(ni); |
| /* no need to update statistics */ |
| return; |
| } |
| #endif /* PLATFORM_QFDR */ |
| |
| memset((u_int8_t*)&rsn_parm, 0, sizeof(rsn_parm)); |
| |
| /* Validate power capability */ |
| /* power capability:ID|LEN|MIN TX CAP|MAX TX CAP */ |
| if ((ic->ic_flags & IEEE80211_F_DOTH) && (ic->ic_flags_ext & IEEE80211_FEXT_TPC) && |
| pwr_cap != NULL) { |
| TPC_DBG(vap, "[AP]channel=%d/regulatory power=%d/power constraint=%d/local max tx power=%d\n", |
| ic->ic_bsschan->ic_ieee, |
| ic->ic_bsschan->ic_maxregpower, |
| ic->ic_pwr_constraint, |
| ic->ic_bsschan->ic_maxregpower - ic->ic_pwr_constraint); |
| |
| /* check Power Cap IE Length */ |
| if (pwr_cap->len != 2) { |
| TPC_DBG(vap, "[%s] invalid power capability, Discard it!\n", |
| ether_sprintf(wh->i_addr2)); |
| IEEE80211_SEND_MGMT(ni, |
| IEEE80211_FC0_SUBTYPE_DEAUTH, |
| IEEE80211_REASON_IE_INVALID); |
| ieee80211_node_leave(ni); |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_ASSOC_FAILS, 1); |
| return; |
| } |
| else { |
| local_max_txpwr = ic->ic_bsschan->ic_maxregpower - ic->ic_pwr_constraint; |
| min_txpwr = pwr_cap->min_txpwr; |
| max_txpwr = pwr_cap->max_txpwr; |
| TPC_DBG(vap, "[RECV ASSOC REQ]min power(%d) max power(%d) for mac(%s)\n", |
| min_txpwr, max_txpwr, ether_sprintf(wh->i_addr2)); |
| if (min_txpwr > max_txpwr) { |
| TPC_DBG(vap, "[AP] Warning,sta min power(%d) larger than max power(%d)!\n", |
| min_txpwr, |
| max_txpwr); |
| } |
| if (min_txpwr > local_max_txpwr) { |
| TPC_DBG(vap, "[AP] power capability unacceptable(min tx power=%d max tx power=%d), discard it!\n", |
| min_txpwr, |
| max_txpwr); |
| IEEE80211_SEND_MGMT(ni, |
| IEEE80211_FC0_SUBTYPE_DEAUTH, |
| IEEE80211_REASON_DISASSOC_BAD_POWER); |
| ieee80211_node_leave(ni); |
| vap->iv_stats.is_rx_assoc_capmismatch++; |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_ASSOC_FAILS, 1); |
| return; |
| } |
| else { |
| ni->ni_tpc_info.tpc_sta_cap.min_txpow = min_txpwr; |
| ni->ni_tpc_info.tpc_sta_cap.max_txpow = max_txpwr; |
| } |
| } |
| } |
| |
| /* Validate association security credentials */ |
| if ((rsn != NULL) || (wpa != NULL) || (osen != NULL)) { |
| /* |
| * Parse WPA information element. Note that |
| * we initialize the param block from the node |
| * state so that information in the IE overrides |
| * our defaults. The resulting parameters are |
| * installed below after the association is assured. |
| */ |
| |
| rsn_parm = ni->ni_rsn; |
| |
| if (rsn != NULL) { |
| reason = ieee80211_parse_rsn(vap, rsn, &rsn_parm, wh); |
| } else { |
| if (wpa != NULL) |
| reason = ieee80211_parse_wpa(vap, wpa, &rsn_parm, wh); |
| else |
| reason = ieee80211_parse_osen(vap, osen, &rsn_parm, wh); |
| } |
| |
| if (reason != 0) { |
| IEEE80211_DPRINTF(vap, |
| IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, |
| "[%s] WPA/RSN IE mismatch in association request\n", |
| ether_sprintf(wh->i_addr2)); |
| IEEE80211_SEND_MGMT(ni, |
| IEEE80211_FC0_SUBTYPE_DEAUTH, reason); |
| ieee80211_node_leave(ni); |
| /* XXX distinguish WPA/RSN? */ |
| vap->iv_stats.is_rx_assoc_badwpaie++; |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_ASSOC_FAILS, 1); |
| return; |
| } |
| IEEE80211_NOTE_MAC(vap, |
| IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, |
| wh->i_addr2, |
| "%s ie: mc %u/%u uc %u/%u key %u caps 0x%x", |
| rsn ? "RSN" : |
| wpa ? "WPA" : "OSEN", |
| rsn_parm.rsn_mcastcipher, rsn_parm.rsn_mcastkeylen, |
| rsn_parm.rsn_ucastcipher, rsn_parm.rsn_ucastkeylen, |
| rsn_parm.rsn_keymgmt, rsn_parm.rsn_caps); |
| |
| /* Reject association if using TKIP and HT */ |
| if ((rsn_parm.rsn_ucastcipher == IEEE80211_CIPHER_TKIP) && (htcap != NULL)) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, wh->i_addr2, |
| "deny %s request, TKIP and HT rates requested", |
| reassoc ? "reassoc" : "assoc"); |
| IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_OTHER); |
| ieee80211_node_leave(ni); |
| vap->iv_stats.is_rx_assoc_tkiphtreject++; |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_ASSOC_FAILS, 1); |
| return; |
| } |
| } |
| |
| /* discard challenge after association */ |
| if (ni->ni_challenge != NULL) { |
| FREE(ni->ni_challenge, M_DEVBUF); |
| ni->ni_challenge = NULL; |
| } |
| /* 802.11 spec says to ignore station's privacy bit */ |
| if ((capinfo & IEEE80211_CAPINFO_ESS) == 0) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, wh->i_addr2, |
| "deny %s request, capability mismatch 0x%x", |
| reassoc ? "reassoc" : "assoc", capinfo); |
| IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_CAPINFO); |
| ieee80211_node_leave(ni); |
| vap->iv_stats.is_rx_assoc_capmismatch++; |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_ASSOC_FAILS, 1); |
| return; |
| } |
| rate = ieee80211_setup_rates(ni, rates, xrates, |
| IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | |
| IEEE80211_F_DONEGO | IEEE80211_F_DODEL); |
| |
| /* |
| * If constrained to 11g-only stations reject an |
| * 11b-only station. We cheat a bit here by looking |
| * at the max negotiated xmit rate and assuming anyone |
| * with a best rate <24Mb/s is an 11b station. |
| */ |
| if ((rate & IEEE80211_RATE_BASIC) || |
| ((vap->iv_flags & IEEE80211_F_PUREG) && rate < 48)) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, wh->i_addr2, |
| "deny %s request, rate set mismatch", |
| reassoc ? "reassoc" : "assoc"); |
| IEEE80211_SEND_MGMT(ni, resp, |
| IEEE80211_STATUS_BASIC_RATE); |
| ieee80211_node_leave(ni); |
| vap->iv_stats.is_rx_assoc_norate++; |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_ASSOC_FAILS, 1); |
| return; |
| } |
| |
| if (ni->ni_associd != 0 && |
| IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan)) { |
| if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) |
| != (capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, |
| wh->i_addr2, |
| "deny %s request, short slot time " |
| "capability mismatch 0x%x", |
| reassoc ? "reassoc" : "assoc", capinfo); |
| IEEE80211_SEND_MGMT(ni, resp, |
| IEEE80211_STATUS_CAPINFO); |
| ieee80211_node_leave(ni); |
| vap->iv_stats.is_rx_assoc_capmismatch++; |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_ASSOC_FAILS, 1); |
| return; |
| } |
| } |
| |
| ni->ni_vendor = PEER_VENDOR_NONE; |
| if (qtnie != NULL) { |
| ni->ni_vendor = PEER_VENDOR_QTN; |
| } |
| if (bcmie != NULL) { |
| ni->ni_vendor = PEER_VENDOR_BRCM; |
| } |
| if (rlnk != NULL) { |
| ni->ni_vendor = PEER_VENDOR_RLNK; |
| } |
| if (rtkie != NULL) { |
| ni->ni_vendor = PEER_VENDOR_RTK; |
| } |
| |
| ieee80211_input_assoc_req_qtnie(ni, vap, qtnie); |
| |
| if (IEEE80211_IS_CHAN_ANYN(ic->ic_curchan)) { |
| /* |
| * Assoc request ignored when 11ac_and_11n flag is set and |
| * vht or ht capabilities are not-present in Assoc req/reassoc req packet. |
| */ |
| if (!ieee80211_phy_mode_allowed(vap, vhtcap, htcap)) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, wh->i_addr2, |
| "deny %s request, VHT or HT capability IE are not present = %d", |
| reassoc ? "reassoc" : "assoc", error); |
| IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_OTHER); |
| ieee80211_node_leave(ni); |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_ASSOC_FAILS, 1); |
| return; |
| } |
| |
| if (htcap != NULL) { |
| ni->ni_flags |= IEEE80211_NODE_HT; |
| |
| /* record capabilities, mark node as capable of HT */ |
| error = ieee80211_setup_htcap(ni, htcap); |
| if (error) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, wh->i_addr2, |
| "deny %s request, ht capability mismatch error = %d", |
| reassoc ? "reassoc" : "assoc", error); |
| IEEE80211_SEND_MGMT(ni, resp, |
| IEEE80211_STATUS_HT_FEATURE); |
| ieee80211_node_leave(ni); |
| vap->iv_stats.is_rx_assoc_nohtcap++; |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_ASSOC_FAILS, 1); |
| return; |
| } |
| |
| if (vhtcap && IS_IEEE80211_DUALBAND_VHT_ENABLED(ic)) { |
| ieee80211_parse_vhtcap(ni, vhtcap); |
| ni->ni_flags |= IEEE80211_NODE_VHT; |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "VHT Enabled: ni_flags = 0x%04x\n", |
| ni->ni_flags); |
| } else { |
| ni->ni_flags &= ~IEEE80211_NODE_VHT; |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_VHT, |
| "VHT Disabled: ni_flags = 0x%04x\n", |
| ni->ni_flags); |
| } |
| } else { |
| /* |
| * Flush any state from a previous association. |
| */ |
| memset(&ni->ni_htcap, 0, sizeof(struct ieee80211_htcap)); |
| memset(&ni->ni_htinfo, 0, sizeof(struct ieee80211_htinfo)); |
| |
| ni->ni_flags &= ~IEEE80211_NODE_HT; |
| ni->ni_flags &= ~IEEE80211_NODE_VHT; |
| } |
| } |
| |
| ni->ni_rstamp = rstamp; |
| ni->ni_last_rx = jiffies; |
| ieee80211_scs_node_clean_stats(IEEE80211_SCS_STATE_INIT, ni); |
| ni->ni_raw_bintval = bintval; |
| ni->ni_intval = IEEE80211_BINTVAL_SANITISE(bintval); |
| ni->ni_capinfo = capinfo; |
| ni->ni_chan = ic->ic_curchan; |
| ni->ni_fhdwell = vap->iv_bss->ni_fhdwell; |
| ni->ni_fhindex = vap->iv_bss->ni_fhindex; |
| ni->ni_rsn = rsn_parm; |
| |
| if (wpa != NULL) { |
| /* |
| * Record WPA/RSN parameters for station, mark |
| * node as using WPA and record information element |
| * for applications that require it. |
| */ |
| ieee80211_saveie(&ni->ni_wpa_ie, wpa); |
| } else if (ni->ni_wpa_ie != NULL) { |
| /* |
| * Flush any state from a previous association. |
| */ |
| FREE(ni->ni_wpa_ie, M_DEVBUF); |
| ni->ni_wpa_ie = NULL; |
| } |
| if (rsn != NULL) { |
| /* |
| * Record WPA/RSN parameters for station, mark |
| * node as using WPA and record information element |
| * for applications that require it. |
| */ |
| ieee80211_saveie(&ni->ni_rsn_ie, rsn); |
| } else if (ni->ni_rsn_ie != NULL) { |
| /* |
| * Flush any state from a previous association. |
| */ |
| FREE(ni->ni_rsn_ie, M_DEVBUF); |
| ni->ni_rsn_ie = NULL; |
| } |
| if (osen != NULL) { |
| /* |
| * Record OSEN parameters for station, mark |
| * node as using OSEN and record information element |
| * for applications that require it. |
| */ |
| ieee80211_saveie(&ni->ni_osen_ie, osen); |
| } else if (ni->ni_osen_ie != NULL) { |
| /* |
| * Flush any state from a previous association. |
| */ |
| FREE(ni->ni_osen_ie, M_DEVBUF); |
| ni->ni_osen_ie = NULL; |
| } |
| if (ni->ni_rx_md_ie != NULL) { |
| /* |
| * Flush any state from a previous association. |
| */ |
| FREE(ni->ni_rx_md_ie, M_DEVBUF); |
| ni->ni_rx_md_ie = NULL; |
| } |
| if (mdie != NULL) { |
| ieee80211_saveie(&ni->ni_rx_md_ie, mdie); |
| } |
| |
| if (coex != NULL) { |
| ni->ni_coex = coex->coex_param; |
| } |
| |
| if ((ni->ni_htcap.cap & IEEE80211_HTCAP_C_CHWIDTH40) && |
| IEEE80211_IS_11NG_40(ic)) |
| ni->ni_obss_scan = IEEE80211_NODE_OBSS_CAPABLE | |
| IEEE80211_NODE_OBSS_RUNNING; |
| |
| if (wme != NULL) { |
| /* |
| * Record WME parameters for station, mark node |
| * as capable of QoS and record information |
| * element for applications that require it. |
| */ |
| ieee80211_saveie(&ni->ni_wme_ie, wme); |
| if (ieee80211_parse_wmeie(wme, wh, ni) > 0) |
| ni->ni_flags |= IEEE80211_NODE_QOS; |
| } else if (ni->ni_wme_ie != NULL) { |
| /* |
| * Flush any state from a previous association. |
| */ |
| FREE(ni->ni_wme_ie, M_DEVBUF); |
| ni->ni_wme_ie = NULL; |
| ni->ni_flags &= ~IEEE80211_NODE_QOS; |
| } |
| if (ath != NULL) { |
| ieee80211_saveath(ni, ath); |
| ni->ni_vendor = PEER_VENDOR_ATH; |
| } else if (ni->ni_ath_ie != NULL) { |
| /* |
| * Flush any state from a previous association. |
| */ |
| FREE(ni->ni_ath_ie, M_DEVBUF); |
| ni->ni_ath_ie = NULL; |
| ni->ni_ath_flags = 0; |
| } |
| if (bcmie != NULL) { |
| ni->ni_brcm_flags = 1; |
| } else if (ni->ni_brcm_flags != 0) { |
| ni->ni_brcm_flags = 0; |
| } |
| |
| if (wscie != NULL) { |
| ieee80211_saveie(&ni->ni_wsc_ie, wscie); |
| } else if (ni->ni_wsc_ie != NULL) { |
| FREE(ni->ni_wsc_ie, M_DEVBUF); |
| ni->ni_wsc_ie = NULL; |
| } |
| |
| if (qtn_pairing_ie != NULL) { |
| ieee80211_saveie(&ni->ni_qtn_pairing_ie, (u_int8_t *)qtn_pairing_ie); |
| } else if (ni->ni_qtn_pairing_ie != NULL) { |
| FREE(ni->ni_qtn_pairing_ie, M_DEVBUF); |
| ni->ni_qtn_pairing_ie = NULL; |
| } |
| if (ni->ni_rx_ft_ie != NULL) { |
| FREE(ni->ni_rx_ft_ie, M_DEVBUF); |
| ni->ni_rx_ft_ie = NULL; |
| } |
| if (ftie != NULL) { |
| ieee80211_saveie(&ni->ni_rx_ft_ie, ftie); |
| } |
| |
| if (supp_chan_ie != NULL) |
| ieee80211_parse_supp_chan(ni, supp_chan_ie); |
| |
| /* Send TGf L2UF frame on behalf of newly associated station */ |
| ieee80211_deliver_l2uf(ni); |
| |
| /* Clean up old block ack state */ |
| ieee80211_node_ba_state_clear(ni); |
| |
| /* Add the local implicit BA values */ |
| if (qtnie && ni->ni_implicit_ba_valid) { |
| ieee80211_node_implicit_ba_setup(ni); |
| } |
| |
| /* Quantenna peers are 4 address capable */ |
| if (!qtnie) { |
| ni->ni_qtn_flags |= QTN_IS_NOT_4ADDR_CAPABLE_NODE; |
| } |
| |
| /* Check if it is a broadcom station */ |
| if (unlikely(bcmie)) { |
| ni->ni_qtn_flags |= QTN_IS_BCM_NODE; |
| if (interworking_ie_present && |
| (ni->ni_flags & IEEE80211_NODE_VHT)) { |
| ni->ni_qtn_flags |= QTN_IS_GALAXY_NOTE_4_NODE; |
| } |
| } |
| |
| if (unlikely(ieee80211_node_is_realtek(ni))) { |
| ni->ni_qtn_flags |= QTN_IS_REALTEK_NODE; |
| } |
| |
| if (unlikely(ieee80211_node_is_opti_node(ni))) { |
| ni->ni_qtn_flags |= QTN_OPTI_NODE; |
| } |
| |
| /* if it an Intel 5x00/620x client, we need to mark the flag to use 2 tx chain only |
| * before informing MuC |
| */ |
| if ((ni->ni_flags & IEEE80211_NODE_HT) || (ni->ni_flags & IEEE80211_NODE_VHT)) { |
| ieee80211_blacklist_ba(ni, 0); |
| } else { |
| /* No BBF for legacy clients */ |
| ni->ni_bbf_disallowed = 1; |
| } |
| |
| /* TODO: Probably we need to filter out vendor sugn from ni_qtn_flags and move it to ni_vendor |
| * to remove duplicate signs of vendor. |
| */ |
| if (unlikely((ni->ni_qtn_flags & QTN_IS_INTEL_NODE || |
| ni->ni_qtn_flags & QTN_IS_INTEL_5100_NODE || |
| ni->ni_qtn_flags & QTN_IS_INTEL_5300_NODE) && |
| ni->ni_vendor == PEER_VENDOR_NONE)) { |
| |
| ni->ni_vendor = PEER_VENDOR_INTEL; |
| } |
| |
| if (vhtcap) { |
| enum ieee80211_vhtop_chanwidth assoc_vhtop_bw; |
| |
| switch (IEEE80211_VHTCAP_GET_CHANWIDTH((struct ieee80211_ie_vhtcap*)vhtcap)) { |
| case IEEE80211_VHTCAP_CW_160M: |
| assoc_vhtop_bw = IEEE80211_VHTOP_CHAN_WIDTH_160MHZ; |
| break; |
| |
| case IEEE80211_VHTCAP_CW_160_AND_80P80M: |
| assoc_vhtop_bw = IEEE80211_VHTOP_CHAN_WIDTH_80PLUS80MHZ; |
| break; |
| |
| case IEEE80211_VHTCAP_CW_80M_ONLY: |
| default: |
| assoc_vhtop_bw = IEEE80211_VHTOP_CHAN_WIDTH_80MHZ; |
| break; |
| } |
| ni->ni_vhtop.chanwidth = min(ic->ic_vhtop.chanwidth, assoc_vhtop_bw); |
| |
| if (IS_IEEE80211_11NG_VHT_ENABLED(ic)) { |
| assoc_vhtop_bw = IEEE80211_VHTOP_CHAN_WIDTH_20_40MHZ; |
| ni->ni_vhtop.chanwidth = min(ic->ic_vhtop_24g.chanwidth, assoc_vhtop_bw); |
| } |
| } |
| |
| if (unlikely(rrm_enabled)) { |
| uint8_t rrm_enabled_byte0 = *(rrm_enabled + 2); |
| if (rrm_enabled_byte0 & IEEE80211_RM_NEIGH_REPORT_CAP) |
| ni->ni_rrm_capability |= IEEE80211_NODE_NEIGHBOR_REPORT_CAPABLE; |
| if (rrm_enabled_byte0 & IEEE80211_RM_BEACON_PASSIVE_REPORT_CAP) |
| ni->ni_rrm_capability |= IEEE80211_NODE_BEACON_PASSIVE_REPORT_CAPABLE; |
| if (rrm_enabled_byte0 & IEEE80211_RM_BEACON_ACTIVE_REPORT_CAP) |
| ni->ni_rrm_capability |= IEEE80211_NODE_BEACON_ACTIVE_REPORT_CAPABLE; |
| if (rrm_enabled_byte0 & IEEE80211_RM_BEACON_TABLE_REPORT_CAP) |
| ni->ni_rrm_capability |= IEEE80211_NODE_BEACON_TABLE_REPORT_CAPABLE; |
| } |
| |
| if (((ni->ni_coex & WLAN_20_40_BSS_COEX_40MHZ_INTOL) || |
| (ni->ni_coex & WLAN_20_40_BSS_COEX_20MHZ_WIDTH_REQ) || |
| (ni->ni_htcap.cap & IEEE80211_HTCAP_C_40_INTOLERANT)) && |
| (ic->ic_20_40_coex_enable)) { |
| ieee80211_change_bw(vap, BW_HT20, 0); |
| ic->ic_coex_stats_update(ic, WLAN_COEX_STATS_BW_ASSOC); |
| } |
| /* Mobility domain present, then let hostapd handle rest of association process */ |
| if (vap->iv_mdid) { |
| /* FT assoc/reassoc request */ |
| if (ni->ni_rx_md_ie != NULL) { |
| uint16_t mdid = *((uint16_t *)(ni->ni_rx_md_ie + 2)); |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "%pM %s request mdid 0x%x\n", wh->i_addr2, |
| reassoc ? "reassoc" : "assoc", mdid); |
| if (mdid == vap->iv_mdid) { |
| forward_mgmt_to_app_for_further_processing(vap, subtype, |
| skb, wh); |
| return; |
| } else { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "%pM %s request mdid mismatch request 0x%x " |
| "vap's mdid 0x%x\n", |
| wh->i_addr2, reassoc ? "reassoc" : "assoc", |
| mdid, vap->iv_mdid); |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, 13); |
| ieee80211_node_leave(ni); |
| vap->iv_stats.is_rx_assoc_capmismatch++; |
| mlme_stats_delayed_update(wh->i_addr2, |
| MLME_STAT_ASSOC_FAILS, 1); |
| return; |
| } |
| } else { |
| /* non FT assoc/reassoc request , let hostapd handle rest of association*/ |
| forward_mgmt_to_app_for_further_processing(vap, subtype, skb, wh); |
| return; |
| } |
| } |
| |
| ieee80211_node_join(ni, resp); |
| |
| if (opmode_notif_ie) { |
| struct ieee80211_ie_vhtop_notif *ie = |
| (struct ieee80211_ie_vhtop_notif *)opmode_notif_ie; |
| |
| uint8_t opmode = recalc_opmode(ni, ie->vhtop_notif_mode); |
| ieee80211_param_to_qdrv(ni->ni_vap, IEEE80211_PARAM_NODE_OPMODE, |
| opmode, ni->ni_macaddr, IEEE80211_ADDR_LEN); |
| ni->ni_vhtop_notif_mode = ie->vhtop_notif_mode; |
| } |
| |
| ieee80211_update_current_mode(ni); |
| |
| mlme_stats_delayed_update(wh->i_addr2, MLME_STAT_ASSOC, 1); |
| break; |
| } |
| |
| case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: |
| case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: { |
| uint16_t capinfo; |
| uint16_t associd; |
| uint16_t status; |
| uint8_t ridx; |
| |
| if (vap->iv_opmode != IEEE80211_M_STA || vap->iv_state != IEEE80211_S_ASSOC) { |
| if (vap->iv_state < IEEE80211_S_ASSOC) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_ASSOC, |
| "%s: Received %sassoc resp when not authed - send deauth\n", |
| __func__, |
| subtype == IEEE80211_FC0_SUBTYPE_REASSOC_RESP ? "re" : ""); |
| ieee80211_send_error(ni, wh->i_addr2, |
| IEEE80211_FC0_SUBTYPE_DEAUTH, |
| IEEE80211_REASON_NOT_AUTHED); |
| } |
| vap->iv_stats.is_rx_mgtdiscard++; |
| return; |
| } |
| |
| /* |
| * asresp frame format |
| * [2] capability information |
| * [2] status |
| * [2] association ID |
| * [tlv] supported rates |
| * [tlv] extended supported rates |
| * [tlv] WME |
| */ |
| IEEE80211_VERIFY_LENGTH(efrm - frm, 6); |
| ni = vap->iv_bss; |
| capinfo = le16toh(*(__le16 *)frm); |
| frm += 2; |
| status = le16toh(*(__le16 *)frm); |
| frm += 2; |
| if (status != 0) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, |
| wh->i_addr2, |
| "%sassoc failed (reason %d)", |
| ISREASSOC(subtype) ? "re" : "", status); |
| vap->iv_stats.is_rx_auth_fail++; /* XXX */ |
| ieee80211_new_state(vap, IEEE80211_S_SCAN, |
| IEEE80211_SCAN_FAIL_STATUS); |
| return; |
| } |
| associd = le16toh(*(__le16 *)frm); |
| if ((IEEE80211_AID(associd) == 0) || (IEEE80211_AID(associd) > IEEE80211_AID_MAX)) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "%s: invalid associd %u\n", __func__, IEEE80211_AID(associd)); |
| IEEE80211_SEND_MGMT(ni, |
| IEEE80211_FC0_SUBTYPE_DISASSOC, |
| IEEE80211_REASON_UNSPECIFIED); |
| return; |
| } |
| frm += 2; |
| |
| rates = xrates = wme = NULL; |
| while (frm < efrm) { |
| /* |
| * Do not discard frames containing proprietary Agere |
| * elements 128 and 129, as the reported element length |
| * is often wrong. Skip rest of the frame, since we can |
| * not rely on the given element length making it impossible |
| * to know where the next element starts. |
| */ |
| if ((*frm == IEEE80211_ELEMID_AGERE1) || |
| (*frm == IEEE80211_ELEMID_AGERE2)) { |
| frm = efrm; |
| continue; |
| } |
| |
| IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1]); |
| switch (*frm) { |
| case IEEE80211_ELEMID_RATES: |
| rates = frm; |
| break; |
| case IEEE80211_ELEMID_HTCAP: |
| htcap = frm; |
| break; |
| case IEEE80211_ELEMID_HTINFO: |
| htinfo = frm; |
| break; |
| case IEEE80211_ELEMID_VHTCAP: |
| vhtcap = frm; |
| break; |
| case IEEE80211_ELEMID_VHTOP: |
| vhtop = frm; |
| break; |
| case IEEE80211_ELEMID_XRATES: |
| xrates = frm; |
| break; |
| case IEEE80211_ELEMID_OPMOD_NOTIF: |
| opmode_notif_ie = frm; |
| break; |
| case IEEE80211_ELEMID_OBSS_SCAN: |
| obss_scan = frm; |
| break; |
| case IEEE80211_ELEMID_VENDOR: |
| if (iswmeoui(frm)) { |
| wme = frm; |
| } else if (isqtnie(frm)) { |
| qtnie = (struct ieee80211_ie_qtn *)frm; |
| } else if (isbroadcomoui(frm)) { |
| bcmie = frm; |
| if (isbrcmvhtoui(frm)) { |
| vhtcap = ieee80211_get_vhtcap_from_brcmvht(ni, frm); |
| vhtop = ieee80211_get_vhtop_from_brcmvht(ni, frm); |
| } |
| } else if (isbroadcomoui2(frm)) { |
| bcmie = frm; |
| } else if (isqtnpairoui(frm)) { |
| qtn_pairing_ie = (struct ieee80211_ie_qtn_pairing *)frm; |
| #ifdef CONFIG_QVSP |
| } else if (isvspie(frm)) { |
| vspie = (struct ieee80211_ie_vsp *)frm; |
| } else if (isqtnwmeie(frm)) { |
| /* override standard WME IE */ |
| struct ieee80211_ie_qtn_wme *qwme = (struct ieee80211_ie_qtn_wme *)frm; |
| IEEE80211_NOTE_MAC(vap, |
| IEEE80211_MSG_ASSOC | IEEE80211_MSG_WME | IEEE80211_MSG_VSP, |
| wh->i_addr2, "%s: found QTN WME IE, version %u\n", |
| __func__, qwme->qtn_wme_ie_version); |
| wme = (uint8_t *)&qwme->qtn_wme_ie; |
| #endif |
| } |
| break; |
| case IEEE80211_ELEMID_EXTCAP: |
| extcap = frm; |
| if (extcap != NULL) |
| ieee80211_parse_extcap(ni, extcap, wh->i_addr3); |
| break; |
| } |
| frm += frm[1] + 2; |
| } |
| if (frm > efrm) |
| return; |
| |
| ni->ni_vendor = PEER_VENDOR_NONE; |
| if (qtnie != NULL) { |
| ni->ni_vendor = PEER_VENDOR_QTN; |
| } |
| if (bcmie != NULL) { |
| ni->ni_vendor = PEER_VENDOR_BRCM; |
| ni->ni_qtn_flags |= QTN_IS_BCM_NODE; |
| } |
| |
| ieee80211_input_assoc_resp_qtnie(ni, vap, qtnie); |
| #ifdef CONFIG_QVSP |
| ieee80211_input_assoc_resp_vspie(vap, vspie, efrm); |
| #endif |
| |
| IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE); |
| rate = ieee80211_setup_rates(ni, rates, xrates, |
| IEEE80211_F_DOSORT | |
| IEEE80211_F_DONEGO | IEEE80211_F_DODEL); |
| if (rate & IEEE80211_RATE_BASIC) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, |
| wh->i_addr2, |
| "%sassoc failed (rate set mismatch)", |
| ISREASSOC(subtype) ? "re" : ""); |
| vap->iv_stats.is_rx_assoc_norate++; |
| ieee80211_new_state(vap, IEEE80211_S_SCAN, |
| IEEE80211_SCAN_FAIL_STATUS); |
| return; |
| } |
| |
| /* check to see if need to set up ERP flag */ |
| if (IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && |
| ieee80211_iserp_rateset(ic, &ni->ni_rates)) { |
| IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, |
| "%s STA is joing an ERP AP, set the ERP flag\n", __func__); |
| ni->ni_flags |= IEEE80211_NODE_ERP; |
| } |
| |
| ni->ni_capinfo = capinfo; |
| ni->ni_associd = associd; |
| if (wme != NULL && |
| ieee80211_parse_wmeparams(vap, wme, wh, &qosinfo) >= 0) { |
| ni->ni_flags |= IEEE80211_NODE_QOS; |
| ieee80211_wme_updateparams(vap, 0); |
| } else |
| ni->ni_flags &= ~IEEE80211_NODE_QOS; |
| /* |
| * Configure state now that we are associated. |
| * |
| * XXX may need different/additional driver callbacks? |
| */ |
| if (IEEE80211_IS_CHAN_A(ic->ic_curchan) || |
| ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE) && |
| (ic->ic_caps & IEEE80211_C_SHPREAMBLE))) { |
| ic->ic_flags |= IEEE80211_F_SHPREAMBLE; |
| ic->ic_flags &= ~IEEE80211_F_USEBARKER; |
| } else { |
| ic->ic_flags &= ~IEEE80211_F_SHPREAMBLE; |
| ic->ic_flags |= IEEE80211_F_USEBARKER; |
| } |
| ieee80211_set_shortslottime(ic, |
| IEEE80211_IS_CHAN_A(ic->ic_curchan) || |
| (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); |
| /* |
| * Honor ERP protection. |
| * |
| * NB: ni_erp should zero for non-11g operation |
| * but we check the channel characteristics |
| * just in case. |
| */ |
| if (IEEE80211_BG_PROTECT_ENABLED(ic) |
| && (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) |
| || IEEE80211_IS_CHAN_11NG(ic->ic_curchan)) |
| && (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) { |
| ic->ic_flags |= IEEE80211_F_USEPROT; |
| /* tell Muc to use ERP cts-to-self mechanism now */ |
| ic->ic_set_11g_erp(vap, 1); |
| } else { |
| ic->ic_flags &= ~IEEE80211_F_USEPROT; |
| /* tell Muc to turn off ERP now */ |
| ic->ic_set_11g_erp(vap, 0); |
| } |
| |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, |
| "%sassoc success: %s preamble, %s slot time ampdu density %d %s%s%s%s%s%s%s", |
| ISREASSOC(subtype) ? "re" : "", |
| (ic->ic_flags&IEEE80211_F_SHPREAMBLE) && |
| (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE) ? "short" : "long", |
| ic->ic_flags&IEEE80211_F_SHSLOT ? "short" : "long", |
| ni->ni_htcap.mpduspacing, |
| ic->ic_flags&IEEE80211_F_USEPROT ? ", protection" : "", |
| ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : "", |
| IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_TURBOP) ? |
| ", turbo" : "", |
| IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_COMP) ? |
| ", compression" : "", |
| IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF) ? |
| ", fast-frames" : "", |
| IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_XR) ? |
| ", XR" : "", |
| IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_AR) ? |
| ", AR" : "" |
| ); |
| |
| /* Sanity check - make sure bridge mode advertisement is as expected */ |
| if (vap->iv_is_qtn_dev) { |
| if (qtnie) { |
| ieee80211_saveie(&ni->ni_qtn_assoc_ie, (u_int8_t *)qtnie); |
| if (((qtnie->qtn_ie_flags & IEEE80211_QTN_BRIDGEMODE) |
| == IEEE80211_QTN_BRIDGEMODE) != |
| ((vap->iv_flags_ext & IEEE80211_FEXT_WDS) == IEEE80211_FEXT_WDS)) { |
| |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "%s: QTN IE in assoc resp is invalid, %02x/%08x\n", __func__, |
| qtnie->qtn_ie_flags, vap->iv_flags_ext); |
| } |
| ni->ni_implicit_ba = 0; |
| /* Implicit BA flags for the AP */ |
| if (IEEE80211_QTN_IE_GE_V2(qtnie) && |
| (ni->ni_flags & IEEE80211_NODE_HT) && !sta_pure_tkip) { |
| ni->ni_implicit_ba = qtnie->qtn_ie_implicit_ba_tid; |
| ni->ni_implicit_ba_valid = 1; |
| } |
| } else { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "%s: QTN IE in assoc resp is missing\n", __func__); |
| } |
| } else if (qtnie) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, |
| "%s: Unexpected QTN IE in assoc resp\n", __func__); |
| } |
| |
| if (qtn_pairing_ie != NULL) { |
| ieee80211_saveie(&ni->ni_qtn_pairing_ie, (u_int8_t *)qtn_pairing_ie); |
| } else if (ni->ni_qtn_pairing_ie != NULL) { |
| FREE(ni->ni_qtn_pairing_ie, M_DEVBUF); |
| ni->ni_qtn_pairing_ie = NULL; |
| } |
| |
| /* 11n */ |
| /* Both Q-Station and AP should support HT */ |
| if ((ic->ic_curmode >= IEEE80211_MODE_11NA) && (htcap != NULL) && !sta_pure_tkip) { |
| ni->ni_flags |= IEEE80211_NODE_HT; |
| ieee80211_parse_htcap(ni, htcap); |
| ieee80211_parse_htinfo(ni, htinfo); |
| /* keep only the rates supported by both parties */ |
| ridx = ieee80211_fix_ht_rate(ni, IEEE80211_F_DOFRATE|IEEE80211_F_DODEL|IEEE80211_F_DOXSECT); |
| } else { |
| /* |
| * Flush any state from a previous association. |
| */ |
| memset(&ni->ni_htcap, 0, sizeof(struct ieee80211_htcap)); |
| memset(&ni->ni_htinfo, 0, sizeof(struct ieee80211_htinfo)); |
| ni->ni_flags &= ~IEEE80211_NODE_HT; |
| } |
| |
| if (obss_scan) { |
| memset(&ni->ni_obss_ie, 0, sizeof(struct ieee80211_obss_scan_ie)); |
| memcpy(&ni->ni_obss_ie, obss_scan, sizeof(struct ieee80211_obss_scan_ie)); |
| } |
| |
| ieee80211_input_sta_vht_set(ni, vap, vhtcap, vhtop, !sta_pure_tkip); |
| ieee80211_node_ba_state_clear(ni); |
| /* Apply the implicit BA parameters to the local structure. |
| * Prevents the sending of addba request to the other side of the link. |
| */ |
| if (ni->ni_implicit_ba_valid) { |
| ieee80211_node_implicit_ba_setup(ni); |
| } |
| |
| if ((ic->ic_flags & IEEE80211_F_DOTH) && |
| (ic->ic_flags_ext & IEEE80211_FEXT_TPC) && |
| (ni->ni_flags & IEEE80211_NODE_TPC)) { |
| if (vap->iv_local_max_txpow >= ic->ic_curchan->ic_maxpower_normal) { |
| vap->iv_local_max_txpow = ic->ic_curchan->ic_maxpower_normal; |
| } else if (vap->iv_local_max_txpow < ic->ic_curchan->ic_minpower_normal) { |
| vap->iv_local_max_txpow = ic->ic_curchan->ic_minpower_normal; |
| } else { |
| ieee80211_update_tx_power(ic, vap->iv_local_max_txpow); |
| } |
| } |
| |
| ni->ni_vhtop_notif_mode = IEEE80211_OPMODE_NOTIFY_INVALID; |
| if (opmode_notif_ie) { |
| struct ieee80211_ie_vhtop_notif *ie = |
| (struct ieee80211_ie_vhtop_notif *)opmode_notif_ie; |
| |
| uint8_t opmode = recalc_opmode(ni, ie->vhtop_notif_mode); |
| ieee80211_param_to_qdrv(ni->ni_vap, IEEE80211_PARAM_NODE_OPMODE, |
| opmode, ni->ni_macaddr, IEEE80211_ADDR_LEN); |
| ni->ni_vhtop_notif_mode = ie->vhtop_notif_mode; |
| } |
| |
| ieee80211_new_state(vap, IEEE80211_S_RUN, subtype); |
| |
| ieee80211_update_current_mode(ni); |
| |
| /*For STA mode, record the start time of association with AP*/ |
| ni->ni_start_time_assoc = get_jiffies_64(); |
| if (IEEE80211_IS_11NG_40(ic) && ic->ic_obss_scan_enable && |
| (ni->ni_htcap.cap & IEEE80211_HTCAP_C_CHWIDTH40) && |
| (ni->ni_obss_ie.param_id)) { |
| ic->ic_obss_timer.function = ieee80211_obss_scan_timer; |
| mod_timer(&ic->ic_obss_timer, |
| jiffies + ni->ni_obss_ie.obss_trigger_interval * HZ); |
| ic->ic_obss_timer.data = (unsigned long)vap; |
| } |
| break; |
| } |
| |
| case IEEE80211_FC0_SUBTYPE_DEAUTH: { |
| u_int16_t reason; |
| |
| if (vap->iv_state == IEEE80211_S_SCAN) { |
| vap->iv_stats.is_rx_mgtdiscard++; |
| return; |
| } |
| /* |
| * deauth frame format |
| * [2] reason |
| */ |
| IEEE80211_VERIFY_LENGTH(efrm - frm, 2); |
| reason = le16toh(*(__le16 *)frm); |
| vap->iv_stats.is_rx_deauth++; |
| IEEE80211_NODE_STAT(ni, rx_deauth); |
| |
| IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, |
| "recv deauthenticate (reason %d) for %s", reason, ether_sprintf(wh->i_addr1)); |
| switch (vap->iv_opmode) { |
| case IEEE80211_M_STA: |
| arg = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; |
| |
| if (!IEEE80211_ADDR_EQ(vap->iv_bss->ni_bssid, wh->i_addr2)) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_AUTH, |
| "drop deauthenticate (reason %d) from unknown addr %pM", |
| reason, wh->i_addr2); |
| break; |
| } |
| |
| /* |
| * Don't keep retrying the auth if we receive a deauth. |
| * Let wpa_supplicant choose the next action. |
| */ |
| if (!ieee80211_tdls_pend_disassociation(vap, IEEE80211_S_INIT, arg)) |
| ieee80211_new_state(vap, IEEE80211_S_INIT, arg); |
| ieee80211_dot11_msg_send(ni->ni_vap, (char *)ni->ni_macaddr, |
| d11_m[IEEE80211_DOT11_MSG_AP_DISCONNECTED], |
| d11_c[IEEE80211_DOT11_MSG_REASON_DEAUTHENTICATED], |
| reason, |
| (reason < DOT11_MAX_REASON_CODE) ? d11_r[reason] : "Reserved", |
| NULL, |
| NULL); |
| break; |
| case IEEE80211_M_HOSTAP: |
| if (!IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_bss->ni_bssid)) { |
| vap->iv_stats.is_rx_mgtdiscard++; |
| return; |
| } |
| if (ni != vap->iv_bss) { |
| mlme_stats_delayed_update(wh->i_addr2, |
| MLME_STAT_DEAUTH, 1); |
| #if defined(CONFIG_QTN_BSA_SUPPORT) |
| if (vap->bsa_status == BSA_STATUS_ACTIVE) |
| ieee80211_bsa_disconnect_event_send(vap, ni, |
| reason, IEEE80211_FC0_SUBTYPE_DEAUTH, |
| BSA_DISCONNECT_PEER_GENERATED); |
| #endif |
| /* Message to indicate STA sent the deauth */ |
| ieee80211_dot11_msg_send(ni->ni_vap, |
| (char *)ni->ni_macaddr, |
| d11_m[IEEE80211_DOT11_MSG_CLIENT_DISCONNECTED], |
| d11_c[IEEE80211_DOT11_MSG_REASON_CLIENT_SENT_DEAUTH], |
| reason, |
| (reason < DOT11_MAX_REASON_CODE) ? |
| d11_r[reason] : "Reserved", |
| NULL, |
| NULL); |
| ieee80211_nofity_sta_require_leave(ni); |
| ieee80211_node_leave(ni); |
| } else { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_AUTH, |
| "Receive deauthenticate (reason %d)" |
| " from unknown addr %pM", |
| reason, wh->i_addr2); |
| } |
| break; |
| default: |
| vap->iv_stats.is_rx_mgtdiscard++; |
| break; |
| } |
| break; |
| } |
| |
| case IEEE80211_FC0_SUBTYPE_DISASSOC: { |
| u_int16_t reason; |
| |
| /* if a disassoc request is received in the un-auth state |
| * a deauth should be sent by the sta */ |
| if (vap->iv_state < IEEE80211_S_ASSOC && vap->iv_opmode == IEEE80211_M_STA) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_ASSOC, |
| "%s: receive disassoc in state-unauthed, send deauth \n", __func__); |
| ieee80211_send_error(ni, wh->i_addr2, |
| IEEE80211_FC0_SUBTYPE_DEAUTH, |
| IEEE80211_REASON_NOT_AUTHED); |
| vap->iv_stats.is_rx_mgtdiscard++; |
| return; |
| } |
| |
| if (vap->iv_state != IEEE80211_S_RUN && |
| vap->iv_state != IEEE80211_S_ASSOC && |
| vap->iv_state != IEEE80211_S_AUTH) { |
| vap->iv_stats.is_rx_mgtdiscard++; |
| return; |
| } |
| /* |
| * disassoc frame format |
| * [2] reason |
| */ |
| IEEE80211_VERIFY_LENGTH(efrm - frm, 2); |
| reason = le16toh(*(__le16 *)frm); |
| vap->iv_stats.is_rx_disassoc++; |
| IEEE80211_NODE_STAT(ni, rx_disassoc); |
| |
| vap->iv_disassoc_reason = reason; |
| IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, |
| "recv disassociate (reason %d)", reason); |
| switch (vap->iv_opmode) { |
| case IEEE80211_M_STA: |
| if (!IEEE80211_ADDR_EQ(vap->iv_bss->ni_bssid, wh->i_addr2)) { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_AUTH, |
| "drop disassociate (reason %d) from unknown addr %pM", |
| reason, wh->i_addr2); |
| break; |
| } |
| |
| if (!ieee80211_tdls_pend_disassociation(vap, IEEE80211_S_ASSOC, 0)) |
| ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); |
| |
| ieee80211_dot11_msg_send(ni->ni_vap, (char *)ni->ni_macaddr, |
| d11_m[IEEE80211_DOT11_MSG_AP_DISCONNECTED], |
| d11_c[IEEE80211_DOT11_MSG_REASON_DISASSOCIATED], |
| reason, |
| (reason < DOT11_MAX_REASON_CODE) ? d11_r[reason] : "Reserved", |
| NULL, |
| NULL); |
| break; |
| case IEEE80211_M_HOSTAP: |
| if (ni != vap->iv_bss) { |
| #if defined(CONFIG_QTN_BSA_SUPPORT) |
| if (vap->bsa_status == BSA_STATUS_ACTIVE) |
| ieee80211_bsa_disconnect_event_send(vap, ni, |
| reason, IEEE80211_FC0_SUBTYPE_DISASSOC, |
| BSA_DISCONNECT_PEER_GENERATED); |
| #endif |
| |
| ieee80211_dot11_msg_send(ni->ni_vap, (char *)wh->i_addr2, |
| d11_m[IEEE80211_DOT11_MSG_CLIENT_DISCONNECTED], |
| d11_c[IEEE80211_DOT11_MSG_REASON_CLIENT_SENT_DISASSOC], |
| reason, |
| (reason < DOT11_MAX_REASON_CODE) ? |
| d11_r[reason] : "Reserved", |
| NULL, |
| NULL); |
| |
| ieee80211_nofity_sta_require_leave(ni); |
| ieee80211_node_leave(ni); |
| mlme_stats_delayed_update(wh->i_addr2, |
| MLME_STAT_DIASSOC, 1); |
| } else { |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_AUTH, |
| "Receive disassociate (reason %d) from unknown addr %pM", |
| reason, wh->i_addr2); |
| } |
| break; |
| default: |
| vap->iv_stats.is_rx_mgtdiscard++; |
| break; |
| } |
| break; |
| } |
| case IEEE80211_FC0_SUBTYPE_ACTION: { |
| struct ieee80211_action *ia; |
| |
| IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action)); |
| ia = (struct ieee80211_action *) (void*)frm; |
| |
| if (vap->iv_state != IEEE80211_S_RUN && |
| vap->iv_state != IEEE80211_S_ASSOC && |
| vap->iv_state != IEEE80211_S_AUTH && |
| !(ic->ic_flags_qtn & IEEE80211_QTN_MONITOR)) { |
| vap->iv_stats.is_rx_mgtdiscard++; |
| return; |
| } |
| if (vap->iv_opmode == IEEE80211_M_HOSTAP && ni == vap->iv_bss && |
| !(IEEE80211_IS_MULTICAST(wh->i_addr1)) && |
| (ia->ia_category != IEEE80211_ACTION_CAT_PUBLIC)) { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "src same as bss"); |
| if (vap->iv_state == IEEE80211_S_RUN) |
| ieee80211_send_error(ni, wh->i_addr2, |
| IEEE80211_FC0_SUBTYPE_DEAUTH, |
| IEEE80211_REASON_NOT_AUTHED); |
| vap->iv_stats.is_rx_notassoc++; |
| return; |
| } |
| |
| memcpy(&ni->ni_action, ia, sizeof(struct ieee80211_action)); |
| |
| vap->iv_stats.is_rx_action++; |
| IEEE80211_NODE_STAT(ni, rx_action); |
| |
| switch (ia->ia_category) { |
| case IEEE80211_ACTION_CAT_PUBLIC: |
| ieee80211_recv_action_public(ni, skb, wh, ia, rssi); |
| break; |
| case IEEE80211_ACTION_CAT_TDLS: |
| IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN, |
| "TDLS %s: Ignoring TDLS MGMT frame\n", __func__); |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, |
| wh, "mgt", "%s frame not allowed", "TDLS Action"); |
| vap->iv_stats.is_rx_mgtdiscard++; |
| break; |
| case IEEE80211_ACTION_CAT_SPEC_MGMT: |
| switch (ia->ia_action) { |
| case IEEE80211_ACTION_S_CHANSWITCHANN: |
| { |
| u_int8_t *csa_tsf_ie = NULL; |
| int return_val = 0; |
| int cur_bw; |
| |
| if ((efrm - frm) >= (sizeof(struct ieee80211_action) + |
| sizeof(struct ieee80211_ie_csa) + |
| sizeof(struct ieee80211_ie_qtn_csa_tsf))) { |
| csa_tsf_ie = frm + sizeof(struct ieee80211_action) + sizeof(struct ieee80211_ie_csa); |
| } |
| |
| ieee80211_param_from_qdrv(ni->ni_vap, IEEE80211_PARAM_BW_SEL_MUC, &cur_bw, NULL, 0); |
| |
| return_val = ieee80211_parse_csaie(ni, frm + sizeof(struct ieee80211_action), csa_tsf_ie, wh); |
| if (QTN_CSAIE_ERR_CHAN_NOT_SUPP == return_val) { |
| if (ieee80211_narrower_bw_supported(ni, frm + sizeof(struct ieee80211_action), cur_bw)) { |
| /* reassociate */ |
| ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); |
| return; |
| } else if (!ieee80211_handle_csa_invalid_channel(ni, wh)) { |
| return; |
| |
| } |
| } else if (ieee80211_wider_bw_supported(ni, frm + sizeof(struct ieee80211_action), cur_bw)) { |
| /* reassociate */ |
| ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); |
| return; |
| } |
| break; |
| } |
| case IEEE80211_ACTION_S_TPC_REQUEST: |
| { |
| struct ieee80211_action_tpc_report tpc_report; |
| struct ieee80211_action_data action_data; |
| |
| if ((ic->ic_flags & IEEE80211_F_DOTH) && (ic->ic_flags_ext & IEEE80211_FEXT_TPC)) { |
| frm = frm + sizeof(struct ieee80211_action); |
| tpc_report.rx_token = *frm++; |
| if (*frm == IEEE80211_ELEMID_TPCREQ) { |
| tpc_report.tx_power = ic->ic_get_local_txpow(ic); |
| ic->ic_get_local_link_margin(ni, &tpc_report.link_margin); |
| action_data.cat = IEEE80211_ACTION_CAT_SPEC_MGMT; |
| action_data.action = IEEE80211_ACTION_S_TPC_REPORT; |
| action_data.params = &tpc_report; |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_ACTION, (int)&action_data); |
| } else { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "Missing TPC REQUEST Element in TPC Request action frame\n"); |
| vap->iv_stats.is_rx_elem_missing++; |
| } |
| } else { |
| TPC_DBG(vap, "TPC Request: frame not supported, drop it\n"); |
| vap->iv_stats.is_rx_mgtdiscard++; |
| } |
| |
| break; |
| } |
| case IEEE80211_ACTION_S_TPC_REPORT: |
| { |
| u_int8_t tpc_report_token; |
| if ((ic->ic_flags & IEEE80211_F_DOTH) && (ic->ic_flags_ext & IEEE80211_FEXT_TPC)) { |
| frm = frm + sizeof(struct ieee80211_action); |
| tpc_report_token = *frm++; |
| IEEE80211_VERIFY_LENGTH(efrm - frm, 4); |
| if ((frm[0] == IEEE80211_ELEMID_TPCREP) && (frm[1] == 2)) { |
| frm += 2; |
| ni->ni_tpc_info.tpc_report.node_txpow = *frm++; |
| ni->ni_tpc_info.tpc_report.node_link_margin = *frm++; |
| |
| ieee80211_ppqueue_remove_with_response(&ni->ni_vap->iv_ppqueue, |
| ni, |
| IEEE80211_ACTION_CAT_SPEC_MGMT, |
| IEEE80211_ACTION_S_TPC_REPORT, |
| tpc_report_token); |
| } else { |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "%s", "Missing TPC REPORT Element in TPC Report action frame\n"); |
| vap->iv_stats.is_rx_elem_missing++; |
| } |
| } else { |
| TPC_DBG(vap, "TPC Report: frame not supported, drop it\n"); |
| vap->iv_stats.is_rx_mgtdiscard++; |
| } |
| break; |
| } |
| case IEEE80211_ACTION_S_MEASUREMENT_REPORT: |
| case IEEE80211_ACTION_S_MEASUREMENT_REQUEST: |
| ieee80211_recv_action_measure_11h(ni, ia, wh, efrm); |
| break; |
| default: |
| vap->iv_stats.is_rx_mgtdiscard++; |
| } |
| break; |
| case IEEE80211_ACTION_CAT_QOS: |
| vap->iv_stats.is_rx_mgtdiscard++; |
| break; |
| case IEEE80211_ACTION_CAT_HT: |
| ieee80211_action_ht(ni, skb, wh, subtype, ia, frm, efrm); |
| break; |
| case IEEE80211_ACTION_CAT_BA: |
| ieee80211_action_ba(ni, wh, subtype, ia, frm, efrm); |
| break; |
| case IEEE80211_ACTION_CAT_RM: |
| ieee80211_recv_action_11k(ni, ia, wh, efrm); |
| break; |
| case IEEE80211_ACTION_CAT_VENDOR: { |
| struct qdrv_vendor_action_header *va; |
| va = (struct qdrv_vendor_action_header *) (void*)frm; |
| #ifdef CONFIG_QHOP |
| if (va->type == QDRV_ACTION_TYPE_QHOP) { |
| if (va->action == QDRV_ACTION_QHOP_DFS_REPORT) { |
| if (IEEE80211_VAP_WDS_IS_MBS(vap)) |
| ic->ic_radar_detected(ic, 0); |
| } |
| } |
| #endif |
| #ifdef CONFIG_QVSP |
| if (va->type != QDRV_ACTION_TYPE_QHOP) { |
| ieee80211_recv_action_vsp(ni, frm, efrm); |
| } |
| #endif |
| break; |
| } |
| case IEEE80211_ACTION_CAT_SA_QUERY: |
| ieee80211_recv_action_sa_query(ni, ia, wh, efrm); |
| break; |
| case IEEE80211_ACTION_CAT_VHT: |
| ieee80211_recv_action_vht(ni, ia, subtype, wh, frm, efrm); |
| break; |
| case IEEE80211_ACTION_CAT_WNM: |
| ieee80211_recv_action_wnm(ni, ia, subtype, wh, frm); |
| break; |
| default: |
| vap->iv_stats.is_rx_mgtdiscard++; |
| break; |
| } |
| break; |
| } |
| default: |
| IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, |
| wh, "mgt", "subtype 0x%x not handled", subtype); |
| vap->iv_stats.is_rx_badsubtype++; |
| break; |
| } |
| #undef ISREASSOC |
| #undef ISPROBE |
| } |
| |
| #undef IEEE80211_VERIFY_ELEMENT |
| |
| /* |
| * Process a received ps-poll frame. |
| */ |
| static void |
| ieee80211_recv_pspoll(struct ieee80211_node *ni, struct sk_buff *skb0) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_frame_min *wh; |
| struct sk_buff *skb; |
| u_int16_t aid; |
| int qlen; |
| |
| wh = (struct ieee80211_frame_min *)skb0->data; |
| if (ni->ni_associd == 0) { |
| IEEE80211_DISCARD(vap, |
| IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG, |
| (struct ieee80211_frame *) wh, "ps-poll", |
| "%s", "unassociated station"); |
| vap->iv_stats.is_ps_unassoc++; |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, |
| IEEE80211_REASON_NOT_ASSOCED); |
| return; |
| } |
| |
| aid = le16toh(*(__le16 *)wh->i_dur); |
| if (aid != ni->ni_associd) { |
| IEEE80211_DISCARD(vap, |
| IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG, |
| (struct ieee80211_frame *) wh, "ps-poll", |
| "aid mismatch: sta aid 0x%x poll aid 0x%x", |
| ni->ni_associd, aid); |
| vap->iv_stats.is_ps_badaid++; |
| IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, |
| IEEE80211_REASON_NOT_ASSOCED); |
| return; |
| } |
| |
| /* Okay, take the first queued packet and put it out... */ |
| IEEE80211_NODE_SAVEQ_LOCK(ni); |
| IEEE80211_NODE_SAVEQ_DEQUEUE(ni, skb, qlen); |
| IEEE80211_NODE_SAVEQ_UNLOCK(ni); |
| if (skb == NULL) { |
| IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_POWER, wh->i_addr2, |
| "%s", "recv ps-poll, but queue empty"); |
| ieee80211_ref_node(ni); |
| ieee80211_send_nulldata(ni); |
| vap->iv_stats.is_ps_qempty++; /* XXX node stat */ |
| if (vap->iv_set_tim != NULL) |
| vap->iv_set_tim(ni, 0); /* just in case */ |
| return; |
| } |
| /* |
| * If there are more packets, set the more packets bit |
| * in the packet dispatched to the station; otherwise |
| * turn off the TIM bit. |
| */ |
| if (qlen != 0) { |
| IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, |
| "recv ps-poll, send packet, %u still queued", qlen); |
| /* |
| * NB: More-data bit will be set during encap. |
| */ |
| } else { |
| IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, |
| "%s", "recv ps-poll, send packet, queue empty"); |
| if (vap->iv_set_tim != NULL) |
| vap->iv_set_tim(ni, 0); |
| } |
| M_PWR_SAV_SET(skb); /* ensure MORE_DATA bit is set correctly */ |
| |
| ieee80211_parent_queue_xmit(skb); /* Submit to parent device, including updating stats */ |
| } |
| |
| #ifdef USE_HEADERLEN_RESV |
| /* |
| * The kernel version of this function alters the skb in a manner |
| * inconsistent with dev->hard_header_len header reservation. This |
| * is a rewrite of the portion of eth_type_trans() that we need. |
| */ |
| static __be16 |
| ath_eth_type_trans(struct sk_buff *skb, struct net_device *dev) |
| { |
| struct ethhdr *eth; |
| |
| skb_reset_mac_header(skb); |
| skb_pull(skb, ETH_HLEN); |
| /* |
| * NB: mac.ethernet is replaced in 2.6.9 by eth_hdr but |
| * since that's an inline and not a define there's |
| * no easy way to do this cleanly. |
| */ |
| eth = (struct ethhdr *)skb_mac_header(skb); |
| |
| if (*eth->h_dest & 1) |
| if (memcmp(eth->h_dest, dev->broadcast, ETH_ALEN) == 0) |
| skb->pkt_type = PACKET_BROADCAST; |
| else |
| skb->pkt_type = PACKET_MULTICAST; |
| else |
| if (memcmp(eth->h_dest, dev->dev_addr, ETH_ALEN)) |
| skb->pkt_type = PACKET_OTHERHOST; |
| |
| return eth->h_proto; |
| } |
| #endif |
| |
| #ifdef IEEE80211_DEBUG |
| /* |
| * Debugging support. |
| */ |
| |
| /* |
| * Return the bssid of a frame. |
| */ |
| static const u_int8_t * |
| ieee80211_getbssid(struct ieee80211vap *vap, const struct ieee80211_frame *wh) |
| { |
| if (vap->iv_opmode == IEEE80211_M_STA) |
| return wh->i_addr2; |
| if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) != IEEE80211_FC1_DIR_NODS) |
| return wh->i_addr1; |
| if ((wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == IEEE80211_FC0_SUBTYPE_PS_POLL) |
| return wh->i_addr1; |
| return wh->i_addr3; |
| } |
| |
| /* used for send formatted string custom event IWEVCUSTOM */ |
| int ieee80211_eventf(struct net_device *dev, const char *fmt, ...) |
| { |
| va_list args; |
| int i; |
| union iwreq_data wreq; |
| char buffer[IW_CUSTOM_MAX]; |
| |
| if (dev == NULL) { |
| return 0; |
| } |
| |
| /* Format the custom wireless event */ |
| memset(&wreq, 0, sizeof(wreq)); |
| |
| va_start(args, fmt); |
| i = vsnprintf(buffer, IW_CUSTOM_MAX, fmt, args); |
| va_end(args); |
| |
| wreq.data.length = strnlen(buffer, IW_CUSTOM_MAX); |
| wireless_send_event(dev, IWEVCUSTOM, &wreq, buffer); |
| return i; |
| } |
| EXPORT_SYMBOL(ieee80211_eventf); |
| |
| void |
| ieee80211_note(struct ieee80211vap *vap, const char *fmt, ...) |
| { |
| char buf[128]; /* XXX */ |
| va_list ap; |
| |
| va_start(ap, fmt); |
| vsnprintf(buf, sizeof(buf), fmt, ap); |
| va_end(ap); |
| |
| printk("%s: %s", vap->iv_dev->name, buf); /* NB: no \n */ |
| } |
| EXPORT_SYMBOL(ieee80211_note); |
| |
| void |
| ieee80211_note_frame(struct ieee80211vap *vap, const struct ieee80211_frame *wh, |
| const char *fmt, ...) |
| { |
| char buf[128]; /* XXX */ |
| va_list ap; |
| |
| va_start(ap, fmt); |
| vsnprintf(buf, sizeof(buf), fmt, ap); |
| va_end(ap); |
| printk("%s: [%s] %s\n", vap->iv_dev->name, |
| ether_sprintf(ieee80211_getbssid(vap, wh)), buf); |
| } |
| EXPORT_SYMBOL(ieee80211_note_frame); |
| |
| void |
| ieee80211_note_mac(struct ieee80211vap *vap, const u_int8_t mac[IEEE80211_ADDR_LEN], |
| const char *fmt, ...) |
| { |
| char buf[128]; /* XXX */ |
| va_list ap; |
| |
| va_start(ap, fmt); |
| vsnprintf(buf, sizeof(buf), fmt, ap); |
| va_end(ap); |
| printk("%s: [%s] %s\n", vap->iv_dev->name, ether_sprintf(mac), buf); |
| } |
| EXPORT_SYMBOL(ieee80211_note_mac); |
| |
| static void |
| ieee80211_discard_frame(struct ieee80211vap *vap, const struct ieee80211_frame *wh, |
| const char *type, const char *fmt, ...) |
| { |
| char buf[128]; /* XXX */ |
| va_list ap; |
| |
| va_start(ap, fmt); |
| vsnprintf(buf, sizeof(buf), fmt, ap); |
| va_end(ap); |
| if (type != NULL) |
| printk("[%s:%s] discard %s frame, %s\n", vap->iv_dev->name, |
| ether_sprintf(ieee80211_getbssid(vap, wh)), type, buf); |
| else |
| printk("[%s:%s] discard frame, %s\n", vap->iv_dev->name, |
| ether_sprintf(ieee80211_getbssid(vap, wh)), buf); |
| } |
| |
| static void |
| ieee80211_discard_ie(struct ieee80211vap *vap, const struct ieee80211_frame *wh, |
| const char *type, const char *fmt, ...) |
| { |
| char buf[128]; /* XXX */ |
| va_list ap; |
| |
| va_start(ap, fmt); |
| vsnprintf(buf, sizeof(buf), fmt, ap); |
| va_end(ap); |
| if (type != NULL) |
| printk("[%s:%s] discard %s information element, %s\n", |
| vap->iv_dev->name, |
| ether_sprintf(ieee80211_getbssid(vap, wh)), type, buf); |
| else |
| printk("[%s:%s] discard information element, %s\n", |
| vap->iv_dev->name, |
| ether_sprintf(ieee80211_getbssid(vap, wh)), buf); |
| } |
| |
| static void |
| ieee80211_discard_mac(struct ieee80211vap *vap, const u_int8_t mac[IEEE80211_ADDR_LEN], |
| const char *type, const char *fmt, ...) |
| { |
| char buf[128]; /* XXX */ |
| va_list ap; |
| |
| va_start(ap, fmt); |
| vsnprintf(buf, sizeof(buf), fmt, ap); |
| va_end(ap); |
| if (type != NULL) |
| printk("[%s:%s] discard %s frame, %s\n", vap->iv_dev->name, |
| ether_sprintf(mac), type, buf); |
| else |
| printk("[%s:%s] discard frame, %s\n", vap->iv_dev->name, |
| ether_sprintf(mac), buf); |
| } |
| #endif /* IEEE80211_DEBUG */ |
| |
| static void ieee80211_recv_action_vht(struct ieee80211_node *ni, |
| struct ieee80211_action *ia, |
| int subtype, |
| struct ieee80211_frame *wh, |
| u_int8_t *frm, |
| u_int8_t *efrm) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| struct ieee80211_vht_mu_grp *mu_grp; |
| struct ieee80211com *ic = ni->ni_ic; |
| struct ieee80211_action_vht_opmode_notification *iaopmode; |
| |
| switch (ia->ia_action) { |
| case IEEE80211_ACTION_VHT_CBEAMFORMING: |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, |
| "VHT: Not handling compressed beamforming frame \ |
| from station %pM\n", ni->ni_macaddr); |
| vap->iv_stats.is_rx_mgtdiscard++; |
| break; |
| case IEEE80211_ACTION_VHT_OPMODE_NOTIFICATION: |
| IEEE80211_VERIFY_LENGTH(efrm - frm, |
| sizeof(struct ieee80211_action_vht_opmode_notification)); |
| |
| iaopmode = (struct ieee80211_action_vht_opmode_notification *)frm; |
| |
| { |
| uint8_t opmode = recalc_opmode(ni, iaopmode->am_opmode); |
| ieee80211_param_to_qdrv(ni->ni_vap, IEEE80211_PARAM_NODE_OPMODE, |
| opmode, ni->ni_macaddr, IEEE80211_ADDR_LEN); |
| } |
| break; |
| case IEEE80211_ACTION_VHT_MU_GRP_ID: |
| if ((vap->iv_opmode == IEEE80211_M_STA) && |
| ieee80211_swfeat_is_supported(SWFEAT_ID_MU_MIMO, 0)) { |
| /* AP sends me my MU group and position arrays, push it down to Muc/HW */ |
| mu_grp = (struct ieee80211_vht_mu_grp *)(ia + 1); |
| memcpy(&vap->iv_bss->ni_mu_grp, mu_grp, sizeof(struct ieee80211_vht_mu_grp)); |
| ic->ic_setparam(ni, IEEE80211_PARAM_UPDATE_MU_GRP, 1, |
| (unsigned char *)&vap->iv_bss->ni_mu_grp, |
| sizeof(struct ieee80211_vht_mu_grp)); |
| } |
| break; |
| default: |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, |
| "VHT: Invalid action frame(%d) from station %pM\n", |
| ia->ia_action, ni->ni_macaddr); |
| vap->iv_stats.is_rx_mgtdiscard++; |
| break; |
| } |
| } |
| |
| #if defined(CONFIG_QTN_BSA_SUPPORT) |
| uint32_t ieee80211_wlan_vht_mcs_streams(uint16_t mcsmap) |
| { |
| uint32_t nss = 8; |
| |
| while ((nss > 0) && ((mcsmap & BSA_VHT_MCSMAP_MASK) == BSA_VHT_MCSMAP_NOT_SUPPORT)) { |
| nss--; |
| mcsmap <<= 2; |
| }; |
| return nss; |
| } |
| |
| uint32_t ieee80211_wlan_vht_rxstreams(struct ieee80211_ie_vhtcap *vhtcap) |
| { |
| uint16_t mcsmap = (u_int16_t)IEEE80211_VHTCAP_GET_RX_MCS_NSS(vhtcap); |
| |
| return ieee80211_wlan_vht_mcs_streams(mcsmap); |
| } |
| EXPORT_SYMBOL(ieee80211_wlan_vht_rxstreams); |
| |
| uint32_t ieee80211_wlan_vht_rx_maxrate(struct ieee80211_ie_vhtcap *vhtcap) |
| { |
| int chan_mode = 0; |
| uint32_t max = 0; |
| uint8_t sgi = 0; |
| int k; |
| int r; |
| uint16_t mask = BSA_VHT_MCSMAP_MASK; |
| |
| if (vhtcap) { |
| u_int16_t mcsmap = 0; |
| r = 0; |
| /* 80+80 or 160 Mhz */ |
| if (IEEE80211_VHTCAP_GET_CHANWIDTH(vhtcap)) { |
| chan_mode = 1; |
| sgi = IEEE80211_VHTCAP_GET_SGI_160MHZ(vhtcap); |
| } else { |
| sgi = IEEE80211_VHTCAP_GET_SGI_80MHZ(vhtcap); |
| } |
| mcsmap = (u_int16_t)IEEE80211_VHTCAP_GET_RX_MCS_NSS(vhtcap); |
| for (k = 8; k > 0; k--) { |
| if ((mcsmap & mask) != mask) { |
| int rate = 0; |
| int val = ((mcsmap & mask) >> ((k-1) * 2)); |
| r = (val == 2) ? 9: (val == 1) ? 8 : 7; |
| rate = ieee80211_mcs2rate(r, chan_mode, sgi, 1); |
| if (rate >= 0) { |
| rate = (rate / 2) * k; |
| if (max < rate) |
| max = rate; |
| break; |
| } |
| } |
| mask = mask >> 2; |
| } |
| } |
| return max; |
| } |
| EXPORT_SYMBOL(ieee80211_wlan_vht_rx_maxrate); |
| |
| uint32_t ieee80211_wlan_ht_rx_maxrate(struct ieee80211_ie_htcap *htcap, uint32_t *rx_ss) |
| { |
| int chan_mode = 0, j; |
| uint32_t max = 0; |
| uint8_t sgi = 0; |
| int k; |
| int r; |
| u_int16_t mask; |
| |
| if (htcap) { |
| r = 0; |
| if (htcap->hc_cap[0] & IEEE80211_HTCAP_C_CHWIDTH40) { |
| chan_mode = 1; |
| sgi = htcap->hc_cap[0] & IEEE80211_HTCAP_C_SHORTGI40 ? 1 : 0; |
| } else { |
| sgi = htcap->hc_cap[0] & IEEE80211_HTCAP_C_SHORTGI20 ? 1 : 0; |
| } |
| for (j = IEEE80211_HT_MCSSET_20_40_NSS1; j <= IEEE80211_HT_MCSSET_20_40_NSS4; j++) { |
| mask = 1; |
| for (k = 0; k < 8; k++, r++) { |
| if (htcap->hc_mcsset[j] & mask) { |
| /* Copy HT rates */ |
| int rate = ieee80211_mcs2rate(r, chan_mode, sgi, 0) / 2; |
| if (rate >= 0 && max < rate) |
| max = rate; |
| *rx_ss = j+1; |
| } |
| mask = mask << 1; |
| } |
| } |
| } |
| return max; |
| } |
| EXPORT_SYMBOL(ieee80211_wlan_ht_rx_maxrate); |
| |
| int ieee80211_bsa_probe_event_send(struct ieee80211vap *vap,struct sk_buff *skb, uint8_t *bssid, |
| uint8_t *sta_mac, int rssi) |
| { |
| uint8_t event_data[IEEE80211_MAX_EVENT_DATA_LEN]; |
| struct ieee80211_frame *wh; |
| uint8_t *frm; |
| uint8_t *efrm; |
| uint8_t *htcap = NULL; |
| uint8_t *htinfo = NULL; |
| uint8_t *vhtcap = NULL; |
| uint8_t *vhtop = NULL; |
| uint8_t chan=0; |
| union iwreq_data wreq; |
| struct ieee80211_ie_htcap *htcap_ie = NULL; |
| struct ieee80211_ie_vhtcap *vhtcap_ie = NULL; |
| uint8_t *extcap = NULL; |
| struct qtn_bsa_peer_event_data *p_data; |
| struct qtn_bsa_probe_event_info *pevent; |
| uint32_t max_ht_phy_rate = 54; |
| uint32_t max_vht_phy_rate = 0; |
| uint32_t max_ht_ss = 1; |
| uint32_t max_vht_ss = 0; |
| struct ieee80211com *ic = vap->iv_ic; |
| uint16_t bandwidth = 1; |
| |
| memset(&event_data, 0, IEEE80211_MAX_EVENT_DATA_LEN); |
| |
| p_data = (void *)event_data; |
| pevent = (void *)(event_data + sizeof(struct qtn_bsa_peer_event_data)); |
| |
| wh = (struct ieee80211_frame *)skb->data; |
| frm = (u_int8_t *)&wh[1]; |
| efrm = skb->data + skb->len; |
| |
| strncpy(p_data->bsa_name, "BSA-PEER-EVENT", sizeof(p_data->bsa_name)); |
| |
| put_unaligned(BSA_PROBE_EVENT_REQ, &p_data->bsa_event_id); |
| memcpy(p_data->bsa_bssid, bssid, IEEE80211_ADDR_LEN); |
| put_unaligned(sizeof(struct qtn_bsa_peer_event_data), &p_data->offset); |
| |
| if (sta_mac) { |
| memcpy(pevent->bsa_sta_mac, sta_mac, IEEE80211_ADDR_LEN); |
| } |
| |
| while (frm < efrm) { |
| switch (*frm) { |
| case IEEE80211_ELEMID_FHPARMS: |
| chan = IEEE80211_FH_CHAN(frm[4], frm[5]); |
| break; |
| case IEEE80211_ELEMID_DSPARMS: |
| chan = frm[2]; |
| break; |
| case IEEE80211_ELEMID_HTCAP: |
| htcap = frm; |
| break; |
| case IEEE80211_ELEMID_VHTCAP: |
| vhtcap = frm; |
| break; |
| case IEEE80211_ELEMID_VHTOP: |
| vhtop = frm; |
| break; |
| case IEEE80211_ELEMID_HTINFO: |
| htinfo = frm; |
| break; |
| case IEEE80211_ELEMID_EXTCAP: |
| extcap = frm; |
| break; |
| default: |
| break; |
| } |
| |
| frm += frm[1] + 2; |
| } |
| |
| put_unaligned(rssi, &pevent->bsa_rssi); |
| put_unaligned(0, &pevent->bsa_band_width); |
| pevent->bsa_mu_mimo_capab = 0; |
| pevent->bsa_vht_capab = 0; |
| put_unaligned(0, &pevent->bsa_bss_transition); |
| if (extcap) { |
| uint8_t value; |
| value = extcap[1]; |
| if (value >= 3) { |
| value = extcap[4]; |
| if (value & 0x8) |
| put_unaligned(1, &pevent->bsa_bss_transition); |
| } |
| } |
| |
| if (htcap != NULL) { |
| htcap_ie = (struct ieee80211_ie_htcap *) htcap; |
| max_ht_phy_rate = ieee80211_wlan_ht_rx_maxrate(htcap_ie, &max_ht_ss); |
| bandwidth = ((htcap_ie->hc_cap[0] & 0x2) >> 1); |
| pevent->bsa_vht_capab |= BSA_HT_SUPPORTED; |
| } |
| |
| if (vhtcap != NULL) { |
| vhtcap_ie = (struct ieee80211_ie_vhtcap * ) vhtcap; |
| max_vht_phy_rate = ieee80211_wlan_vht_rx_maxrate(vhtcap_ie); |
| pevent->bsa_vht_capab |= BSA_VHT_SUPPORTED; |
| pevent->bsa_mu_mimo_capab = ((vhtcap_ie->vht_cap[2] & 0x18)>>3); |
| max_vht_ss = ieee80211_wlan_vht_rxstreams(vhtcap_ie); |
| bandwidth |= ((vhtcap_ie->vht_cap[0] & 0xc) >> 1); |
| } |
| put_unaligned(bandwidth, &pevent->bsa_band_width); |
| put_unaligned((max_vht_phy_rate > max_ht_phy_rate)? max_vht_phy_rate : max_ht_phy_rate, |
| &pevent->bsa_max_phy_rate); |
| put_unaligned((max_ht_ss > max_vht_ss) ? max_ht_ss : max_vht_ss, |
| &pevent->bsa_nss); |
| |
| if (IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan)) |
| put_unaligned(BSA_OPER_BAND_5G, &pevent->bsa_curr_band); |
| else |
| put_unaligned(BSA_OPER_BAND_2G, &pevent->bsa_curr_band); |
| |
| put_unaligned(ic->ic_curchan->ic_ieee, &pevent->bsa_channel); |
| |
| put_unaligned(skb->len, &pevent->cookie_len); |
| pevent->cookie = skb->data; |
| |
| memset(&wreq, 0, sizeof(wreq)); |
| wreq.data.length = sizeof(*pevent) + sizeof(*p_data); |
| wireless_send_event(vap->iv_dev, IWEVCUSTOM, &wreq, (char *)&event_data); |
| |
| return 0; |
| } |
| #endif |
| |
| /* BSS transition management query reasons ieee80211 2102 spec Table 8-138 */ |
| char *btm_query_reason[] = { |
| "Unspecified", |
| "Excessive frame loss rates and/or poor conditions", |
| "Excessive delay for current traffic streams", |
| "Insufficient QoS capacity for current traffic streams (TSPEC rejected)", |
| "First association to ESS (the association initiated by an Association " |
| "Request message instead of a Reassociation Request message)", |
| "Load balancing", |
| "Better AP found", |
| "Deauthenticated or Disassociated from the previous AP", |
| "AP failed IEEE 802.1X EAP Authentication", |
| "AP failed 4-Way Handshake", |
| "Received too many replay counter failures", |
| "Received too many data MIC failures", |
| "Exceeded maximum number of retransmissions", |
| "Received too many broadcast disassociations", |
| "Received too many broadcast deauthentications", |
| "Previous transition failed", |
| "Low RSSI", |
| "Roam from a non IEEE 802.11 system", |
| "Transition due to received BSS Transition Request frame", |
| "Preferred BSS transition candidate list included", |
| "Leaving ESS", |
| "Reserved" |
| }; |
| #define WNM_BTM_QUERY_REASON_CODE_MAX ARRAY_SIZE(btm_query_reason) |
| |
| /* BTM response codes */ |
| char *btm_resp_status_codes[] = { |
| "Accept", |
| "Reject - Unspecified reject reason", |
| "Reject - Insufficient Beacon or Probe Response frames received from all candidates", |
| "Reject - Insufficient available capacity from all candidates", |
| "Reject - BSS termination undesired", |
| "Reject - BSS Termination delay requested", |
| "Reject - STA BSS Transition Candidate List provided", |
| "Reject - No suitable BSS transition candidates", |
| "Reject - Leaving ESS", |
| "Reserved" |
| }; |
| #define WNM_BTM_RESPONSE_STATUS_CODE_MAX ARRAY_SIZE(btm_resp_status_codes) |
| |
| int |
| ieee80211_wnm_btm_create_pref_candidate_list(struct ieee80211_node *ni, uint8_t **list) |
| { |
| struct ieee80211_neighbor_report_request_item *item_cache = NULL; |
| int i = 0; |
| int num_items = 0; |
| uint8_t *neigh_repo = NULL; |
| int neigh_repo_size = 0; |
| uint8_t pref = 255; |
| |
| num_items = ieee80211_create_neighbor_reports(ni, &item_cache, |
| NEIGH_REPORTS_MAX, 1); |
| neigh_repo_size = (sizeof(struct ieee80211_ie_neighbor_report) |
| + sizeof(struct ieee80211_subie_pref)) * num_items; |
| *list = kmalloc(neigh_repo_size, GFP_ATOMIC); |
| neigh_repo = *list; |
| if (neigh_repo && (num_items > 0)) { |
| for (i = 0; i < num_items; i++) { |
| *neigh_repo++ = IEEE80211_ELEMID_NEIGHBOR_REP; |
| *neigh_repo++ = sizeof(struct ieee80211_ie_neighbor_report) |
| + sizeof(struct ieee80211_subie_pref) |
| - IEEE80211_IE_ID_LEN_SIZE; |
| memcpy(neigh_repo, item_cache->bssid, IEEE80211_ADDR_LEN); |
| neigh_repo += IEEE80211_ADDR_LEN; |
| ADDINT32(neigh_repo, item_cache->bssid_info); |
| *neigh_repo++ = item_cache->operating_class; |
| *neigh_repo++ = item_cache->channel; |
| *neigh_repo++ = item_cache->phy_type; |
| /* fill preference sub element */ |
| *neigh_repo++ = WNM_NEIGHBOR_BTM_CANDIDATE_PREFERENCE; |
| *neigh_repo++ = 1; |
| *neigh_repo++ = pref--; |
| item_cache++; |
| } |
| } |
| |
| kfree(item_cache); |
| |
| return neigh_repo_size; |
| } |
| |
| static void |
| ieee80211_handle_btm_query(struct ieee80211_node *ni, struct ieee80211_action_btm_query *query) |
| { |
| uint8_t rcode = 0; |
| uint8_t mode = BTM_REQ_PREF_CAND_LIST_INCLUDED | BTM_REQ_ABRIDGED; |
| int nsize = 0; |
| uint8_t *neigh_repos = NULL; |
| |
| rcode = (query->btm_query_param.reason < WNM_BTM_QUERY_REASON_CODE_MAX) ? |
| query->btm_query_param.reason : |
| (WNM_BTM_QUERY_REASON_CODE_MAX - 1); |
| IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ACTION, |
| "WNM: Received BSS Transition management query from station %pM token: %u reason %s\n", |
| ni->ni_macaddr, query->btm_query_param.dialog_token, |
| btm_query_reason[rcode]); |
| nsize = ieee80211_wnm_btm_create_pref_candidate_list(ni, &neigh_repos); |
| |
| if (nsize == 0) |
| mode &= ~BTM_REQ_PREF_CAND_LIST_INCLUDED; |
| |
| if (ieee80211_send_wnm_bss_tm_solicited_req(ni, mode, 0, |
| WNM_BTM_DEFAULT_VAL_INTVAL, |
| NULL, NULL, neigh_repos, |
| nsize, |
| query->btm_query_param.dialog_token)) |
| IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ACTION, "WNM: Failed to send BTM request %pM\n", |
| ni->ni_macaddr); |
| kfree(neigh_repos); |
| |
| } |
| |
| #if defined(CONFIG_QTN_BSA_SUPPORT) |
| static int ieee80211_bsa_btm_resp_event(struct ieee80211vap *vap,struct ieee80211_node *ni, uint8_t status) |
| { |
| struct qtn_bsa_peer_event_data *p_data; |
| struct ieee80211_bsa_btm_resp_event *pevent; |
| uint8_t event_data[IEEE80211_MAX_EVENT_DATA_LEN]; |
| union iwreq_data wreq; |
| |
| p_data = (void *)event_data; |
| pevent = (void *)(event_data + sizeof(struct qtn_bsa_peer_event_data)); |
| strncpy(p_data->bsa_name, "BSA-PEER-EVENT", sizeof(p_data->bsa_name)); |
| put_unaligned(BSA_EVENT_BSS_TRANS_STATUS, &p_data->bsa_event_id); |
| memcpy(p_data->bsa_bssid, ni->ni_bssid, IEEE80211_ADDR_LEN); |
| put_unaligned(sizeof(struct qtn_bsa_peer_event_data), &p_data->offset); |
| memcpy(pevent->bsa_sta_mac, ni->ni_macaddr, IEEE80211_ADDR_LEN); |
| put_unaligned(status, &pevent->bsa_btm_resp_status); |
| memset(&wreq, 0, sizeof(wreq)); |
| wreq.data.length = sizeof(*pevent) + sizeof(*p_data); |
| wireless_send_event(vap->iv_dev, IWEVCUSTOM, &wreq, (char *)&event_data); |
| |
| return 0; |
| } |
| #endif |
| |
| static void |
| ieee80211_hanle_btm_resp(struct ieee80211_node *ni, struct ieee80211_action_btm_rsp *rsp) |
| { |
| uint8_t scode = 0; |
| |
| scode = (rsp->btm_rsp_param.status_code >= WNM_BTM_RESPONSE_STATUS_CODE_MAX) ? |
| (WNM_BTM_RESPONSE_STATUS_CODE_MAX - 1) : |
| rsp->btm_rsp_param.status_code; |
| IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ACTION, |
| "WNM: Received BSS Transition management response from station %pM token: %u status code %s\n", |
| ni->ni_macaddr, rsp->btm_rsp_param.dialog_token, btm_resp_status_codes[scode]); |
| /* |
| * TBD: we don't have full WNM upper layer information or management control for now |
| * we are just logging and clearing pending BTM request |
| */ |
| if ((ni->ni_btm_req != 0) && (ni->ni_btm_req == rsp->btm_rsp_param.dialog_token)) { |
| del_timer_sync(&ni->ni_btm_resp_wait_timer); |
| ni->ni_btm_req = 0; |
| #if defined(CONFIG_QTN_BSA_SUPPORT) |
| ieee80211_bsa_btm_resp_event(ni->ni_vap, ni, scode); |
| #endif |
| } |
| } |
| |
| static void |
| ieee80211_recv_action_wnm(struct ieee80211_node *ni, |
| struct ieee80211_action *ia, |
| int subtype, |
| struct ieee80211_frame *wh, |
| u_int8_t *frm) |
| { |
| struct ieee80211vap *vap = ni->ni_vap; |
| |
| switch (ia->ia_action) { |
| case IEEE80211_WNM_BSS_TRANS_MGMT_QUERY: { |
| struct ieee80211_action_btm_query *query = (struct ieee80211_action_btm_query *)frm; |
| ieee80211_handle_btm_query(ni, query); |
| break; |
| } |
| case IEEE80211_WNM_BSS_TRANS_MGMT_RESP: { |
| struct ieee80211_action_btm_rsp *rsp = (struct ieee80211_action_btm_rsp *)frm; |
| ieee80211_hanle_btm_resp(ni, rsp); |
| break; |
| } |
| default: |
| IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION, "WNM: Invalid action frame(%d) from station %pM\n", |
| ia->ia_action, ni->ni_macaddr); |
| vap->iv_stats.is_rx_mgtdiscard++; |
| break; |
| } |
| } |