mirror of
https://github.com/proski/madwifi
synced 2024-11-25 07:49:43 +03:00
4393 lines
126 KiB
C
4393 lines
126 KiB
C
/*-
|
|
* 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$
|
|
*/
|
|
#ifndef EXPORT_SYMTAB
|
|
#define EXPORT_SYMTAB
|
|
#endif
|
|
|
|
/*
|
|
* IEEE 802.11 input handling.
|
|
*/
|
|
#if !defined(AUTOCONF_INCLUDED) && !defined(CONFIG_LOCALVERSION)
|
|
#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 <net80211/if_llc.h>
|
|
#include <net80211/if_ethersubr.h>
|
|
#include <net80211/if_media.h>
|
|
#include <net80211/if_athproto.h>
|
|
|
|
#include <net80211/ieee80211_var.h>
|
|
|
|
#ifdef IEEE80211_DEBUG
|
|
|
|
#define BUF_LEN 192
|
|
/*
|
|
* 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 *);
|
|
|
|
|
|
#ifdef ATH_SUPERG_FF
|
|
static int athff_decap(struct sk_buff *);
|
|
#endif
|
|
#ifdef USE_HEADERLEN_RESV
|
|
static __be16 ath_eth_type_trans(struct sk_buff *, struct net_device *);
|
|
#endif
|
|
|
|
/**
|
|
* 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 " MAC_FMT
|
|
", threshold is active and rssi exceeds"
|
|
" it -> raise an iwspy event\n",
|
|
__func__, MAC_ADDR(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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 ieee80211vap *vap, struct ieee80211_node *ni_or_null,
|
|
struct sk_buff *skb, int rssi, u_int64_t rtsf)
|
|
{
|
|
#define HAS_SEQ(type) ((type & 0x4) == 0)
|
|
struct ieee80211_node *ni = ni_or_null;
|
|
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;
|
|
#ifdef ATH_SUPERG_FF
|
|
struct llc *llc;
|
|
#endif
|
|
int hdrlen;
|
|
u_int8_t dir, type = -1, subtype;
|
|
u_int8_t *bssid;
|
|
u_int16_t rxseq;
|
|
|
|
if ((vap->iv_dev->flags & (IFF_RUNNING | IFF_UP)) !=
|
|
(IFF_RUNNING | IFF_UP)) {
|
|
ieee80211_dev_kfree_skb(&skb);
|
|
return -1;
|
|
}
|
|
|
|
/* Initialize ni as in the previous API. */
|
|
if (ni_or_null == NULL) {
|
|
/* This function does not 'own' vap->iv_bss, so we cannot
|
|
* guarantee its existence during the following call, hence
|
|
* briefly grab our own reference. */
|
|
ni = ieee80211_ref_node(vap->iv_bss);
|
|
}
|
|
KASSERT(skb != NULL, ("null skb"));
|
|
KASSERT(ni != NULL, ("null node"));
|
|
ni->ni_inact = ni->ni_inact_reload;
|
|
|
|
/* 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_ANY,
|
|
ni->ni_macaddr, NULL,
|
|
"too short (1): len %u", skb->len);
|
|
vap->iv_stats.is_rx_tooshort++;
|
|
goto out;
|
|
}
|
|
|
|
/* 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_ANY,
|
|
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:
|
|
bssid = wh->i_addr2;
|
|
if (!IEEE80211_ADDR_EQ(bssid, vap->iv_bssid)) {
|
|
/* not interested in */
|
|
IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT,
|
|
bssid, NULL, "%s", "not to bss");
|
|
vap->iv_stats.is_rx_wrongbss++;
|
|
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_ANY, 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;
|
|
}
|
|
}
|
|
}
|
|
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_ANY, 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.
|
|
*/
|
|
#ifdef ATH_SUPERG_XR
|
|
if (!IEEE80211_ADDR_EQ(bssid, vap->iv_bssid) &&
|
|
!IEEE80211_ADDR_EQ(bssid, dev->broadcast)) {
|
|
/*
|
|
* allow MGT frames to vap->iv_xrvap.
|
|
* this will allow roaming between XR and normal vaps
|
|
* without station dis associating from previous vap.
|
|
*/
|
|
if (!(vap->iv_xrvap &&
|
|
IEEE80211_ADDR_EQ(bssid, vap->iv_xrvap->iv_bssid) &&
|
|
type == IEEE80211_FC0_TYPE_MGT &&
|
|
ni != vap->iv_bss)) {
|
|
/* not interested in */
|
|
IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT,
|
|
bssid, NULL, "%s", "not to bss or xrbss");
|
|
vap->iv_stats.is_rx_wrongbss++;
|
|
goto out;
|
|
}
|
|
}
|
|
#else
|
|
if (!IEEE80211_ADDR_EQ(bssid, vap->iv_bssid) &&
|
|
!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;
|
|
}
|
|
|
|
#endif
|
|
break;
|
|
case IEEE80211_M_WDS:
|
|
if (skb->len < sizeof(struct ieee80211_frame_addr4)) {
|
|
IEEE80211_DISCARD_MAC(vap,
|
|
IEEE80211_MSG_ANY, 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_bssid) &&
|
|
!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;
|
|
}
|
|
/* since ieee80211_input() can be called multiple times for
|
|
* flooding VAPs when we don't know which VAP needs the packet -
|
|
* we don't want to update the wrong state when ni is assigned
|
|
* to the bss node to accommodate this case. */
|
|
if (IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) {
|
|
ni->ni_rssi = rssi;
|
|
ni->ni_rtsf = rtsf;
|
|
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_LEQ(rxseq, ni->ni_rxseqs[tid])) {
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
switch (type) {
|
|
case IEEE80211_FC0_TYPE_DATA:
|
|
hdrlen = ieee80211_hdrsize(wh);
|
|
if (skb->len < hdrlen) {
|
|
IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY,
|
|
wh, "data", "too short: len %u, expecting %u",
|
|
skb->len, hdrlen);
|
|
vap->iv_stats.is_rx_tooshort++;
|
|
goto out; /* XXX */
|
|
}
|
|
switch (vap->iv_opmode) {
|
|
case IEEE80211_M_STA:
|
|
if ((dir != IEEE80211_FC1_DIR_FROMDS) &&
|
|
(!((vap->iv_flags_ext & IEEE80211_FEXT_WDS) &&
|
|
(dir == IEEE80211_FC1_DIR_DSTODS)))) {
|
|
IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY,
|
|
wh, "data", "invalid dir 0x%x", dir);
|
|
vap->iv_stats.is_rx_wrongdir++;
|
|
goto out;
|
|
}
|
|
|
|
if (IEEE80211_IS_MULTICAST(wh->i_addr1)) {
|
|
/* Discard multicast if IFF_MULTICAST not set */
|
|
if ((0 != memcmp(wh->i_addr3, dev->broadcast, ETH_ALEN)) &&
|
|
(0 == (dev->flags & IFF_MULTICAST))) {
|
|
IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT,
|
|
wh, NULL, "%s", "multicast disabled.");
|
|
printk(KERN_ERR "CONFIG ERROR: multicast flag "
|
|
"cleared on radio, but multicast was used.\n");
|
|
vap->iv_stats.is_rx_mcastdisabled++;
|
|
goto out;
|
|
}
|
|
/* Discard echos of our own multicast or broadcast */
|
|
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 brodcasted 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_unref_node(&ni_wds);
|
|
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_ANY,
|
|
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:
|
|
if ((dir != IEEE80211_FC1_DIR_TODS) &&
|
|
(dir != IEEE80211_FC1_DIR_DSTODS)) {
|
|
IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY,
|
|
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)
|
|
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.
|
|
*/
|
|
/* XXX: Useless node mgmt API; make better */
|
|
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. remove the
|
|
* reference to the previous station and add
|
|
* reference to the new one
|
|
*/
|
|
(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_unref_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_ANY,
|
|
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;
|
|
}
|
|
|
|
/* These frames have no further meaning. */
|
|
if ((subtype == IEEE80211_FC0_SUBTYPE_NULL) ||
|
|
(subtype == IEEE80211_FC0_SUBTYPE_QOS_NULL))
|
|
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, hdrlen);
|
|
if (key == NULL) {
|
|
/* NB: stats+msgs handled in crypto_decap */
|
|
IEEE80211_NODE_STAT(ni, rx_wepfail);
|
|
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, hdrlen);
|
|
if (skb == NULL) {
|
|
/* Fragment dropped or frame not complete yet */
|
|
goto out;
|
|
}
|
|
}
|
|
wh = NULL; /* no longer valid, catch any uses */
|
|
|
|
/* Next strip any MSDU crypto. bits. */
|
|
if (key != NULL &&
|
|
!ieee80211_crypto_demic(vap, key, skb, hdrlen, 0)) {
|
|
IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT,
|
|
ni->ni_macaddr, "data", "%s", "demic error");
|
|
IEEE80211_NODE_STAT(ni, rx_demicfail);
|
|
goto out;
|
|
}
|
|
|
|
/* Finally, strip the 802.11 header. */
|
|
skb = ieee80211_decap(vap, skb, hdrlen);
|
|
if (skb == NULL) {
|
|
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;
|
|
|
|
#ifdef ATH_SUPERG_FF
|
|
/* check for FF */
|
|
llc = (struct llc *)(skb->data + sizeof(struct ether_header));
|
|
if (ntohs(llc->llc_snap.ether_type) == (u_int16_t)ATH_ETH_TYPE) {
|
|
struct sk_buff *skb1 = NULL;
|
|
struct ether_header *eh_tmp;
|
|
struct athl2p_tunnel_hdr *ath_hdr;
|
|
unsigned int frame_len;
|
|
|
|
/* NB: assumes linear (i.e., non-fragmented) skb */
|
|
|
|
/* get to the tunneled headers */
|
|
ath_hdr = (struct athl2p_tunnel_hdr *)
|
|
skb_pull(skb, sizeof(struct ether_header) + LLC_SNAPFRAMELEN);
|
|
/* ignore invalid frames */
|
|
if (ath_hdr == NULL)
|
|
goto err;
|
|
|
|
/* only implementing FF now. drop all others. */
|
|
if (ath_hdr->proto != ATH_L2TUNNEL_PROTO_FF) {
|
|
IEEE80211_DISCARD_MAC(vap,
|
|
IEEE80211_MSG_SUPG | IEEE80211_MSG_INPUT,
|
|
eh->ether_shost, "fast-frame",
|
|
"bad atheros tunnel prot %u",
|
|
ath_hdr->proto);
|
|
vap->iv_stats.is_rx_badathtnl++;
|
|
goto err;
|
|
}
|
|
vap->iv_stats.is_rx_ffcnt++;
|
|
|
|
/* move past the tunneled header, with alignment */
|
|
skb_pull(skb, roundup(sizeof(struct athl2p_tunnel_hdr) - 2, 4) + 2);
|
|
eh_tmp = (struct ether_header *)skb->data;
|
|
|
|
/* ether_type must be length as FF frames are always LLC/SNAP encap'd */
|
|
frame_len = ntohs(eh_tmp->ether_type);
|
|
|
|
skb1 = skb_copy(skb, GFP_ATOMIC);
|
|
if (skb1 == NULL)
|
|
goto err;
|
|
ieee80211_skb_copy_noderef(skb, skb1);
|
|
|
|
/* we now have 802.3 MAC hdr followed by 802.2 LLC/SNAP; convert to EthernetII.
|
|
* Note that the frame is at least IEEE80211_MIN_LEN, due to the driver code. */
|
|
athff_decap(skb);
|
|
|
|
/* remove second frame from end of first */
|
|
skb_trim(skb, sizeof(struct ether_header) + frame_len - LLC_SNAPFRAMELEN);
|
|
|
|
/* prepare second tunneled frame */
|
|
skb_pull(skb1, roundup(sizeof(struct ether_header) + frame_len, 4));
|
|
|
|
/* Fail if there is no space left for at least the necessary headers */
|
|
if (athff_decap(skb1)) {
|
|
IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT,
|
|
ni->ni_macaddr, "data", "%s", "Decapsulation error");
|
|
vap->iv_stats.is_rx_decap++;
|
|
IEEE80211_NODE_STAT(ni, rx_decap);
|
|
ieee80211_dev_kfree_skb(&skb1); /* This is a copy! */
|
|
goto err;
|
|
}
|
|
|
|
/* Deliver the frames. */
|
|
ieee80211_deliver_data(ni, skb);
|
|
ieee80211_deliver_data(ni, skb1);
|
|
} else {
|
|
/* Assume non-atheros LLC type. */
|
|
ieee80211_deliver_data(ni, skb);
|
|
}
|
|
#else /* !ATH_SUPERG_FF */
|
|
ieee80211_deliver_data(ni, skb);
|
|
#endif
|
|
if (ni_or_null == NULL)
|
|
ieee80211_unref_node(&ni);
|
|
/* XXX: Why doesn't this use 'goto out'?
|
|
* If it did, then the SKB would be accessed after we
|
|
* have given it to ieee80211_deliver_data and we get
|
|
* crashes/errors. */
|
|
return IEEE80211_FC0_TYPE_DATA;
|
|
case IEEE80211_FC0_TYPE_MGT:
|
|
/* WDS opmode does not support management frames. */
|
|
if (vap->iv_opmode == IEEE80211_M_WDS) {
|
|
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_ANY,
|
|
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 " MAC_FMT " rssi %d\n",
|
|
ieee80211_mgt_subtype_name[subtype >>
|
|
IEEE80211_FC0_SUBTYPE_SHIFT],
|
|
MAC_ADDR(wh->i_addr2), rssi);
|
|
}
|
|
#endif
|
|
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;
|
|
}
|
|
hdrlen = ieee80211_hdrsize(wh);
|
|
key = ieee80211_crypto_decap(ni, skb, hdrlen);
|
|
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(vap, ni_or_null, skb, subtype, rssi, rtsf);
|
|
goto out;
|
|
|
|
case IEEE80211_FC0_TYPE_CTL:
|
|
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);
|
|
goto out;
|
|
|
|
default:
|
|
IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY,
|
|
wh, NULL, "bad frame type 0x%x", type);
|
|
/* should not come here */
|
|
break;
|
|
}
|
|
err:
|
|
vap->iv_devstats.rx_errors++;
|
|
out:
|
|
ieee80211_dev_kfree_skb(&skb);
|
|
if (ni_or_null == NULL)
|
|
ieee80211_unref_node(&ni);
|
|
return type;
|
|
#undef HAS_SEQ
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_input);
|
|
|
|
/*
|
|
* Deliver a received skb to all VAPs, free the skb.
|
|
*
|
|
* Context: softIRQ (tasklet)
|
|
*/
|
|
int
|
|
ieee80211_input_all(struct ieee80211com *ic,
|
|
struct sk_buff *skb, int rssi, u_int64_t rtsf)
|
|
{
|
|
struct ieee80211vap *vap, *first_vap = NULL;
|
|
int type; /* Used to determine when to blink LEDs. */
|
|
|
|
for (vap = TAILQ_FIRST(&ic->ic_vaps); vap;
|
|
vap = TAILQ_NEXT(vap, iv_next)) {
|
|
struct sk_buff *tskb;
|
|
|
|
/* Check if the interface is up and running */
|
|
if ((vap->iv_dev->flags & (IFF_RUNNING | IFF_UP)) !=
|
|
(IFF_RUNNING | IFF_UP))
|
|
continue;
|
|
|
|
/* The first VAP will get a special treatment - it will
|
|
get the original skb to avoid unneeded skb copying */
|
|
if (!first_vap) {
|
|
first_vap = vap;
|
|
continue;
|
|
}
|
|
|
|
/* Other VAPs get a copy of the skb */
|
|
tskb = skb_copy(skb, GFP_ATOMIC);
|
|
if (!tskb) {
|
|
/* XXX: Brilliant OOM handling. */
|
|
vap->iv_devstats.tx_dropped++;
|
|
continue;
|
|
}
|
|
ieee80211_input(vap, NULL, tskb, rssi, rtsf);
|
|
}
|
|
|
|
/* Process the first VAP now. Any VAP should return a valid type
|
|
unless something is very wrong (invalid packet etc). */
|
|
if (first_vap)
|
|
type = ieee80211_input(first_vap, NULL, skb, rssi, rtsf);
|
|
else {
|
|
/* No active VAPs, just free the skb */
|
|
ieee80211_dev_kfree_skb(&skb);
|
|
type = -1;
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_input_all);
|
|
|
|
/*
|
|
* 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(ETHERTYPE_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;
|
|
} else {
|
|
/*
|
|
* When denying unencrypted frames, discard
|
|
* any non-PAE frames received without encryption.
|
|
*/
|
|
if ((vap->iv_flags & IEEE80211_F_DROPUNENC) && key == NULL) {
|
|
IEEE80211_NODE_STAT(ni, rx_unencrypted);
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
|
|
#undef IS_EAPOL
|
|
#undef PAIRWISE_SET
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/*
|
|
* 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 */
|
|
ieee80211_dev_kfree_skb(&skb);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
* XXX HUGE HORRIFIC HACK
|
|
*/
|
|
ni->ni_rxfragstamp = jiffies;
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
ieee80211_dev_kfree_skb(&ni->ni_rxfrag);
|
|
}
|
|
}
|
|
|
|
/* 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 continuous buffer to
|
|
* assemble fragments
|
|
*/
|
|
ni->ni_rxfrag = skb_copy(skb, GFP_ATOMIC);
|
|
if (ni->ni_rxfrag) {
|
|
ieee80211_skb_copy_noderef(skb, ni->ni_rxfrag);
|
|
ieee80211_dev_kfree_skb(&skb);
|
|
}
|
|
}
|
|
/*
|
|
* Check that we have enough space to hold
|
|
* incoming fragments
|
|
* XXX 4-address/QoS frames?
|
|
*/
|
|
else if ((skb_end_pointer(skb) - skb->head) <
|
|
(ni->ni_vap->iv_dev->mtu + hdrlen)) {
|
|
ni->ni_rxfrag = skb_copy_expand(skb, 0,
|
|
(ni->ni_vap->iv_dev->mtu + hdrlen) -
|
|
(skb_end_pointer(skb) - skb->head),
|
|
GFP_ATOMIC);
|
|
if (ni->ni_rxfrag)
|
|
ieee80211_skb_copy_noderef(skb, ni->ni_rxfrag);
|
|
ieee80211_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 */
|
|
ieee80211_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;
|
|
struct ieee80211_node *tni;
|
|
int ret;
|
|
|
|
#ifdef ATH_SUPERG_XR
|
|
/*
|
|
* if it is a XR vap, send the data to associated normal net
|
|
* device. XR vap has a net device which is not registered with
|
|
* OS.
|
|
*/
|
|
if (vap->iv_xrvap && vap->iv_flags & IEEE80211_F_XR)
|
|
dev = vap->iv_xrvap->iv_dev;
|
|
#endif
|
|
|
|
/* perform as a bridge within the vap */
|
|
/* XXX 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)) {
|
|
/* Create a SKB for the BSS to send out. */
|
|
skb1 = skb_copy(skb, GFP_ATOMIC);
|
|
if (skb1)
|
|
SKB_NI(skb1) = ieee80211_ref_node(vap->iv_bss);
|
|
}
|
|
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) &&
|
|
(ni1 != vap->iv_bss) &&
|
|
ieee80211_node_is_authorized(ni1)) {
|
|
skb1 = skb;
|
|
skb = NULL;
|
|
}
|
|
/* XXX: statistic? */
|
|
ieee80211_unref_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);
|
|
|
|
/* This SKB is being emitted to the physical/parent
|
|
* device, which maintains node references. However,
|
|
* there is kernel code in between which does not.
|
|
* Therefore, the ref. is cleaned if the SKB is
|
|
* dropped. */
|
|
tni = SKB_NI(skb1);
|
|
/* XXX: Insert vlan tag before queuing it? */
|
|
if (dev_queue_xmit(skb1) == NET_XMIT_DROP) {
|
|
vap->iv_devstats.tx_dropped++;
|
|
if (tni != NULL)
|
|
ieee80211_unref_node(&tni);
|
|
}
|
|
/* SKB is no longer ours, either way after dev_queue_xmit. */
|
|
skb1 = NULL;
|
|
}
|
|
}
|
|
|
|
if (skb != NULL) {
|
|
vap->iv_devstats.rx_packets++;
|
|
vap->iv_devstats.rx_bytes += skb->len;
|
|
dev->last_rx = jiffies;
|
|
|
|
skb->dev = dev;
|
|
#ifdef USE_HEADERLEN_RESV
|
|
skb->protocol = ath_eth_type_trans(skb, dev);
|
|
#else
|
|
skb->protocol = eth_type_trans(skb, dev);
|
|
#endif
|
|
tni = SKB_NI(skb);
|
|
#if IEEE80211_VLAN_TAG_USED
|
|
if ((ni->ni_vlan != 0) && (vap->iv_vlgrp != NULL))
|
|
/* Attach VLAN tag. */
|
|
ret = vlan_hwaccel_rx(skb,
|
|
vap->iv_vlgrp, ni->ni_vlan);
|
|
else
|
|
#endif
|
|
ret = netif_rx(skb);
|
|
if (ret == NET_RX_DROP)
|
|
vap->iv_devstats.rx_dropped++;
|
|
if (tni != NULL)
|
|
ieee80211_unref_node(&tni);
|
|
skb = NULL; /* SKB is no longer ours */
|
|
}
|
|
}
|
|
|
|
/* This function removes the 802.11 header, including LLC/SNAP headers and
|
|
* replaces it with an Ethernet II header. */
|
|
static struct sk_buff *
|
|
ieee80211_decap(struct ieee80211vap *vap, struct sk_buff *skb, int hdrlen)
|
|
{
|
|
const struct llc snap_hdr = {.llc_dsap = LLC_SNAP_LSAP,
|
|
.llc_ssap = LLC_SNAP_LSAP,
|
|
.llc_snap.control = LLC_UI,
|
|
.llc_snap.org_code = {0x0, 0x0, 0x0}};
|
|
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); /* Make a copy of the variably sized .11 header */
|
|
|
|
llc = (struct llc *)skb_pull(skb, hdrlen);
|
|
/* XXX: For some unknown reason some APs think they are from DEC and
|
|
* use an OUI of 00-00-f8. This should be killed as soon as sanity is
|
|
* restored. */
|
|
if ((skb->len >= LLC_SNAPFRAMELEN) && (memcmp(&snap_hdr, llc, 5) == 0) &&
|
|
((llc->llc_snap.org_code[2] == 0x0) ||
|
|
(llc->llc_snap.org_code[2] == 0xf8))) {
|
|
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);
|
|
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);
|
|
IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr3);
|
|
break;
|
|
case IEEE80211_FC1_DIR_DSTODS:
|
|
IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3);
|
|
IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr4);
|
|
break;
|
|
}
|
|
|
|
if (llc != NULL)
|
|
eh->ether_type = htons(skb->len - sizeof(*eh));
|
|
else
|
|
eh->ether_type = ether_type;
|
|
|
|
return skb;
|
|
}
|
|
|
|
/*
|
|
* 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];
|
|
memcpy(rs->rs_rates, rates + 2, rs->rs_nrates);
|
|
if (xrates != NULL) {
|
|
u_int8_t nxrates;
|
|
/*
|
|
* 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;
|
|
}
|
|
return ieee80211_fix_rate(ni, flags);
|
|
}
|
|
|
|
static void
|
|
ieee80211_auth_open(struct ieee80211_node *ni, struct ieee80211_frame *wh,
|
|
int rssi, u_int64_t rtsf, u_int16_t seq, u_int16_t status)
|
|
{
|
|
struct ieee80211vap *vap = ni->ni_vap;
|
|
unsigned int tmpnode = 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) {
|
|
if (ni == vap->iv_bss) {
|
|
ni = ieee80211_dup_bss(vap, wh->i_addr2, 0);
|
|
if (ni == NULL)
|
|
return;
|
|
tmpnode = 1;
|
|
}
|
|
IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH,
|
|
(seq + 1) | (IEEE80211_STATUS_ALG << 16));
|
|
|
|
if (tmpnode)
|
|
ieee80211_unref_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++;
|
|
return;
|
|
}
|
|
/* always accept open authentication requests */
|
|
if (ni == vap->iv_bss) {
|
|
ni = ieee80211_dup_bss(vap, wh->i_addr2, 0);
|
|
if (ni == NULL)
|
|
return;
|
|
tmpnode = 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");
|
|
/*
|
|
* When 802.1x is not in use mark the port
|
|
* authorized at this point so traffic can flow.
|
|
*/
|
|
if (ni->ni_authmode != IEEE80211_AUTH_8021X)
|
|
ieee80211_node_authorize(ni);
|
|
if (tmpnode)
|
|
ieee80211_unref_node(&ni);
|
|
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 istmp;
|
|
|
|
if (ni == vap->iv_bss) {
|
|
ni = ieee80211_dup_bss(vap, mac, 1);
|
|
if (ni == NULL) {
|
|
/* XXX msg */
|
|
return;
|
|
}
|
|
istmp = 1;
|
|
} else
|
|
istmp = 0;
|
|
IEEE80211_SEND_MGMT(ni, subtype, arg);
|
|
if (istmp)
|
|
ieee80211_unref_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_int64_t rtsf,
|
|
u_int16_t seq, u_int16_t status)
|
|
{
|
|
struct ieee80211vap *vap = ni->ni_vap;
|
|
u_int8_t *challenge;
|
|
int allocbs = 0, estatus = 0;
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
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, 0);
|
|
if (ni == NULL) {
|
|
/* NB: no way to return an error */
|
|
return;
|
|
}
|
|
allocbs = 1;
|
|
}
|
|
|
|
ni->ni_rssi = rssi;
|
|
ni->ni_rtsf = rtsf;
|
|
ni->ni_last_rx = jiffies;
|
|
if (!alloc_challenge(ni)) {
|
|
if (allocbs)
|
|
ieee80211_unref_node(&ni);
|
|
/* NB: don't return error so they rexmit */
|
|
return;
|
|
}
|
|
get_random_bytes(ni->ni_challenge,
|
|
IEEE80211_CHALLENGE_LEN);
|
|
IEEE80211_NOTE(vap,
|
|
IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni,
|
|
"shared key %sauth request", allocbs ? "" : "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);
|
|
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 (allocbs)
|
|
ieee80211_unref_node(&ni);
|
|
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));
|
|
/* Remove node state if it exists and isn't just a
|
|
* temporary copy of the bss (dereferenced later) */
|
|
if (!allocbs && (ni != vap->iv_bss))
|
|
ieee80211_node_leave(ni);
|
|
} 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);
|
|
}
|
|
if (allocbs)
|
|
ieee80211_unref_node(&ni);
|
|
}
|
|
|
|
/* 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 0; \
|
|
} \
|
|
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 0; \
|
|
} \
|
|
} 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 0; \
|
|
} \
|
|
} 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)
|
|
{
|
|
printk("[" MAC_FMT "] discard %s frame, ssid mismatch: ",
|
|
MAC_ADDR(mac), tag);
|
|
ieee80211_print_essid(ssid + 2, ssid[1]);
|
|
printk("\n");
|
|
}
|
|
|
|
#define IEEE80211_VERIFY_SSID(_ni, _ssid) do { \
|
|
if ((_ssid)[1] != 0 && \
|
|
((_ssid)[1] != (_ni)->ni_esslen || \
|
|
memcmp((_ssid) + 2, (_ni)->ni_essid, (_ssid)[1]) != 0)) { \
|
|
if (ieee80211_msg_input(vap)) \
|
|
ieee80211_ssid_mismatch(vap, \
|
|
ieee80211_mgt_subtype_name[subtype >> \
|
|
IEEE80211_FC0_SUBTYPE_SHIFT], \
|
|
wh->i_addr2, _ssid); \
|
|
vap->iv_stats.is_rx_ssidmismatch++; \
|
|
return 0; \
|
|
} \
|
|
} while (0)
|
|
#else /* !IEEE80211_DEBUG */
|
|
#define IEEE80211_VERIFY_SSID(_ni, _ssid) do { \
|
|
if ((_ssid)[1] != 0 && \
|
|
((_ssid)[1] != (_ni)->ni_esslen || \
|
|
memcmp((_ssid) + 2, (_ni)->ni_essid, (_ssid)[1]) != 0)) { \
|
|
vap->iv_stats.is_rx_ssidmismatch++; \
|
|
return 0; \
|
|
} \
|
|
} while (0)
|
|
#endif /* !IEEE80211_DEBUG */
|
|
|
|
/* 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_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)))
|
|
|
|
static __inline int
|
|
iswpaoui(const u_int8_t *frm)
|
|
{
|
|
return frm[1] > 3 && LE_READ_4(frm+2) == ((WPA_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
|
|
isatherosoui(const u_int8_t *frm)
|
|
{
|
|
return frm[1] > 3 && LE_READ_4(frm+2) == ((ATH_OUI_TYPE<<24)|ATH_OUI);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
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))
|
|
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_NONE):
|
|
return RSN_ASE_NONE;
|
|
}
|
|
return 0; /* NB: so is discarded */
|
|
#undef RSN_SEL
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
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;
|
|
}
|
|
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))
|
|
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;
|
|
}
|
|
w &= rsn_parm->rsn_keymgmtset;
|
|
if (w == 0) {
|
|
IEEE80211_DISCARD_IE(vap,
|
|
IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
|
|
wh, "RSN", "%s", "no acceptable key mgmt alg");
|
|
return IEEE80211_REASON_IE_INVALID;
|
|
}
|
|
if (w & RSN_ASE_8021X_UNSPEC)
|
|
rsn_parm->rsn_keymgmt = RSN_ASE_8021X_UNSPEC;
|
|
else
|
|
rsn_parm->rsn_keymgmt = RSN_ASE_8021X_PSK;
|
|
|
|
/* optional RSN capabilities */
|
|
if (len > 2)
|
|
rsn_parm->rsn_caps = LE_READ_2(frm);
|
|
/* XXX PMKID */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Record information element for later use. */
|
|
void
|
|
ieee80211_saveie(u_int8_t **iep, const u_int8_t *ie)
|
|
{
|
|
if ((*iep == NULL) || (ie == NULL) || ((*iep)[1] != ie[1])) {
|
|
if (*iep != NULL)
|
|
FREE(*iep, M_DEVBUF);
|
|
*iep = NULL;
|
|
if (ie != NULL)
|
|
MALLOC(*iep, void*, ie[1] + 2, M_DEVBUF, M_NOWAIT);
|
|
}
|
|
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;
|
|
switch (WME_UAPSD_MAXSP(ni->ni_uapsd)) {
|
|
case 1:
|
|
ni->ni_uapsd_maxsp = 2; break;
|
|
case 2:
|
|
ni->ni_uapsd_maxsp = 4; break;
|
|
case 3:
|
|
ni->ni_uapsd_maxsp = 6; break;
|
|
default:
|
|
ni->ni_uapsd_maxsp = WME_UAPSD_NODE_MAXQDEPTH;
|
|
}
|
|
}
|
|
IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_POWER, ni,
|
|
"UAPSD bit settings from STA: %02x", ni->ni_uapsd);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
ieee80211_parse_wmeparams(struct ieee80211vap *vap, u_int8_t *frm,
|
|
const struct ieee80211_frame *wh, u_int8_t *qosinfo)
|
|
{
|
|
#define MS(_v, _f) (((_v) & _f) >> _f##_S)
|
|
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 wmeParams *wmep =
|
|
&wme->wme_wmeChanParams.cap_wmeParams[i];
|
|
/* NB: ACI not used */
|
|
wmep->wmep_acm = MS(frm[0], WME_PARAM_ACM);
|
|
wmep->wmep_aifsn = MS(frm[0], WME_PARAM_AIFSN);
|
|
wmep->wmep_logcwmin = MS(frm[1], WME_PARAM_LOGCWMIN);
|
|
wmep->wmep_logcwmax = MS(frm[1], WME_PARAM_LOGCWMAX);
|
|
wmep->wmep_txopLimit = LE_READ_2(frm + 2);
|
|
frm += 4;
|
|
}
|
|
wme->wme_wmeChanParams.cap_info_count = qosinfo_count;
|
|
return 1;
|
|
#undef MS
|
|
}
|
|
|
|
static void
|
|
ieee80211_parse_athParams(struct ieee80211_node *ni, u_int8_t *ie)
|
|
{
|
|
#ifdef ATH_SUPERG_DYNTURBO
|
|
struct ieee80211vap *vap = ni->ni_vap;
|
|
struct ieee80211com *ic = ni->ni_ic;
|
|
#endif /* ATH_SUPERG_DYNTURBO */
|
|
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);
|
|
#if 0
|
|
/* NB: too noisy */
|
|
IEEE80211_NOTE(vap, IEEE80211_MSG_SUPG, ni,
|
|
"recv ath params: caps 0x%x flags 0x%x defkeyix %u",
|
|
athIe->athAdvCap_capability, ni->ni_ath_flags,
|
|
ni->ni_ath_defkeyindex);
|
|
#endif
|
|
#ifdef ATH_SUPERG_DYNTURBO
|
|
if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_ATHC_TURBOP)) {
|
|
u_int16_t curflags, newflags;
|
|
|
|
/*
|
|
* Check for turbo mode switch. Calculate flags
|
|
* for the new mode and effect the switch.
|
|
*/
|
|
newflags = curflags = ic->ic_bsschan->ic_flags;
|
|
/* NB: ATHC_BOOST is not in ic_ath_cap, so get it from the ie */
|
|
if (athIe->athAdvCap_capability & IEEE80211_ATHC_BOOST)
|
|
newflags |= IEEE80211_CHAN_TURBO;
|
|
else
|
|
newflags &= ~IEEE80211_CHAN_TURBO;
|
|
if (newflags != curflags)
|
|
ieee80211_dturbo_switch(ic, newflags);
|
|
}
|
|
#endif /* ATH_SUPERG_DYNTURBO */
|
|
}
|
|
|
|
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;
|
|
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;
|
|
/* We duplicate the reference after skb_copy */
|
|
ieee80211_skb_copy_noderef(skb, skb1);
|
|
skb1->dev = dev;
|
|
skb_reset_mac_header(skb1);
|
|
|
|
skb1->ip_summed = CHECKSUM_NONE;
|
|
skb1->pkt_type = PACKET_OTHERHOST;
|
|
skb1->protocol = __constant_htons(0x0019); /* ETH_P_80211_RAW */
|
|
|
|
vap->iv_devstats.rx_packets++;
|
|
vap->iv_devstats.rx_bytes += skb1->len;
|
|
|
|
if (SKB_NI(skb1) != NULL)
|
|
ieee80211_unref_node(&SKB_NI(skb1));
|
|
if (netif_rx(skb1) == NET_RX_DROP)
|
|
vap->iv_devstats.rx_dropped++;
|
|
}
|
|
}
|
|
|
|
void
|
|
ieee80211_saveath(struct ieee80211_node *ni, u_int8_t *ie)
|
|
{
|
|
const struct ieee80211_ie_athAdvCap *athIe =
|
|
(const struct ieee80211_ie_athAdvCap *)ie;
|
|
|
|
ieee80211_saveie(&ni->ni_ath_ie, ie);
|
|
if (athIe != NULL) {
|
|
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);
|
|
} else {
|
|
ni->ni_ath_flags = 0;
|
|
ni->ni_ath_defkeyindex = IEEE80211_INVAL_DEFKEY;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Structure to be passed through ieee80211_iterate_nodes() to count_nodes()
|
|
*/
|
|
struct count_nodes_arg {
|
|
const int k;
|
|
const int *subset;
|
|
int count;
|
|
struct ieee80211_node *new;
|
|
};
|
|
|
|
/* Count nodes which don't support at least one of arg->subset. */
|
|
static void
|
|
count_nodes(void *_arg, struct ieee80211_node *ni)
|
|
{
|
|
struct count_nodes_arg *arg = (struct count_nodes_arg *)_arg;
|
|
int i;
|
|
|
|
if (ni->ni_suppchans == NULL)
|
|
return;
|
|
|
|
if (ni == arg->new)
|
|
return;
|
|
|
|
for (i = 0; i < arg->k; i++)
|
|
if (isclr(ni->ni_suppchans, arg->subset[i])) {
|
|
arg->count++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Structure to be passed through combinations() to channel_combination() */
|
|
struct channel_combination_arg {
|
|
struct ieee80211com *ic;
|
|
struct ieee80211_node *new;
|
|
int *best;
|
|
int benefit;
|
|
};
|
|
|
|
#ifdef IEEE80211_DEBUG
|
|
/* sprintf() set[] array consisting of k integers */
|
|
static const char*
|
|
ints_sprintf(const int k, const int set[])
|
|
{
|
|
static char buf[915]; /* 0-255: 10*2 + 90*3 + 156*4 + '\0' */
|
|
char *ptr = buf;
|
|
int i;
|
|
for (i = 0; i < k; i++)
|
|
ptr += snprintf(ptr, buf + sizeof(buf) - ptr, "%d ", set[i]);
|
|
return buf;
|
|
}
|
|
#endif
|
|
|
|
/* Action done for each combination of channels that are not supported by
|
|
* currently joining station. */
|
|
static void
|
|
channel_combination(const int k, const int subset[], void *_arg)
|
|
{
|
|
struct channel_combination_arg *arg =
|
|
(struct channel_combination_arg *)_arg;
|
|
struct ieee80211com *ic = arg->ic;
|
|
struct count_nodes_arg cn_arg = { k, subset, 0, arg->new };
|
|
int permil, allowed;
|
|
int sta_assoc = ic->ic_sta_assoc; /* make > 0 check consistent
|
|
* with / operation */
|
|
|
|
ieee80211_iterate_nodes(&arg->ic->ic_sta,
|
|
&count_nodes, (void*)&cn_arg);
|
|
|
|
/* The following two sanity checks can theoretically fail due to lack
|
|
* of locking, but since it is not fatal, we will just print a debug
|
|
* msg and neglect it */
|
|
if (cn_arg.count == 0) {
|
|
IEEE80211_NOTE(arg->new->ni_vap, IEEE80211_MSG_ANY, arg->new,
|
|
"%s", "ic_chan_nodes incosistency (incorrect "
|
|
"uncommon channel count)");
|
|
return;
|
|
}
|
|
if (sta_assoc == 0) {
|
|
IEEE80211_NOTE(arg->new->ni_vap, IEEE80211_MSG_ANY, arg->new,
|
|
"%s", "no STAs associated, so there should be "
|
|
"no \"uncommon\" channels");
|
|
return;
|
|
}
|
|
|
|
permil = 1000 * cn_arg.count / sta_assoc;
|
|
allowed = ic->ic_sc_slcg * k;
|
|
/* clamp it to provide more sensible output */
|
|
if (allowed > 1000)
|
|
allowed = 1000;
|
|
|
|
IEEE80211_NOTE(arg->new->ni_vap, IEEE80211_MSG_ASSOC|IEEE80211_MSG_DOTH,
|
|
arg->new, "Making channels %savailable would require "
|
|
"kicking out %d stations,", ints_sprintf(k, subset),
|
|
cn_arg.count);
|
|
IEEE80211_NOTE(arg->new->ni_vap, IEEE80211_MSG_ASSOC|IEEE80211_MSG_DOTH,
|
|
arg->new, "what is %d permils of all associated STAs "
|
|
"(slcg permits < %d).", permil, allowed);
|
|
|
|
if (permil > allowed)
|
|
return;
|
|
if (allowed - permil > arg->benefit) {
|
|
memcpy(arg->best, subset, k * sizeof(*subset));
|
|
arg->benefit = allowed - permil;
|
|
}
|
|
}
|
|
|
|
/* Enumerate all combinations of k-element subset of n-element set via a
|
|
* callback function. */
|
|
static void
|
|
combinations(int n, int set[], int k,
|
|
void (*callback)(const int, const int [], void *), void *arg)
|
|
{
|
|
int subset[k], pos[k], i;
|
|
for (i = 0; i < k; i++)
|
|
pos[i] = 0;
|
|
i = 0;
|
|
forward:
|
|
if (i > 0) {
|
|
while (set[pos[i]] < subset[i - 1] && pos[i] < n)
|
|
pos[i]++;
|
|
if (pos[i] == n)
|
|
goto backward;
|
|
}
|
|
subset[i] = set[pos[i]];
|
|
set[pos[i]] = set[n - 1];
|
|
n--;
|
|
|
|
i++;
|
|
if (i == k) {
|
|
callback(k, subset, arg);
|
|
} else {
|
|
pos[i] = 0;
|
|
goto forward;
|
|
}
|
|
backward:
|
|
i--;
|
|
if (i < 0)
|
|
return;
|
|
set[pos[i]] = subset[i];
|
|
n++;
|
|
|
|
pos[i]++;
|
|
if (pos[i] == n)
|
|
goto backward;
|
|
goto forward;
|
|
}
|
|
|
|
static __inline int
|
|
find_worse_nodes(struct ieee80211com *ic, struct ieee80211_node *new)
|
|
{
|
|
int i, tmp1, tmp2;
|
|
u_int16_t n_common, n_uncommon;
|
|
u_int16_t cn_total = ic->ic_cn_total;
|
|
u_int16_t to_gain;
|
|
|
|
if (cn_total == 0)
|
|
/* should not happen */
|
|
return 1;
|
|
|
|
n_common = n_uncommon = 0;
|
|
|
|
CHANNEL_FOREACH(i, ic, tmp1, tmp2) {
|
|
if (isset(new->ni_suppchans_new, i)) {
|
|
if (ic->ic_chan_nodes[i] == ic->ic_cn_total) {
|
|
n_common++;
|
|
} else {
|
|
n_uncommon++;
|
|
}
|
|
}
|
|
}
|
|
|
|
to_gain = ic->ic_sc_mincom - n_common + 1;
|
|
IEEE80211_NOTE(new->ni_vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DOTH,
|
|
new, "By accepting STA we would need to gain at least "
|
|
"%d common channels.", to_gain);
|
|
IEEE80211_NOTE(new->ni_vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DOTH,
|
|
new, "%d channels supported by the joining STA are "
|
|
"not commonly supported by others.", n_uncommon);
|
|
|
|
if (to_gain > n_uncommon) {
|
|
IEEE80211_NOTE(new->ni_vap, IEEE80211_MSG_ASSOC |
|
|
IEEE80211_MSG_DOTH, new, "%s",
|
|
"Even disassociating all the nodes will not "
|
|
"be enough.");
|
|
return 0;
|
|
}
|
|
|
|
{
|
|
int uncommon[n_uncommon];
|
|
int best[to_gain];
|
|
struct channel_combination_arg arg = { ic, new, best, -1 };
|
|
int j = 0;
|
|
|
|
CHANNEL_FOREACH(i, ic, tmp1, tmp2)
|
|
if (isset(new->ni_suppchans_new, i) &&
|
|
(ic->ic_chan_nodes[i] !=
|
|
ic->ic_cn_total)) {
|
|
if (j == n_uncommon)
|
|
/* silent assert */
|
|
break;
|
|
uncommon[j++] = i;
|
|
}
|
|
|
|
combinations(n_uncommon, uncommon, to_gain,
|
|
&channel_combination, &arg);
|
|
if (arg.benefit < 0) {
|
|
IEEE80211_NOTE(new->ni_vap, IEEE80211_MSG_ASSOC |
|
|
IEEE80211_MSG_DOTH, new, "%s",
|
|
"No combination of channels allows a "
|
|
"beneficial trade-off.");
|
|
return 0;
|
|
}
|
|
IEEE80211_NOTE(new->ni_vap, IEEE80211_MSG_ASSOC |
|
|
IEEE80211_MSG_DOTH, new,
|
|
"Nodes which don't support channels %swill be "
|
|
"forced to leave.",
|
|
ints_sprintf(to_gain, best));
|
|
|
|
if (new->ni_needed_chans != NULL)
|
|
FREE(new->ni_needed_chans, M_DEVBUF);
|
|
MALLOC(new->ni_needed_chans, void*,
|
|
to_gain * sizeof(*new->ni_needed_chans),
|
|
M_DEVBUF, M_NOWAIT);
|
|
|
|
if (new->ni_needed_chans == NULL) {
|
|
IEEE80211_NOTE(new->ni_vap, IEEE80211_MSG_DEBUG |
|
|
IEEE80211_MSG_DOTH, new, "%s",
|
|
"needed_chans allocation failed");
|
|
return 0;
|
|
}
|
|
|
|
/* Store the list of channels to remove nodes which don't
|
|
* support them. */
|
|
for (i = 0; i < to_gain; i++)
|
|
new->ni_needed_chans[i] = best[i];
|
|
new->ni_n_needed_chans = to_gain;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int
|
|
ieee80211_parse_sc_ie(struct ieee80211_node *ni, u_int8_t *frm,
|
|
const struct ieee80211_frame *wh)
|
|
{
|
|
struct ieee80211_ie_sc *sc_ie = (struct ieee80211_ie_sc *)frm;
|
|
struct ieee80211com *ic = ni->ni_ic;
|
|
#ifdef IEEE80211_DEBUG
|
|
struct ieee80211vap *vap = ni->ni_vap;
|
|
int reassoc = (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) ==
|
|
IEEE80211_FC0_SUBTYPE_REASSOC_REQ;
|
|
#endif
|
|
int i, tmp1, tmp2;
|
|
int count;
|
|
|
|
if (sc_ie == NULL) {
|
|
if (ni->ni_ic->ic_sc_algorithm == IEEE80211_SC_STRICT) {
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC |
|
|
IEEE80211_MSG_DOTH, wh->i_addr2,
|
|
"deny %s request, no supported "
|
|
"channels IE",
|
|
reassoc ? "reassoc" : "assoc");
|
|
return IEEE80211_STATUS_SUPPCHAN_UNACCEPTABLE;
|
|
}
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC |
|
|
IEEE80211_MSG_DOTH, wh->i_addr2,
|
|
"%s request: no supported channels IE",
|
|
reassoc ? "reassoc" : "assoc");
|
|
return IEEE80211_STATUS_SUCCESS;
|
|
}
|
|
if (sc_ie->sc_len % 2 != 0) {
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC |
|
|
IEEE80211_MSG_DOTH, wh->i_addr2,
|
|
"deny %s request, malformed supported "
|
|
"channels IE (len)",
|
|
reassoc ? "reassoc" : "assoc");
|
|
/* XXX: deauth with IEEE80211_REASON_IE_INVALID? */
|
|
return IEEE80211_STATUS_SUPPCHAN_UNACCEPTABLE;
|
|
}
|
|
if (ni->ni_suppchans_new == NULL) {
|
|
MALLOC(ni->ni_suppchans_new, void*, IEEE80211_CHAN_BYTES,
|
|
M_DEVBUF, M_NOWAIT);
|
|
if (ni->ni_suppchans_new == NULL) {
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC |
|
|
IEEE80211_MSG_DOTH, wh->i_addr2,
|
|
"deny %s request, couldn't allocate "
|
|
"memory for SC IE!",
|
|
reassoc ? "reassoc" : "assoc");
|
|
return IEEE80211_STATUS_SUPPCHAN_UNACCEPTABLE;
|
|
}
|
|
}
|
|
memset(ni->ni_suppchans_new, 0, IEEE80211_CHAN_BYTES);
|
|
for (i = 0; i < (sc_ie->sc_len / 2); i++) {
|
|
u_int8_t chan = sc_ie->sc_subband[i].sc_first;
|
|
/* XXX: see 802.11d-2001-4-05-03-interp,
|
|
* but what about .11j, turbo, etc.? */
|
|
u_int8_t step = (chan <= 14 ? 1 : 4);
|
|
u_int16_t last = chan + step *
|
|
(sc_ie->sc_subband[i].sc_number - 1);
|
|
|
|
/* check for subband under- (sc_number == 0) or overflow */
|
|
if ((last < chan) || ((chan <= 14) && (last > 14)) ||
|
|
(chan > 14 && last > 200)) {
|
|
/* XXX: deauth with IEEE80211_REASON_IE_INVALID? */
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC |
|
|
IEEE80211_MSG_DOTH, wh->i_addr2,
|
|
"deny %s request, malformed supported "
|
|
"channels ie (subbands, %d, %d)",
|
|
reassoc ? "reassoc" : "assoc",
|
|
chan, last);
|
|
return IEEE80211_STATUS_SUPPCHAN_UNACCEPTABLE;
|
|
}
|
|
|
|
for (; chan <= last; chan += step)
|
|
setbit(ni->ni_suppchans_new, chan);
|
|
}
|
|
/* forbid STAs that claim they don't support the channel they are
|
|
* currently operating at */
|
|
if (isclr(ni->ni_suppchans_new, ic->ic_bsschan->ic_ieee)) {
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC |
|
|
IEEE80211_MSG_DOTH, wh->i_addr2,
|
|
"deny %s request, sc ie does not contain bss "
|
|
"channel(subbands)",
|
|
reassoc ? "reassoc" : "assoc");
|
|
return IEEE80211_STATUS_SUPPCHAN_UNACCEPTABLE;
|
|
}
|
|
|
|
if ((ic->ic_sc_algorithm != IEEE80211_SC_TIGHT) &&
|
|
(ic->ic_sc_algorithm != IEEE80211_SC_STRICT))
|
|
goto success;
|
|
|
|
/* count number of channels that will be common to all STAs after the
|
|
* new one joins */
|
|
count = 0;
|
|
CHANNEL_FOREACH(i, ic, tmp1, tmp2)
|
|
if (isset(ni->ni_suppchans_new, i) && (
|
|
ic->ic_chan_nodes[i] == ic->ic_cn_total))
|
|
count++;
|
|
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DOTH,
|
|
wh->i_addr2, "%s request: %d common channels, %d "
|
|
"required", reassoc ? "reassoc" : "assoc",
|
|
count, ic->ic_sc_mincom);
|
|
if (count < ic->ic_sc_mincom) {
|
|
/* common channel count decreases below the required minimum */
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DOTH,
|
|
wh->i_addr2, "%s request: not enough common "
|
|
"channels available, tight/strict algorithm "
|
|
"engaged", reassoc ? "reassoc" : "assoc");
|
|
|
|
if (!find_worse_nodes(ic, ni)) {
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC |
|
|
IEEE80211_MSG_DOTH, wh->i_addr2,
|
|
"deny %s request, tight/strict "
|
|
"criterion not met",
|
|
reassoc ? "reassoc" : "assoc");
|
|
return IEEE80211_STATUS_SUPPCHAN_UNACCEPTABLE;
|
|
}
|
|
}
|
|
|
|
success:
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_DOTH | IEEE80211_MSG_ASSOC |
|
|
IEEE80211_MSG_ELEMID, wh->i_addr2,
|
|
"%s", "supported channels ie parsing successful");
|
|
return IEEE80211_STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* This function is called at the end of the Channel Shutdown procedure, just
|
|
* before switching to the new channel (if any) */
|
|
|
|
static void
|
|
ieee80211_doth_cancel_cs(struct ieee80211com *ic)
|
|
{
|
|
del_timer(&ic->ic_csa_timer);
|
|
ic->ic_flags &= ~IEEE80211_F_CHANSWITCH;
|
|
ic->ic_set_dfs_clear(ic, 0);
|
|
}
|
|
|
|
static void
|
|
ieee80211_doth_switch_channel(struct ieee80211com *ic)
|
|
{
|
|
struct ieee80211vap *vap;
|
|
u_int32_t now_tu;
|
|
|
|
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
|
|
now_tu = IEEE80211_TSF_TO_TU(vap->iv_get_tsf(vap));
|
|
|
|
IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOTH,
|
|
"%s: Channel switch to %3d (%4d MHz) NOW! "
|
|
"(now_tu:%lu)\n", __func__,
|
|
ic->ic_csa_chan->ic_ieee,
|
|
ic->ic_csa_chan->ic_freq, now_tu);
|
|
}
|
|
|
|
ieee80211_doth_cancel_cs(ic);
|
|
ic->ic_curchan = ic->ic_bsschan = ic->ic_csa_chan;
|
|
ic->ic_set_channel(ic);
|
|
}
|
|
|
|
void
|
|
ieee80211_doth_switch_channel_tmr(unsigned long arg)
|
|
{
|
|
struct ieee80211com *ic = (struct ieee80211com *)arg;
|
|
ieee80211_doth_switch_channel(ic);
|
|
}
|
|
|
|
/* This function is called when we received an action frame or a beacon frame
|
|
* containing a CSA IE. */
|
|
static int
|
|
ieee80211_parse_csaie(struct ieee80211_node *ni, u_int8_t *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 *)frm;
|
|
int subtype;
|
|
|
|
subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
|
|
IEEE80211_DPRINTF(
|
|
vap, IEEE80211_MSG_DOTH,
|
|
"%s: Receiving %s%s%s frame with CSA IE: %u/%u/%u\n",
|
|
__func__,
|
|
subtype == IEEE80211_FC0_SUBTYPE_BEACON ? "beacon" : "",
|
|
subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP ? "probe response" : "",
|
|
subtype == IEEE80211_FC0_SUBTYPE_ACTION ? "action" : "",
|
|
csa_ie->csa_mode,
|
|
csa_ie->csa_chan,
|
|
csa_ie->csa_count);
|
|
|
|
if ((ic->ic_flags & IEEE80211_F_DOTH) == 0) {
|
|
IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOTH,
|
|
"%s: Ignored CSA IE since 802.11h "
|
|
"support is disabled\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (csa_ie->csa_id != IEEE80211_ELEMID_CHANSWITCHANN) {
|
|
IEEE80211_DISCARD_IE(vap,
|
|
IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH,
|
|
wh, "channel switch", "invalid element ID %u",
|
|
csa_ie->csa_id);
|
|
return -1;
|
|
}
|
|
|
|
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 -1;
|
|
}
|
|
|
|
if ((csa_ie->csa_mode != IEEE80211_CSA_CAN_STOP_TX) &&
|
|
(csa_ie->csa_mode != IEEE80211_CSA_MUST_STOP_TX)) {
|
|
IEEE80211_DISCARD_IE(vap,
|
|
IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH,
|
|
wh, "channel switch", "invalid CSA mode %u",
|
|
csa_ie->csa_mode);
|
|
return -1;
|
|
}
|
|
|
|
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 -1;
|
|
}
|
|
|
|
if ((c = ieee80211_doth_findchan(vap, csa_ie->csa_chan)) == NULL) {
|
|
/* XXX something wrong */
|
|
IEEE80211_DISCARD_IE(vap,
|
|
IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH,
|
|
wh, "channel switch",
|
|
"channel %u lookup failed", csa_ie->csa_chan);
|
|
return -1;
|
|
}
|
|
|
|
ieee80211_start_new_csa(vap, csa_ie->csa_mode, c, csa_ie->csa_count,
|
|
subtype == IEEE80211_FC0_SUBTYPE_BEACON);
|
|
|
|
/* This is an extension to 802.11h. When we receive a CSA IE with
|
|
* Mode=1, then we treat it like a "remote" radar detected event. This
|
|
* is needed to effectively stop transmitting */
|
|
if (csa_ie->csa_mode == IEEE80211_CSA_MUST_STOP_TX) {
|
|
ic->ic_radar_detected(ic, "remote radar from CSA IE",
|
|
1, csa_ie->csa_chan);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* XXX. Not the right place for such a definition */
|
|
struct l2_update_frame {
|
|
u8 da[ETH_ALEN]; /* broadcast */
|
|
u8 sa[ETH_ALEN]; /* STA addr */
|
|
__be16 len; /* 6 */
|
|
u8 dsap; /* null DSAP address */
|
|
u8 ssap; /* null SSAP address, CR=Response */
|
|
u8 control;
|
|
u8 xid_info[3];
|
|
} __attribute__ ((packed));
|
|
|
|
|
|
static void
|
|
ieee80211_deliver_l2_rnr(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;
|
|
|
|
skb = ieee80211_dev_alloc_skb(sizeof(*l2uf));
|
|
if (skb == NULL) {
|
|
return;
|
|
}
|
|
skb_put(skb, sizeof(*l2uf));
|
|
l2uf = (struct l2_update_frame *)(skb->data);
|
|
/* dst: Broadcast address */
|
|
memcpy(l2uf->da, dev->broadcast, ETH_ALEN);
|
|
/* src: associated STA */
|
|
memcpy(l2uf->sa, ni->ni_macaddr, ETH_ALEN);
|
|
l2uf->len = htons(6);
|
|
l2uf->dsap = 0;
|
|
l2uf->ssap = 0;
|
|
l2uf->control = 0xf5;
|
|
l2uf->xid_info[0] = 0x81;
|
|
l2uf->xid_info[1] = 0x80;
|
|
l2uf->xid_info[2] = 0x00;
|
|
|
|
skb->dev = dev;
|
|
/* eth_trans_type modifies skb state (skb_pull(ETH_HLEN)), so use
|
|
* constants instead. We know the packet type anyway. */
|
|
skb->pkt_type = PACKET_BROADCAST;
|
|
skb->protocol = htons(ETH_P_802_2);
|
|
skb_reset_mac_header(skb);
|
|
|
|
ieee80211_deliver_data(ni, skb);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
ieee80211_deliver_l2_xid(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;
|
|
|
|
skb = ieee80211_dev_alloc_skb(sizeof(*l2uf));
|
|
if (skb == NULL) {
|
|
return;
|
|
}
|
|
/* Leak check / cleanup destructor */
|
|
skb_put(skb, sizeof(*l2uf));
|
|
l2uf = (struct l2_update_frame *)(skb->data);
|
|
/* dst: Broadcast address */
|
|
memcpy(l2uf->da, dev->broadcast, ETH_ALEN);
|
|
/* src: associated STA */
|
|
memcpy(l2uf->sa, ni->ni_macaddr, ETH_ALEN);
|
|
l2uf->len = htons(6);
|
|
l2uf->dsap = 0x00; /* NULL DSAP address */
|
|
l2uf->ssap = 0x01;/* NULL SSAP address, CR Bit: Response */
|
|
l2uf->control = 0xaf; /* XID response lsb.1111F101.
|
|
* F=0 (no poll command; unsolicited frame) */
|
|
l2uf->xid_info[0] = 0x81; /* XID format identifier */
|
|
l2uf->xid_info[1] = 1; /* LLC types/classes: Type 1 LLC */
|
|
l2uf->xid_info[2] = 1 << 1; /* XID sender's receive window size (RW)
|
|
* FIX: what is correct RW with 802.11? */
|
|
skb->dev = dev;
|
|
/* eth_trans_type modifies skb state (skb_pull(ETH_HLEN)), so use
|
|
* constants instead. We know the packet type anyway. */
|
|
skb->pkt_type = PACKET_BROADCAST;
|
|
skb->protocol = htons(ETH_P_802_2);
|
|
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;
|
|
|
|
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));
|
|
}
|
|
|
|
|
|
/*
|
|
* Context: SoftIRQ
|
|
*/
|
|
int
|
|
ieee80211_recv_mgmt(struct ieee80211vap *vap,
|
|
struct ieee80211_node *ni_or_null, struct sk_buff *skb,
|
|
int subtype, int rssi, u_int64_t rtsf)
|
|
{
|
|
#define ISPROBE(_st) ((_st) == IEEE80211_FC0_SUBTYPE_PROBE_RESP)
|
|
#define ISREASSOC(_st) ((_st) == IEEE80211_FC0_SUBTYPE_REASSOC_RESP)
|
|
struct ieee80211_node * ni = ni_or_null;
|
|
struct ieee80211com *ic = vap->iv_ic;
|
|
struct ieee80211_frame *wh;
|
|
u_int8_t *frm, *efrm;
|
|
u_int8_t *ssid, *rates, *xrates, *suppchan, *wpa, *rsn, *wme, *ath;
|
|
u_int8_t rate;
|
|
int reassoc, resp, allocbs = 0;
|
|
u_int8_t qosinfo;
|
|
|
|
if (ni_or_null == NULL)
|
|
ni = vap->iv_bss;
|
|
|
|
wh = (struct ieee80211_frame *)skb->data;
|
|
frm = (u_int8_t *)&wh[1];
|
|
efrm = skb->data + skb->len;
|
|
|
|
IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC,
|
|
"%s: vap:%p[" MAC_FMT "] ni:%p[" MAC_FMT "]\n",
|
|
__func__, vap, MAC_ADDR(vap->iv_bssid),
|
|
ni_or_null, MAC_ADDR(wh->i_addr2));
|
|
|
|
/* forward management frame to application */
|
|
if (vap->iv_opmode != IEEE80211_M_MONITOR)
|
|
forward_mgmt_to_app(vap, subtype, skb, wh);
|
|
|
|
switch (subtype) {
|
|
case IEEE80211_FC0_SUBTYPE_PROBE_RESP:
|
|
case IEEE80211_FC0_SUBTYPE_BEACON: {
|
|
struct ieee80211_scanparams scan;
|
|
|
|
/*
|
|
* We process beacon/probe response frames:
|
|
* o when scanning, or
|
|
* o station mode when associated (to collect state
|
|
* updates such as 802.11g slot time), or
|
|
* o adhoc mode (to discover neighbors)
|
|
* Frames otherwise received are discarded.
|
|
*/
|
|
if (!((ic->ic_flags & IEEE80211_F_SCAN) ||
|
|
(vap->iv_opmode == IEEE80211_M_STA && ni->ni_associd) ||
|
|
vap->iv_opmode == IEEE80211_M_IBSS)) {
|
|
vap->iv_stats.is_rx_mgtdiscard++;
|
|
return 0;
|
|
}
|
|
/*
|
|
* 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] WME
|
|
* [tlv] WPA or RSN
|
|
* [tlv] Atheros Advanced Capabilities
|
|
*/
|
|
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);
|
|
scan.chan = scan.bchan;
|
|
|
|
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_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_VENDOR:
|
|
if (iswpaoui(frm))
|
|
scan.wpa = frm;
|
|
else if (iswmeparam(frm) || iswmeinfo(frm))
|
|
scan.wme = frm;
|
|
else if (isatherosoui(frm))
|
|
scan.ath = frm;
|
|
break;
|
|
case IEEE80211_ELEMID_CHANSWITCHANN:
|
|
if (ic->ic_flags & IEEE80211_F_DOTH)
|
|
scan.csa = 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 0; /* reached past the end */
|
|
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 0;
|
|
}
|
|
#endif
|
|
if (scan.chan != scan.bchan &&
|
|
ic->ic_phytype != IEEE80211_T_FH) {
|
|
/*
|
|
* 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 %u", scan.chan);
|
|
vap->iv_stats.is_rx_chanmismatch++;
|
|
return 0;
|
|
}
|
|
|
|
/* 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.
|
|
*/
|
|
if (!IEEE80211_BINTVAL_VALID(scan.bintval)) {
|
|
IEEE80211_DISCARD(vap, IEEE80211_MSG_SCAN,
|
|
wh, "beacon", "invalid beacon interval (%u)",
|
|
scan.bintval);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
|
|
/* According to 802.11h 11.6 p47 : if a STA with
|
|
* dot11SpectrumManagementRequired set to TRUE receives a
|
|
* Beacon or Probe Response frames with the Spectrum
|
|
* Management bit set to 1, then we behave the same way as if
|
|
* Channel Availability Check is done */
|
|
|
|
if ((ic->ic_flags & IEEE80211_F_DOTH) &&
|
|
(scan.capinfo & IEEE80211_CAPINFO_SPECTRUM_MGMT)) {
|
|
IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOTH,
|
|
"Received an enabling signal from "
|
|
MAC_FMT "\n", MAC_ADDR(wh->i_addr2));
|
|
ic->ic_set_dfs_clear(ic, 1);
|
|
}
|
|
|
|
/*
|
|
* 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, vap->iv_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 */
|
|
}
|
|
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 (scan.erp & IEEE80211_ERP_USE_PROTECTION)
|
|
ic->ic_flags |= IEEE80211_F_USEPROT;
|
|
else
|
|
ic->ic_flags &= ~IEEE80211_F_USEPROT;
|
|
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);
|
|
}
|
|
} else
|
|
ni->ni_flags &= ~IEEE80211_NODE_UAPSD;
|
|
if (scan.ath != NULL)
|
|
ieee80211_parse_athParams(ni, scan.ath);
|
|
if (scan.csa != NULL)
|
|
ieee80211_parse_csaie(ni, scan.csa, wh);
|
|
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 ((tim->tim_bitctl&1) ||
|
|
(min <= ix && ix <= max &&
|
|
isset(tim->tim_bitmap - min, aid)))
|
|
ieee80211_sta_pwrsave(vap, 0);
|
|
vap->iv_dtim_count = tim->tim_count;
|
|
}
|
|
|
|
/* WDS/Repeater: re-schedule software beacon timer for
|
|
* STA. */
|
|
if ((vap->iv_state == IEEE80211_S_RUN) &&
|
|
(vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS)) {
|
|
mod_timer(&vap->iv_swbmiss,
|
|
jiffies + vap->iv_swbmiss_period);
|
|
}
|
|
|
|
/* 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)
|
|
ieee80211_add_scan(vap, &scan, wh,
|
|
subtype, rssi, rtsf);
|
|
else if (contbgscan(vap) || startbgscan(vap))
|
|
ieee80211_bg_scan(vap);
|
|
return 0;
|
|
}
|
|
|
|
/* ieee80211_parse_csaie() needs to be called in IBSS mode as
|
|
* well. We filter on the IBSSID */
|
|
if ((vap->iv_opmode == IEEE80211_M_IBSS) &&
|
|
(scan.capinfo & IEEE80211_CAPINFO_IBSS) &&
|
|
IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_bssid)) {
|
|
if (scan.csa != NULL)
|
|
ieee80211_parse_csaie(ni,scan.csa,wh);
|
|
}
|
|
|
|
/*
|
|
* If scanning, just pass information to the scan module.
|
|
*/
|
|
if (ic->ic_flags & IEEE80211_F_SCAN) {
|
|
ieee80211_add_scan(vap, &scan, wh, subtype, rssi, rtsf);
|
|
return 0;
|
|
}
|
|
if ((vap->iv_opmode == IEEE80211_M_IBSS) &&
|
|
(scan.capinfo & IEEE80211_CAPINFO_IBSS)) {
|
|
if (ni_or_null == NULL) {
|
|
/* Create a new entry in the neighbor table. */
|
|
ni = ieee80211_add_neighbor(vap, wh, &scan);
|
|
if (ni == NULL)
|
|
return 0;
|
|
} 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.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_rtsf = rtsf;
|
|
ni->ni_last_rx = jiffies;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IEEE80211_FC0_SUBTYPE_PROBE_REQ:
|
|
if (vap->iv_opmode == IEEE80211_M_STA ||
|
|
vap->iv_opmode == IEEE80211_M_AHDEMO ||
|
|
vap->iv_state != IEEE80211_S_RUN) {
|
|
vap->iv_stats.is_rx_mgtdiscard++;
|
|
return 0;
|
|
}
|
|
if (IEEE80211_IS_MULTICAST(wh->i_addr2)) {
|
|
/* frame must be directed */
|
|
vap->iv_stats.is_rx_mgtdiscard++; /* XXX: stat */
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* XR vap does not process probe requests.
|
|
*/
|
|
#ifdef ATH_SUPERG_XR
|
|
if (vap->iv_flags & IEEE80211_F_XR)
|
|
return 0;
|
|
#endif
|
|
/*
|
|
* 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:
|
|
ssid = frm;
|
|
break;
|
|
case IEEE80211_ELEMID_RATES:
|
|
rates = frm;
|
|
break;
|
|
case IEEE80211_ELEMID_XRATES:
|
|
xrates = frm;
|
|
break;
|
|
case IEEE80211_ELEMID_VENDOR:
|
|
if (isatherosoui(frm))
|
|
ath = frm;
|
|
/* XXX Atheros OUI support */
|
|
break;
|
|
}
|
|
frm += frm[1] + 2;
|
|
}
|
|
if (frm > efrm)
|
|
return 0; /* reached past the end */
|
|
IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE);
|
|
IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN);
|
|
IEEE80211_VERIFY_SSID(vap->iv_bss, ssid);
|
|
if ((vap->iv_flags & IEEE80211_F_HIDESSID) && ssid[1] == 0) {
|
|
IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT,
|
|
wh, ieee80211_mgt_subtype_name[subtype >>
|
|
IEEE80211_FC0_SUBTYPE_SHIFT],
|
|
"%s", "no ssid with ssid suppression enabled");
|
|
vap->iv_stats.is_rx_ssidmismatch++; /*XXX*/
|
|
return 0;
|
|
}
|
|
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_dup_bss(vap, wh->i_addr2, 1);
|
|
}
|
|
if (ni == NULL)
|
|
return 0;
|
|
allocbs = 1;
|
|
}
|
|
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, wh->i_addr2,
|
|
"%s", "recv probe req");
|
|
ni->ni_rssi = rssi;
|
|
ni->ni_rtsf = rtsf;
|
|
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 {
|
|
IEEE80211_SEND_MGMT(ni,
|
|
IEEE80211_FC0_SUBTYPE_PROBE_RESP, 0);
|
|
}
|
|
if (allocbs) {
|
|
/*
|
|
* Temporary node created just to send a
|
|
* response, reclaim immediately
|
|
*/
|
|
ieee80211_unref_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));
|
|
#ifdef ATH_SUPERG_XR
|
|
if (!IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_bssid)) {
|
|
/*
|
|
* node roaming between XR and normal vaps.
|
|
* this can only happen in AP mode. disaccociate from
|
|
* previous vap first.
|
|
*/
|
|
if (vap->iv_xrvap) {
|
|
if (ni == vap->iv_bss)
|
|
ni = vap->iv_xrvap->iv_bss;
|
|
else {
|
|
ieee80211_node_leave(ni);
|
|
/* This would be a stupid place to add
|
|
* a node to the table; XR stuff needs
|
|
* work anyway. */
|
|
ieee80211_node_reset(ni, vap->iv_xrvap);
|
|
}
|
|
vap = vap->iv_xrvap;
|
|
} else {
|
|
IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH,
|
|
wh, "auth", "%s", "not to pier xr bssid");
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH, wh->i_addr2,
|
|
"recv auth frame with algorithm %d seq %d", algo, seq);
|
|
/* Consult the ACL policy module if setup. */
|
|
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++;
|
|
return 0;
|
|
}
|
|
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) {
|
|
ieee80211_send_error(ni, wh->i_addr2,
|
|
IEEE80211_FC0_SUBTYPE_AUTH,
|
|
IEEE80211_REASON_MIC_FAILURE);
|
|
}
|
|
return 0;
|
|
}
|
|
if (algo == IEEE80211_AUTH_ALG_SHARED)
|
|
ieee80211_auth_shared(ni, wh, frm + 6, efrm, rssi,
|
|
rtsf, seq, status);
|
|
else if (algo == IEEE80211_AUTH_ALG_OPEN)
|
|
ieee80211_auth_open(ni, wh, rssi, rtsf, seq, status);
|
|
else {
|
|
IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY,
|
|
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 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IEEE80211_FC0_SUBTYPE_ASSOC_REQ:
|
|
case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: {
|
|
u_int16_t capinfo, bintval;
|
|
struct ieee80211_rsnparms rsn_parm;
|
|
u_int8_t reason;
|
|
|
|
if (vap->iv_opmode != IEEE80211_M_HOSTAP ||
|
|
vap->iv_state != IEEE80211_S_RUN) {
|
|
vap->iv_stats.is_rx_mgtdiscard++;
|
|
return 0;
|
|
}
|
|
|
|
if (subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) {
|
|
reassoc = 1;
|
|
resp = IEEE80211_FC0_SUBTYPE_REASSOC_RESP;
|
|
} else {
|
|
reassoc = 0;
|
|
resp = IEEE80211_FC0_SUBTYPE_ASSOC_RESP;
|
|
}
|
|
|
|
/*
|
|
* 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] supported channels
|
|
* [tlv] wpa or RSN
|
|
* [tlv] WME
|
|
* [tlv] Atheros Advanced Capabilities
|
|
*/
|
|
IEEE80211_VERIFY_LENGTH(efrm - frm, (reassoc ? 10 : 4));
|
|
if (!IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_bssid)) {
|
|
IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY,
|
|
wh, ieee80211_mgt_subtype_name[subtype >>
|
|
IEEE80211_FC0_SUBTYPE_SHIFT],
|
|
"%s", "wrong bssid");
|
|
vap->iv_stats.is_rx_assoc_bss++;
|
|
return 0;
|
|
}
|
|
capinfo = le16toh(*(__le16 *)frm);
|
|
frm += 2;
|
|
bintval = le16toh(*(__le16 *)frm);
|
|
frm += 2;
|
|
if (reassoc)
|
|
frm += 6; /* ignore current AP info */
|
|
ssid = rates = xrates = suppchan = wpa = rsn = wme = ath = NULL;
|
|
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;
|
|
/* XXX verify only one of RSN and WPA IEs? */
|
|
case IEEE80211_ELEMID_RSN:
|
|
if (vap->iv_flags & IEEE80211_F_WPA2)
|
|
rsn = frm;
|
|
else
|
|
IEEE80211_DPRINTF(vap,
|
|
IEEE80211_MSG_ASSOC |
|
|
IEEE80211_MSG_WPA,
|
|
"[" MAC_FMT "] ignoring RSN IE "
|
|
"in association request\n",
|
|
MAC_ADDR(wh->i_addr2));
|
|
break;
|
|
case IEEE80211_ELEMID_VENDOR:
|
|
/* NB: Provide all IEs for wpa_supplicant, so
|
|
* it can handle downgrade attacks, etc. */
|
|
if (iswpaoui(frm) && !wpa) {
|
|
if (vap->iv_flags & IEEE80211_F_WPA1)
|
|
wpa = frm;
|
|
else
|
|
IEEE80211_DPRINTF(vap,
|
|
IEEE80211_MSG_ASSOC |
|
|
IEEE80211_MSG_WPA,
|
|
"[" MAC_FMT "] "
|
|
"ignoring WPA IE in "
|
|
"association request\n",
|
|
MAC_ADDR(wh->i_addr2));
|
|
} else if (iswmeinfo(frm))
|
|
wme = frm;
|
|
else if (isatherosoui(frm))
|
|
ath = frm;
|
|
break;
|
|
}
|
|
frm += frm[1] + 2;
|
|
}
|
|
if (frm > efrm)
|
|
return 0; /* reached past the end */
|
|
|
|
IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE);
|
|
IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN);
|
|
IEEE80211_VERIFY_SSID(vap->iv_bss, ssid);
|
|
|
|
if (ni == vap->iv_bss) {
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ANY, 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++;
|
|
return 0;
|
|
}
|
|
|
|
if (ni->ni_needed_chans != NULL) {
|
|
FREE(ni->ni_needed_chans, M_DEVBUF);
|
|
ni->ni_needed_chans = NULL;
|
|
}
|
|
|
|
if (ic->ic_flags & IEEE80211_F_DOTH) {
|
|
u_int8_t status;
|
|
status = ieee80211_parse_sc_ie(ni, suppchan, wh);
|
|
if (status != IEEE80211_STATUS_SUCCESS) {
|
|
/* ieee80211_parse_sc_ie already printed dbg msg */
|
|
IEEE80211_SEND_MGMT(ni, resp, status);
|
|
ieee80211_node_leave(ni); /* XXX */
|
|
vap->iv_stats.is_rx_assoc_badscie++; /* XXX */
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Assert right associstion security credentials */
|
|
/* XXX Divy. Incomplete */
|
|
if (wpa == NULL && (ic->ic_flags & IEEE80211_F_WPA)) {
|
|
IEEE80211_DPRINTF(vap,
|
|
IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA,
|
|
"[" MAC_FMT "] no WPA/RSN IE in association request\n",
|
|
MAC_ADDR(wh->i_addr2));
|
|
IEEE80211_SEND_MGMT(ni,
|
|
IEEE80211_FC0_SUBTYPE_DEAUTH,
|
|
IEEE80211_REASON_RSN_REQUIRED);
|
|
ieee80211_node_leave(ni);
|
|
/* XXX distinguish WPA/RSN? */
|
|
vap->iv_stats.is_rx_assoc_badwpaie++;
|
|
return 0;
|
|
}
|
|
|
|
if (rsn != NULL) {
|
|
/* Initialise values to node defaults, which are then
|
|
* overwritten by values in the IE. These are
|
|
* installed once association is complete. */
|
|
rsn_parm = ni->ni_rsn;
|
|
if (rsn[0] != IEEE80211_ELEMID_RSN)
|
|
reason = ieee80211_parse_wpa(vap, rsn, &rsn_parm, wh);
|
|
else
|
|
reason = ieee80211_parse_rsn(vap, rsn, &rsn_parm, wh);
|
|
if (reason != 0) {
|
|
IEEE80211_SEND_MGMT(ni,
|
|
IEEE80211_FC0_SUBTYPE_DEAUTH, reason);
|
|
ieee80211_node_leave(ni);
|
|
/* XXX distinguish WPA/RSN? */
|
|
vap->iv_stats.is_rx_assoc_badwpaie++;
|
|
return 0;
|
|
}
|
|
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[0] != IEEE80211_ELEMID_RSN ? "WPA" : "RSN",
|
|
rsn_parm.rsn_mcastcipher, rsn_parm.rsn_mcastkeylen,
|
|
rsn_parm.rsn_ucastcipher, rsn_parm.rsn_ucastkeylen,
|
|
rsn_parm.rsn_keymgmt, rsn_parm.rsn_caps);
|
|
}
|
|
/* 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_ANY, 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++;
|
|
return 0;
|
|
}
|
|
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_ANY, 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++;
|
|
return 0;
|
|
}
|
|
|
|
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_ANY,
|
|
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++;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ni->ni_rssi = rssi;
|
|
ni->ni_rtsf = rtsf;
|
|
ni->ni_last_rx = jiffies;
|
|
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;
|
|
|
|
/* WPA */
|
|
ieee80211_saveie(&ni->ni_wpa_ie, wpa);
|
|
/* RSN */
|
|
ni->ni_rsn = rsn_parm;
|
|
ieee80211_saveie(&ni->ni_rsn_ie, rsn);
|
|
/* WME - including QoS flag */
|
|
ieee80211_saveie(&ni->ni_wme_ie, wme);
|
|
ni->ni_flags &= ~IEEE80211_NODE_QOS;
|
|
if ((wme != NULL) && (ieee80211_parse_wmeie(wme, wh, ni) > 0))
|
|
ni->ni_flags |= IEEE80211_NODE_QOS;
|
|
|
|
ieee80211_saveath(ni, ath);
|
|
|
|
/* Send Receiver Not Ready (RNR) followed by XID for newly
|
|
* associated stations. */
|
|
ieee80211_deliver_l2_rnr(ni);
|
|
ieee80211_deliver_l2_xid(ni);
|
|
ieee80211_node_join(ni, resp);
|
|
#ifdef ATH_SUPERG_XR
|
|
if (ni->ni_prev_vap &&
|
|
ni->ni_vap != ni->ni_prev_vap &&
|
|
ni->ni_vap->iv_ath_cap & IEEE80211_ATHC_XR) {
|
|
/*
|
|
* node moved between XR and normal vap.
|
|
* move the data between XR and normal vap.
|
|
*/
|
|
ic->ic_node_move_data(ni);
|
|
ni->ni_prev_vap = ni->ni_vap;
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case IEEE80211_FC0_SUBTYPE_ASSOC_RESP:
|
|
case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: {
|
|
u_int16_t capinfo, associd;
|
|
u_int16_t status;
|
|
|
|
if (vap->iv_opmode != IEEE80211_M_STA ||
|
|
vap->iv_state != IEEE80211_S_ASSOC) {
|
|
vap->iv_stats.is_rx_mgtdiscard++;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 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 0;
|
|
}
|
|
associd = le16toh(*(__le16 *)frm);
|
|
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_XRATES:
|
|
xrates = frm;
|
|
break;
|
|
case IEEE80211_ELEMID_VENDOR:
|
|
if (iswmeoui(frm))
|
|
wme = frm;
|
|
break;
|
|
}
|
|
frm += frm[1] + 2;
|
|
}
|
|
if (frm > efrm)
|
|
return 0; /* reached past the end */
|
|
IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE);
|
|
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_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 0;
|
|
}
|
|
|
|
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);
|
|
} 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_IS_CHAN_ANYG(ic->ic_curchan) &&
|
|
(ni->ni_erp & IEEE80211_ERP_USE_PROTECTION))
|
|
ic->ic_flags |= IEEE80211_F_USEPROT;
|
|
else
|
|
ic->ic_flags &= ~IEEE80211_F_USEPROT;
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2,
|
|
"%sassoc success: %s preamble, %s slot time%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",
|
|
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" : ""
|
|
);
|
|
ieee80211_new_state(vap, IEEE80211_S_RUN, subtype);
|
|
break;
|
|
}
|
|
|
|
case IEEE80211_FC0_SUBTYPE_DEAUTH: {
|
|
u_int16_t reason;
|
|
|
|
if (vap->iv_state == IEEE80211_S_SCAN) {
|
|
vap->iv_stats.is_rx_mgtdiscard++;
|
|
return 0;
|
|
}
|
|
/*
|
|
* 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)", reason);
|
|
switch (vap->iv_opmode) {
|
|
case IEEE80211_M_STA:
|
|
ieee80211_new_state(vap, IEEE80211_S_AUTH,
|
|
wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK);
|
|
break;
|
|
case IEEE80211_M_HOSTAP:
|
|
if (ni != vap->iv_bss)
|
|
ieee80211_node_leave(ni);
|
|
break;
|
|
default:
|
|
vap->iv_stats.is_rx_mgtdiscard++;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IEEE80211_FC0_SUBTYPE_DISASSOC: {
|
|
u_int16_t reason;
|
|
|
|
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 0;
|
|
}
|
|
/*
|
|
* 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);
|
|
|
|
IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni,
|
|
"recv disassociate (reason %d)", reason);
|
|
switch (vap->iv_opmode) {
|
|
case IEEE80211_M_STA:
|
|
ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0);
|
|
break;
|
|
case IEEE80211_M_HOSTAP:
|
|
if (ni != vap->iv_bss)
|
|
ieee80211_node_leave(ni);
|
|
break;
|
|
default:
|
|
vap->iv_stats.is_rx_mgtdiscard++;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IEEE80211_FC0_SUBTYPE_ACTION: {
|
|
unsigned char cat, act;
|
|
/* we only parse Action frame from our BSSID */
|
|
if (!IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_bssid)) {
|
|
IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT,
|
|
wh->i_addr3, NULL,
|
|
"%s", "not to bss");
|
|
vap->iv_stats.is_rx_wrongbss ++;
|
|
return 0;
|
|
}
|
|
|
|
/* parse the Category field */
|
|
switch (cat = *frm++) {
|
|
case IEEE80211_ACTION_SPECTRUM_MANAGEMENT:
|
|
switch (act = *frm++) {
|
|
case IEEE80211_ACTION_S_CHANSWITCHANN:
|
|
ieee80211_parse_csaie(ni, frm, wh);
|
|
break;
|
|
default:
|
|
IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh,
|
|
"mgt", "action frame category 0x%x, "
|
|
"action 0x%x not handled", cat, act);
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "mgt",
|
|
"action frame category 0x%x not handled", cat);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "mgt",
|
|
"subtype 0x%x not handled", subtype);
|
|
vap->iv_stats.is_rx_badsubtype++;
|
|
break;
|
|
}
|
|
#undef ISREASSOC
|
|
#undef ISPROBE
|
|
return 1;
|
|
}
|
|
#undef IEEE80211_VERIFY_LENGTH
|
|
#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(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_IRQ(ni);
|
|
qlen = IEEE80211_NODE_SAVEQ_DEQUEUE(ni, skb);
|
|
IEEE80211_NODE_SAVEQ_UNLOCK_IRQ(ni);
|
|
if (skb == NULL) {
|
|
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_POWER, wh->i_addr2,
|
|
"%s", "recv ps-poll, but queue empty");
|
|
ieee80211_send_nulldata(ieee80211_ref_node(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 ATH_SUPERG_FF
|
|
static int
|
|
athff_decap(struct sk_buff *skb)
|
|
{
|
|
struct ether_header eh_src, *eh_dst;
|
|
struct llc *llc;
|
|
|
|
if (skb->len < (sizeof(struct ether_header) + LLC_SNAPFRAMELEN))
|
|
return -1;
|
|
|
|
memcpy(&eh_src, skb->data, sizeof(struct ether_header));
|
|
llc = (struct llc *)skb_pull(skb, sizeof(struct ether_header));
|
|
eh_src.ether_type = llc->llc_un.type_snap.ether_type;
|
|
skb_pull(skb, LLC_SNAPFRAMELEN);
|
|
|
|
eh_dst = (struct ether_header *)skb_push(skb, sizeof(struct ether_header));
|
|
memcpy(eh_dst, &eh_src, sizeof(struct ether_header));
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#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);
|
|
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
|
|
|
|
/* Re-process a frame w/ HW detected MIC failure, as it may be a false
|
|
* negative. The frame will be dropped in any case. */
|
|
void
|
|
ieee80211_check_mic(struct ieee80211_node *ni, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211vap *vap = ni->ni_vap;
|
|
struct ieee80211_frame *wh;
|
|
struct ieee80211_key *key;
|
|
int hdrlen;
|
|
|
|
if (skb->len < sizeof(struct ieee80211_frame_min)) {
|
|
IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY,
|
|
ni->ni_macaddr, NULL,
|
|
"too short (1): len %u", skb->len);
|
|
vap->iv_stats.is_rx_tooshort++;
|
|
return;
|
|
}
|
|
|
|
wh = (struct ieee80211_frame *)skb->data;
|
|
hdrlen = ieee80211_hdrsize(wh);
|
|
|
|
key = ieee80211_crypto_decap(ni, skb, hdrlen);
|
|
if (key == NULL) {
|
|
/* NB: stats+msgs handled in crypto_decap */
|
|
IEEE80211_NODE_STAT(ni, rx_wepfail);
|
|
} else if (!ieee80211_crypto_demic(vap, key, skb, hdrlen, 1)) {
|
|
IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT,
|
|
ni->ni_macaddr, "data", "%s", "demic error");
|
|
IEEE80211_NODE_STAT(ni, rx_demicfail);
|
|
} else
|
|
IEEE80211_NODE_STAT(ni, rx_hwdemicerr);
|
|
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_check_mic);
|
|
|
|
#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;
|
|
}
|
|
|
|
void
|
|
ieee80211_note(struct ieee80211vap *vap, const char *fmt, ...)
|
|
{
|
|
char buf[BUF_LEN];
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
printk(KERN_DEBUG "%s/%s[" MAC_FMT "]: %s",
|
|
vap->iv_ic->ic_dev->name, vap->iv_dev->name,
|
|
MAC_ADDR(vap->iv_myaddr),
|
|
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[BUF_LEN];
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
printk(KERN_DEBUG "%s/%s[" MAC_FMT "]: " MAC_FMT " %s\n",
|
|
vap->iv_ic->ic_dev->name, vap->iv_dev->name,
|
|
MAC_ADDR(vap->iv_myaddr),
|
|
MAC_ADDR(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[BUF_LEN];
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
printk(KERN_DEBUG "%s/%s[" MAC_FMT "]: " MAC_FMT " %s\n",
|
|
vap->iv_ic->ic_dev->name, vap->iv_dev->name,
|
|
MAC_ADDR(vap->iv_myaddr),
|
|
MAC_ADDR(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[BUF_LEN];
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
printk(KERN_DEBUG "%s/%s[" MAC_FMT "]: " MAC_FMT " discard %s%sframe, %s\n",
|
|
vap->iv_ic->ic_dev->name, vap->iv_dev->name,
|
|
MAC_ADDR(vap->iv_myaddr),
|
|
MAC_ADDR(wh->i_addr2),
|
|
(type != NULL) ? type : "",
|
|
(type != NULL) ? " " : "",
|
|
buf);
|
|
}
|
|
|
|
static void
|
|
ieee80211_discard_ie(struct ieee80211vap *vap, const struct ieee80211_frame *wh,
|
|
const char *type, const char *fmt, ...)
|
|
{
|
|
char buf[BUF_LEN];
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
printk(KERN_DEBUG "%s/%s[" MAC_FMT "]: "
|
|
MAC_FMT " discard %s%sinformation element, %s\n",
|
|
vap->iv_ic->ic_dev->name, vap->iv_dev->name,
|
|
MAC_ADDR(vap->iv_myaddr),
|
|
MAC_ADDR(ieee80211_getbssid(vap, wh)),
|
|
(type != NULL) ? type : "",
|
|
(type != NULL) ? " " : "",
|
|
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[BUF_LEN];
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
printk(KERN_DEBUG "%s/%s[" MAC_FMT "]: " MAC_FMT " discard %s%sframe, %s\n",
|
|
vap->iv_ic->ic_dev->name,
|
|
vap->iv_dev->name,
|
|
MAC_ADDR(vap->iv_myaddr),
|
|
MAC_ADDR(mac),
|
|
(type != NULL) ? type : "",
|
|
(type != NULL) ? " " : "",
|
|
buf);
|
|
}
|
|
#endif /* IEEE80211_DEBUG */
|