diff options
author | sam <sam@FreeBSD.org> | 2008-04-20 20:35:46 +0000 |
---|---|---|
committer | sam <sam@FreeBSD.org> | 2008-04-20 20:35:46 +0000 |
commit | 3569e353ca63336d80ab0143dd9669b0b9e6b123 (patch) | |
tree | bc7985c57e7ecfa1ac03e48c406a25430dba634b /sys/net80211/ieee80211_wds.c | |
parent | 682b4ae9be70192e298129ada878af3486683aaf (diff) | |
download | FreeBSD-src-3569e353ca63336d80ab0143dd9669b0b9e6b123.zip FreeBSD-src-3569e353ca63336d80ab0143dd9669b0b9e6b123.tar.gz |
Multi-bss (aka vap) support for 802.11 devices.
Note this includes changes to all drivers and moves some device firmware
loading to use firmware(9) and a separate module (e.g. ral). Also there
no longer are separate wlan_scan* modules; this functionality is now
bundled into the wlan module.
Supported by: Hobnob and Marvell
Reviewed by: many
Obtained from: Atheros (some bits)
Diffstat (limited to 'sys/net80211/ieee80211_wds.c')
-rw-r--r-- | sys/net80211/ieee80211_wds.c | 865 |
1 files changed, 865 insertions, 0 deletions
diff --git a/sys/net80211/ieee80211_wds.c b/sys/net80211/ieee80211_wds.c new file mode 100644 index 0000000..f3d90f9 --- /dev/null +++ b/sys/net80211/ieee80211_wds.c @@ -0,0 +1,865 @@ +/*- + * Copyright (c) 2007-2008 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. + * + * 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. + */ + +#include <sys/cdefs.h> +#ifdef __FreeBSD__ +__FBSDID("$FreeBSD$"); +#endif + +/* + * IEEE 802.11 WDS mode support. + */ +#include "opt_inet.h" +#include "opt_wlan.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/kernel.h> + +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/endian.h> +#include <sys/errno.h> +#include <sys/proc.h> +#include <sys/sysctl.h> + +#include <net/if.h> +#include <net/if_media.h> +#include <net/if_llc.h> +#include <net/ethernet.h> + +#include <net/bpf.h> + +#include <net80211/ieee80211_var.h> +#include <net80211/ieee80211_wds.h> +#include <net80211/ieee80211_input.h> + +static void wds_vattach(struct ieee80211vap *); +static int wds_newstate(struct ieee80211vap *, enum ieee80211_state, int); +static int wds_input(struct ieee80211_node *ni, struct mbuf *m, + int rssi, int noise, uint32_t rstamp); +static void wds_recv_mgmt(struct ieee80211_node *, struct mbuf *, + int subtype, int rssi, int noise, u_int32_t rstamp); + +void +ieee80211_wds_attach(struct ieee80211com *ic) +{ + ic->ic_vattach[IEEE80211_M_WDS] = wds_vattach; +} + +void +ieee80211_wds_detach(struct ieee80211com *ic) +{ +} + +static void +wds_vdetach(struct ieee80211vap *vap) +{ + if (vap->iv_bss != NULL) { + /* XXX locking? */ + if (vap->iv_bss->ni_wdsvap == vap) + vap->iv_bss->ni_wdsvap = NULL; + } +} + +static void +wds_vattach(struct ieee80211vap *vap) +{ + vap->iv_newstate = wds_newstate; + vap->iv_input = wds_input; + vap->iv_recv_mgmt = wds_recv_mgmt; + vap->iv_opdetach = wds_vdetach; +} + +static int +ieee80211_create_wds(struct ieee80211vap *vap, struct ieee80211_channel *chan) +{ + struct ieee80211com *ic = vap->iv_ic; + struct ieee80211_node_table *nt = &ic->ic_sta; + struct ieee80211_node *ni, *obss; + + IEEE80211_DPRINTF(vap, IEEE80211_MSG_WDS, + "%s: creating link to %s on channel %u\n", __func__, + ether_sprintf(vap->iv_des_bssid), ieee80211_chan2ieee(ic, chan)); + + /* NB: vap create must specify the bssid for the link */ + KASSERT(vap->iv_flags & IEEE80211_F_DESBSSID, ("no bssid")); + /* NB: we should only be called on RUN transition */ + KASSERT(vap->iv_state == IEEE80211_S_RUN, ("!RUN state")); + + if ((vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY) == 0) { + /* + * Dynamic/non-legacy WDS. Reference the associated + * station specified by the desired bssid setup at vap + * create. Point ni_wdsvap at the WDS vap so 4-address + * frames received through the associated AP vap will + * be dispatched upward (e.g. to a bridge) as though + * they arrived on the WDS vap. + */ + IEEE80211_NODE_LOCK(nt); + obss = NULL; + ni = ieee80211_find_node_locked(&ic->ic_sta, vap->iv_des_bssid); + if (ni == NULL) { + /* + * Node went away before we could hookup. This + * should be ok; no traffic will flow and a leave + * event will be dispatched that should cause + * the vap to be destroyed. + */ + IEEE80211_DPRINTF(vap, IEEE80211_MSG_WDS, + "%s: station %s went away\n", + __func__, ether_sprintf(vap->iv_des_bssid)); + /* XXX stat? */ + } else if (ni->ni_wdsvap != NULL) { + /* + * Node already setup with a WDS vap; we cannot + * allow multiple references so disallow. If + * ni_wdsvap points at us that's ok; we should + * do nothing anyway. + */ + /* XXX printf instead? */ + IEEE80211_DPRINTF(vap, IEEE80211_MSG_WDS, + "%s: station %s in use with %s\n", + __func__, ether_sprintf(vap->iv_des_bssid), + ni->ni_wdsvap->iv_ifp->if_xname); + /* XXX stat? */ + } else { + /* + * Committed to new node, setup state. + */ + obss = vap->iv_bss; + vap->iv_bss = ni; + ni->ni_wdsvap = vap; + } + IEEE80211_NODE_UNLOCK(nt); + if (obss != NULL) { + /* NB: deferred to avoid recursive lock */ + ieee80211_free_node(obss); + } + } else { + /* + * Legacy WDS vap setup. + */ + /* + * The far end does not associate so we just create + * create a new node and install it as the vap's + * bss node. We must simulate an association and + * authorize the port for traffic to flow. + * XXX check if node already in sta table? + */ + ni = ieee80211_node_create_wds(vap, vap->iv_des_bssid, chan); + if (ni != NULL) { + obss = vap->iv_bss; + vap->iv_bss = ieee80211_ref_node(ni); + ni->ni_flags |= IEEE80211_NODE_AREF; + if (obss != NULL) + ieee80211_free_node(obss); + /* give driver a chance to setup state like ni_txrate */ + if (ic->ic_newassoc != NULL) + ic->ic_newassoc(ni, 1); + /* tell the authenticator about new station */ + if (vap->iv_auth->ia_node_join != NULL) + vap->iv_auth->ia_node_join(ni); + if (ni->ni_authmode != IEEE80211_AUTH_8021X) + ieee80211_node_authorize(ni); + + ieee80211_notify_node_join(ni, 1 /*newassoc*/); + /* XXX inject l2uf frame */ + } + } + + /* + * Flush pending frames now that were setup. + */ + if (ni != NULL && IEEE80211_NODE_WDSQ_QLEN(ni) != 0) { + int8_t rssi, noise; + + IEEE80211_NOTE(vap, IEEE80211_MSG_WDS, ni, + "flush wds queue, %u packets queued", + IEEE80211_NODE_WDSQ_QLEN(ni)); + ic->ic_node_getsignal(ni, &rssi, &noise); + for (;;) { + struct mbuf *m; + + IEEE80211_NODE_WDSQ_LOCK(ni); + _IEEE80211_NODE_WDSQ_DEQUEUE_HEAD(ni, m); + IEEE80211_NODE_WDSQ_UNLOCK(ni); + if (m == NULL) + break; + /* XXX cheat and re-use last rstamp */ + ieee80211_input(ni, m, rssi, noise, ni->ni_rstamp); + } + } + return (ni == NULL ? ENOENT : 0); +} + +/* + * Propagate multicast frames of an ap vap to all DWDS links. + * The caller is assumed to have verified this frame is multicast. + */ +void +ieee80211_dwds_mcast(struct ieee80211vap *vap0, struct mbuf *m) +{ + struct ieee80211com *ic = vap0->iv_ic; + struct ifnet *parent = ic->ic_ifp; + const struct ether_header *eh = mtod(m, const struct ether_header *); + struct ieee80211_node *ni; + struct ieee80211vap *vap; + struct ifnet *ifp; + struct mbuf *mcopy; + int err; + + KASSERT(ETHER_IS_MULTICAST(eh->ether_dhost), + ("%s not mcast", ether_sprintf(eh->ether_dhost))); + + /* XXX locking */ + TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { + /* only DWDS vaps are interesting */ + if (vap->iv_opmode != IEEE80211_M_WDS || + (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY)) + continue; + /* if it came in this interface, don't send it back out */ + ifp = vap->iv_ifp; + if (ifp == m->m_pkthdr.rcvif) + continue; + /* + * Duplicate the frame and send it. We don't need + * to classify or lookup the tx node; this was already + * done by the caller so we can just re-use the info. + */ + mcopy = m_copypacket(m, M_DONTWAIT); + if (mcopy == NULL) { + ifp->if_oerrors++; + /* XXX stat + msg */ + continue; + } + ni = ieee80211_find_txnode(vap, eh->ether_dhost); + if (ni == NULL) { + /* NB: ieee80211_find_txnode does stat+msg */ + ifp->if_oerrors++; + m_freem(mcopy); + continue; + } + if (ieee80211_classify(ni, mcopy)) { + IEEE80211_DISCARD_MAC(vap, + IEEE80211_MSG_OUTPUT | IEEE80211_MSG_WDS, + eh->ether_dhost, NULL, + "%s", "classification failure"); + vap->iv_stats.is_tx_classify++; + ifp->if_oerrors++; + m_freem(mcopy); + ieee80211_free_node(ni); + continue; + } + mcopy->m_flags |= M_MCAST | M_WDS; + mcopy->m_pkthdr.rcvif = (void *) ni; + + IFQ_HANDOFF(parent, mcopy, err); + if (err) { + /* NB: IFQ_HANDOFF reclaims mbuf */ + ifp->if_oerrors++; + ieee80211_free_node(ni); + } else + ifp->if_opackets++; + } +} + +/* + * Handle DWDS discovery on receipt of a 4-address frame in + * ap mode. Queue the frame and post an event for someone + * to plumb the necessary WDS vap for this station. Frames + * received prior to the vap set running will then be reprocessed + * as if they were just received. + */ +void +ieee80211_dwds_discover(struct ieee80211_node *ni, struct mbuf *m) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + int qlen, age; + + IEEE80211_NODE_WDSQ_LOCK(ni); + if (!_IF_QFULL(&ni->ni_wdsq)) { + /* + * Tag the frame with it's expiry time and insert + * it in the queue. The aging interval is 4 times + * the listen interval specified by the station. + * Frames that sit around too long are reclaimed + * using this information. + */ + /* XXX handle overflow? */ + /* XXX per/vap beacon interval? */ + /* NB: TU -> secs */ + age = ((ni->ni_intval * ic->ic_lintval) << 2) / 1024; + _IEEE80211_NODE_WDSQ_ENQUEUE(ni, m, qlen, age); + IEEE80211_NODE_WDSQ_UNLOCK(ni); + + IEEE80211_NOTE(vap, IEEE80211_MSG_WDS, ni, + "save frame, %u now queued", qlen); + } else { + vap->iv_stats.is_dwds_qdrop++; + _IF_DROP(&ni->ni_wdsq); + IEEE80211_NODE_WDSQ_UNLOCK(ni); + + IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_WDS, + mtod(m, struct ieee80211_frame *), "wds data", + "pending q overflow, drops %d (len %d)", + ni->ni_wdsq.ifq_drops, ni->ni_wdsq.ifq_len); + +#ifdef IEEE80211_DEBUG + if (ieee80211_msg_dumppkts(vap)) + ieee80211_dump_pkt(ic, mtod(m, caddr_t), + m->m_len, -1, -1); +#endif + /* XXX tail drop? */ + m_freem(m); + } + ieee80211_notify_wds_discover(ni); +} + +/* + * Age frames on the WDS pending queue. The aging interval is + * 4 times the listen interval specified by the station. This + * number is factored into the age calculations when the frame + * is placed on the queue. We store ages as time differences + * so we can check and/or adjust only the head of the list. + * If a frame's age exceeds the threshold then discard it. + * The number of frames discarded is returned to the caller. + */ +int +ieee80211_node_wdsq_age(struct ieee80211_node *ni) +{ +#ifdef IEEE80211_DEBUG + struct ieee80211vap *vap = ni->ni_vap; +#endif + struct mbuf *m; + int discard = 0; + + IEEE80211_NODE_WDSQ_LOCK(ni); + while (_IF_POLL(&ni->ni_wdsq, m) != NULL && + M_AGE_GET(m) < IEEE80211_INACT_WAIT) { + IEEE80211_NOTE(vap, IEEE80211_MSG_WDS, ni, + "discard frame, age %u", M_AGE_GET(m)); + + /* XXX could be optimized */ + _IEEE80211_NODE_WDSQ_DEQUEUE_HEAD(ni, m); + m_freem(m); + discard++; + } + if (m != NULL) + M_AGE_SUB(m, IEEE80211_INACT_WAIT); + IEEE80211_NODE_WDSQ_UNLOCK(ni); + + IEEE80211_NOTE(vap, IEEE80211_MSG_WDS, ni, + "discard %u frames for age", discard); +#if 0 + IEEE80211_NODE_STAT_ADD(ni, wds_discard, discard); +#endif + return discard; +} + +/* + * IEEE80211_M_WDS vap state machine handler. + */ +static int +wds_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct ieee80211com *ic = vap->iv_ic; + struct ieee80211_node *ni; + enum ieee80211_state ostate; + int error; + + IEEE80211_LOCK_ASSERT(ic); + + ostate = vap->iv_state; + IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s\n", __func__, + ieee80211_state_name[ostate], ieee80211_state_name[nstate]); + vap->iv_state = nstate; /* state transition */ + callout_stop(&vap->iv_mgtsend); /* XXX callout_drain */ + if (ostate != IEEE80211_S_SCAN) + ieee80211_cancel_scan(vap); /* background scan */ + ni = vap->iv_bss; /* NB: no reference held */ + if (vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS) + callout_stop(&vap->iv_swbmiss); + error = 0; + switch (nstate) { + case IEEE80211_S_INIT: + switch (ostate) { + case IEEE80211_S_SCAN: + ieee80211_cancel_scan(vap); + break; + default: + break; + } + if (ostate != IEEE80211_S_INIT) { + /* NB: optimize INIT -> INIT case */ + ieee80211_reset_bss(vap); + } + break; + case IEEE80211_S_SCAN: + switch (ostate) { + case IEEE80211_S_INIT: + ieee80211_check_scan_current(vap); + break; + default: + break; + } + break; + case IEEE80211_S_RUN: + if (ostate == IEEE80211_S_INIT) { + /* + * Already have a channel; bypass the scan + * and startup immediately. + */ + error = ieee80211_create_wds(vap, ic->ic_curchan); + } + break; + default: + break; + } + return error; +} + +/* + * 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 iv_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. + */ +static int +wds_input(struct ieee80211_node *ni, struct mbuf *m, + int rssi, int noise, uint32_t rstamp) +{ +#define SEQ_LEQ(a,b) ((int)((a)-(b)) <= 0) +#define HAS_SEQ(type) ((type & 0x4) == 0) + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = vap->iv_ifp; + struct ieee80211_frame *wh; + struct ieee80211_key *key; + struct ether_header *eh; + int hdrspace, need_tap; + uint8_t dir, type, subtype, qos; + uint16_t rxseq; + + if (m->m_flags & M_AMPDU) { + /* + * Fastpath for A-MPDU reorder q resubmission. Frames + * w/ M_AMPDU marked have already passed through here + * but were received out of order and been held on the + * reorder queue. When resubmitted they are marked + * with the M_AMPDU flag and we can bypass most of the + * normal processing. + */ + wh = mtod(m, struct ieee80211_frame *); + type = IEEE80211_FC0_TYPE_DATA; + dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; + subtype = IEEE80211_FC0_SUBTYPE_QOS; + hdrspace = ieee80211_hdrspace(ic, wh); /* XXX optimize? */ + goto resubmit_ampdu; + } + + KASSERT(ni != NULL, ("null node")); + + need_tap = 1; /* mbuf need to be tapped. */ + type = -1; /* undefined */ + + if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_min)) { + IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, + ni->ni_macaddr, NULL, + "too short (1): len %u", m->m_pkthdr.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 = mtod(m, struct ieee80211_frame *); + + 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; + + /* NB: WDS vap's do not scan */ + if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_addr4)) { + IEEE80211_DISCARD_MAC(vap, + IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, + "too short (3): len %u", m->m_pkthdr.len); + vap->iv_stats.is_rx_tooshort++; + goto out; + } + /* NB: the TA is implicitly verified by finding the wds peer node */ + if (!IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr) && + !IEEE80211_ADDR_EQ(wh->i_addr1, ifp->if_broadcastaddr)) { + /* not interested in */ + IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, + wh->i_addr1, NULL, "%s", "not to bss"); + vap->iv_stats.is_rx_wrongbss++; + goto out; + } + IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); + ni->ni_noise = noise; + ni->ni_rstamp = rstamp; + if (HAS_SEQ(type)) { + uint8_t tid = ieee80211_gettid(wh); + if (IEEE80211_QOS_HAS_SEQ(wh) && + TID_TO_WME_AC(tid) >= WME_AC_VI) + ic->ic_wme.wme_hipri_traffic++; + rxseq = le16toh(*(uint16_t *)wh->i_seq); + if ((ni->ni_flags & IEEE80211_NODE_HT) == 0 && + (wh->i_fc[1] & IEEE80211_FC1_RETRY) && + SEQ_LEQ(rxseq, ni->ni_rxseqs[tid])) { + /* duplicate, discard */ + IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, + wh->i_addr1, "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: + hdrspace = ieee80211_hdrspace(ic, wh); + if (m->m_len < hdrspace && + (m = m_pullup(m, hdrspace)) == NULL) { + IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, + ni->ni_macaddr, NULL, + "data too short: expecting %u", hdrspace); + vap->iv_stats.is_rx_tooshort++; + goto out; /* XXX */ + } + if (dir != IEEE80211_FC1_DIR_DSTODS) { + IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, + wh, "data", "incorrect dir 0x%x", dir); + vap->iv_stats.is_rx_wrongdir++; + goto out; + } + /* + * Only legacy WDS traffic should take this path. + */ + if ((vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY) == 0) { + IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, + wh, "data", "%s", "not legacy wds"); + vap->iv_stats.is_rx_wrongdir++;/*XXX*/ + goto out; + } + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) + ni->ni_inact = ni->ni_inact_reload; + /* + * Handle A-MPDU re-ordering. The station must be + * associated and negotiated HT. The frame must be + * a QoS frame (not QoS null data) and not previously + * processed for A-MPDU re-ordering. If the frame is + * to be processed directly then ieee80211_ampdu_reorder + * will return 0; otherwise it has consumed the mbuf + * and we should do nothing more with it. + */ + if ((ni->ni_flags & IEEE80211_NODE_HT) && + subtype == IEEE80211_FC0_SUBTYPE_QOS && + ieee80211_ampdu_reorder(ni, m) != 0) { + m = NULL; + goto out; + } + resubmit_ampdu: + + /* + * 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_WEP) { + 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, m, hdrspace); + if (key == NULL) { + /* NB: stats+msgs handled in crypto_decap */ + IEEE80211_NODE_STAT(ni, rx_wepfail); + goto out; + } + wh = mtod(m, struct ieee80211_frame *); + wh->i_fc[1] &= ~IEEE80211_FC1_WEP; + } else { + /* XXX M_WEP and IEEE80211_F_PRIVACY */ + key = NULL; + } + + /* + * Save QoS bits for use below--before we strip the header. + */ + if (subtype == IEEE80211_FC0_SUBTYPE_QOS) { + qos = (dir == IEEE80211_FC1_DIR_DSTODS) ? + ((struct ieee80211_qosframe_addr4 *)wh)->i_qos[0] : + ((struct ieee80211_qosframe *)wh)->i_qos[0]; + } else + qos = 0; + + /* + * Next up, any fragmentation. + */ + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + m = ieee80211_defrag(ni, m, hdrspace); + if (m == 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, m, 0)) { + IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, + ni->ni_macaddr, "data", "%s", "demic error"); + vap->iv_stats.is_rx_demicfail++; + IEEE80211_NODE_STAT(ni, rx_demicfail); + goto out; + } + + /* copy to listener after decrypt */ + if (bpf_peers_present(vap->iv_rawbpf)) + bpf_mtap(vap->iv_rawbpf, m); + need_tap = 0; + + /* + * Finally, strip the 802.11 header. + */ + m = ieee80211_decap(vap, m, hdrspace); + if (m == NULL) { + /* XXX mask bit to check for both */ + /* don't count Null data frames as errors */ + if (subtype == IEEE80211_FC0_SUBTYPE_NODATA || + subtype == IEEE80211_FC0_SUBTYPE_QOS_NULL) + goto out; + IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, + ni->ni_macaddr, "data", "%s", "decap error"); + vap->iv_stats.is_rx_decap++; + IEEE80211_NODE_STAT(ni, rx_decap); + goto err; + } + eh = mtod(m, struct ether_header *); + 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. + */ + if (eh->ether_type != htons(ETHERTYPE_PAE)) { + IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, + eh->ether_shost, "data", + "unauthorized port: ether type 0x%x len %u", + eh->ether_type, m->m_pkthdr.len); + vap->iv_stats.is_rx_unauth++; + IEEE80211_NODE_STAT(ni, rx_unauth); + goto err; + } + } else { + /* + * When denying unencrypted frames, discard + * any non-PAE frames received without encryption. + */ + if ((vap->iv_flags & IEEE80211_F_DROPUNENC) && + (key == NULL && (m->m_flags & M_WEP) == 0) && + eh->ether_type != htons(ETHERTYPE_PAE)) { + /* + * Drop unencrypted frames. + */ + vap->iv_stats.is_rx_unencrypted++; + IEEE80211_NODE_STAT(ni, rx_unencrypted); + goto out; + } + } + /* XXX require HT? */ + if (qos & IEEE80211_QOS_AMSDU) { + m = ieee80211_decap_amsdu(ni, m); + if (m == NULL) + return IEEE80211_FC0_TYPE_DATA; + } else if ((ni->ni_ath_flags & IEEE80211_NODE_FF) && +#define FF_LLC_SIZE (sizeof(struct ether_header) + sizeof(struct llc)) + m->m_pkthdr.len >= 3*FF_LLC_SIZE) { + struct llc *llc; + + /* + * Check for fast-frame tunnel encapsulation. + */ + if (m->m_len < FF_LLC_SIZE && + (m = m_pullup(m, FF_LLC_SIZE)) == NULL) { + IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, + ni->ni_macaddr, "fast-frame", + "%s", "m_pullup(llc) failed"); + vap->iv_stats.is_rx_tooshort++; + return IEEE80211_FC0_TYPE_DATA; + } + llc = (struct llc *)(mtod(m, uint8_t *) + + sizeof(struct ether_header)); + if (llc->llc_snap.ether_type == htons(ATH_FF_ETH_TYPE)) { + m_adj(m, FF_LLC_SIZE); + m = ieee80211_decap_fastframe(ni, m); + if (m == NULL) + return IEEE80211_FC0_TYPE_DATA; + } + } +#undef FF_LLC_SIZE + ieee80211_deliver_data(vap, ni, m); + return IEEE80211_FC0_TYPE_DATA; + + case IEEE80211_FC0_TYPE_MGT: + vap->iv_stats.is_rx_mgmt++; + IEEE80211_NODE_STAT(ni, rx_mgmt); + if (dir != IEEE80211_FC1_DIR_NODS) { + IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, + wh, "data", "incorrect dir 0x%x", dir); + vap->iv_stats.is_rx_wrongdir++; + goto err; + } + if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { + IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, + ni->ni_macaddr, "mgt", "too short: len %u", + m->m_pkthdr.len); + vap->iv_stats.is_rx_tooshort++; + goto out; + } +#ifdef IEEE80211_DEBUG + if (ieee80211_msg_debug(vap) || ieee80211_msg_dumppkts(vap)) { + if_printf(ifp, "received %s from %s rssi %d\n", + ieee80211_mgt_subtype_name[subtype >> + IEEE80211_FC0_SUBTYPE_SHIFT], + ether_sprintf(wh->i_addr2), rssi); + } +#endif + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, + wh, NULL, "%s", "WEP set but not permitted"); + vap->iv_stats.is_rx_mgtdiscard++; /* XXX */ + goto out; + } + if (bpf_peers_present(vap->iv_rawbpf)) + bpf_mtap(vap->iv_rawbpf, m); + vap->iv_recv_mgmt(ni, m, subtype, rssi, noise, rstamp); + m_freem(m); + return IEEE80211_FC0_TYPE_MGT; + + case IEEE80211_FC0_TYPE_CTL: + vap->iv_stats.is_rx_ctl++; + IEEE80211_NODE_STAT(ni, rx_ctrl); + goto out; + default: + IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, + wh, "bad", "frame type 0x%x", type); + /* should not come here */ + break; + } +err: + ifp->if_ierrors++; +out: + if (m != NULL) { + if (bpf_peers_present(vap->iv_rawbpf) && need_tap) + bpf_mtap(vap->iv_rawbpf, m); + m_freem(m); + } + return type; +#undef SEQ_LEQ +} + +static void +wds_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, + int subtype, int rssi, int noise, u_int32_t rstamp) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + struct ieee80211_frame *wh; + u_int8_t *frm, *efrm; + + wh = mtod(m0, struct ieee80211_frame *); + frm = (u_int8_t *)&wh[1]; + efrm = mtod(m0, u_int8_t *) + m0->m_len; + switch (subtype) { + case IEEE80211_FC0_SUBTYPE_DEAUTH: + case IEEE80211_FC0_SUBTYPE_PROBE_RESP: + case IEEE80211_FC0_SUBTYPE_BEACON: + case IEEE80211_FC0_SUBTYPE_PROBE_REQ: + case IEEE80211_FC0_SUBTYPE_AUTH: + case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: + case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: + case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: + case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: + case IEEE80211_FC0_SUBTYPE_DISASSOC: + vap->iv_stats.is_rx_mgtdiscard++; + break; + case IEEE80211_FC0_SUBTYPE_ACTION: + if (vap->iv_state != IEEE80211_S_RUN || + IEEE80211_IS_MULTICAST(wh->i_addr1)) { + vap->iv_stats.is_rx_mgtdiscard++; + break; + } + ni->ni_inact = ni->ni_inact_reload; + if (ieee80211_parse_action(ni, m0) == 0) + ic->ic_recv_action(ni, frm, efrm); + break; + default: + IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, + wh, "mgt", "subtype 0x%x not handled", subtype); + vap->iv_stats.is_rx_badsubtype++; + break; + } +} |