ieee80211_wds.c revision 186658
1178354Ssam/*- 2178354Ssam * Copyright (c) 2007-2008 Sam Leffler, Errno Consulting 3178354Ssam * All rights reserved. 4178354Ssam * 5178354Ssam * Redistribution and use in source and binary forms, with or without 6178354Ssam * modification, are permitted provided that the following conditions 7178354Ssam * are met: 8178354Ssam * 1. Redistributions of source code must retain the above copyright 9178354Ssam * notice, this list of conditions and the following disclaimer. 10178354Ssam * 2. Redistributions in binary form must reproduce the above copyright 11178354Ssam * notice, this list of conditions and the following disclaimer in the 12178354Ssam * documentation and/or other materials provided with the distribution. 13178354Ssam * 14178354Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15178354Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16178354Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17178354Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18178354Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19178354Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20178354Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21178354Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22178354Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23178354Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24178354Ssam */ 25178354Ssam 26178354Ssam#include <sys/cdefs.h> 27178354Ssam#ifdef __FreeBSD__ 28178354Ssam__FBSDID("$FreeBSD: head/sys/net80211/ieee80211_wds.c 186658 2008-12-31 21:21:46Z sam $"); 29178354Ssam#endif 30178354Ssam 31178354Ssam/* 32178354Ssam * IEEE 802.11 WDS mode support. 33178354Ssam */ 34178354Ssam#include "opt_inet.h" 35178354Ssam#include "opt_wlan.h" 36178354Ssam 37178354Ssam#include <sys/param.h> 38178354Ssam#include <sys/systm.h> 39178354Ssam#include <sys/mbuf.h> 40178354Ssam#include <sys/malloc.h> 41178354Ssam#include <sys/kernel.h> 42178354Ssam 43178354Ssam#include <sys/socket.h> 44178354Ssam#include <sys/sockio.h> 45178354Ssam#include <sys/endian.h> 46178354Ssam#include <sys/errno.h> 47178354Ssam#include <sys/proc.h> 48178354Ssam#include <sys/sysctl.h> 49178354Ssam 50178354Ssam#include <net/if.h> 51178354Ssam#include <net/if_media.h> 52178354Ssam#include <net/if_llc.h> 53178354Ssam#include <net/ethernet.h> 54178354Ssam 55178354Ssam#include <net/bpf.h> 56178354Ssam 57178354Ssam#include <net80211/ieee80211_var.h> 58178354Ssam#include <net80211/ieee80211_wds.h> 59178354Ssam#include <net80211/ieee80211_input.h> 60178354Ssam 61178354Ssamstatic void wds_vattach(struct ieee80211vap *); 62178354Ssamstatic int wds_newstate(struct ieee80211vap *, enum ieee80211_state, int); 63178354Ssamstatic int wds_input(struct ieee80211_node *ni, struct mbuf *m, 64178354Ssam int rssi, int noise, uint32_t rstamp); 65178354Ssamstatic void wds_recv_mgmt(struct ieee80211_node *, struct mbuf *, 66178354Ssam int subtype, int rssi, int noise, u_int32_t rstamp); 67178354Ssam 68178354Ssamvoid 69178354Ssamieee80211_wds_attach(struct ieee80211com *ic) 70178354Ssam{ 71178354Ssam ic->ic_vattach[IEEE80211_M_WDS] = wds_vattach; 72178354Ssam} 73178354Ssam 74178354Ssamvoid 75178354Ssamieee80211_wds_detach(struct ieee80211com *ic) 76178354Ssam{ 77178354Ssam} 78178354Ssam 79178354Ssamstatic void 80178354Ssamwds_vdetach(struct ieee80211vap *vap) 81178354Ssam{ 82178354Ssam if (vap->iv_bss != NULL) { 83178354Ssam /* XXX locking? */ 84178354Ssam if (vap->iv_bss->ni_wdsvap == vap) 85178354Ssam vap->iv_bss->ni_wdsvap = NULL; 86178354Ssam } 87178354Ssam} 88178354Ssam 89178354Ssamstatic void 90178354Ssamwds_vattach(struct ieee80211vap *vap) 91178354Ssam{ 92178354Ssam vap->iv_newstate = wds_newstate; 93178354Ssam vap->iv_input = wds_input; 94178354Ssam vap->iv_recv_mgmt = wds_recv_mgmt; 95178354Ssam vap->iv_opdetach = wds_vdetach; 96178354Ssam} 97178354Ssam 98178354Ssamstatic int 99178354Ssamieee80211_create_wds(struct ieee80211vap *vap, struct ieee80211_channel *chan) 100178354Ssam{ 101178354Ssam struct ieee80211com *ic = vap->iv_ic; 102178354Ssam struct ieee80211_node_table *nt = &ic->ic_sta; 103178354Ssam struct ieee80211_node *ni, *obss; 104178354Ssam 105178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_WDS, 106178354Ssam "%s: creating link to %s on channel %u\n", __func__, 107178354Ssam ether_sprintf(vap->iv_des_bssid), ieee80211_chan2ieee(ic, chan)); 108178354Ssam 109178354Ssam /* NB: vap create must specify the bssid for the link */ 110178354Ssam KASSERT(vap->iv_flags & IEEE80211_F_DESBSSID, ("no bssid")); 111178354Ssam /* NB: we should only be called on RUN transition */ 112178354Ssam KASSERT(vap->iv_state == IEEE80211_S_RUN, ("!RUN state")); 113178354Ssam 114178354Ssam if ((vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY) == 0) { 115178354Ssam /* 116178354Ssam * Dynamic/non-legacy WDS. Reference the associated 117178354Ssam * station specified by the desired bssid setup at vap 118178354Ssam * create. Point ni_wdsvap at the WDS vap so 4-address 119178354Ssam * frames received through the associated AP vap will 120178354Ssam * be dispatched upward (e.g. to a bridge) as though 121178354Ssam * they arrived on the WDS vap. 122178354Ssam */ 123178354Ssam IEEE80211_NODE_LOCK(nt); 124178354Ssam obss = NULL; 125178354Ssam ni = ieee80211_find_node_locked(&ic->ic_sta, vap->iv_des_bssid); 126178354Ssam if (ni == NULL) { 127178354Ssam /* 128178354Ssam * Node went away before we could hookup. This 129178354Ssam * should be ok; no traffic will flow and a leave 130178354Ssam * event will be dispatched that should cause 131178354Ssam * the vap to be destroyed. 132178354Ssam */ 133178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_WDS, 134178354Ssam "%s: station %s went away\n", 135178354Ssam __func__, ether_sprintf(vap->iv_des_bssid)); 136178354Ssam /* XXX stat? */ 137178354Ssam } else if (ni->ni_wdsvap != NULL) { 138178354Ssam /* 139178354Ssam * Node already setup with a WDS vap; we cannot 140178354Ssam * allow multiple references so disallow. If 141178354Ssam * ni_wdsvap points at us that's ok; we should 142178354Ssam * do nothing anyway. 143178354Ssam */ 144178354Ssam /* XXX printf instead? */ 145178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_WDS, 146178354Ssam "%s: station %s in use with %s\n", 147178354Ssam __func__, ether_sprintf(vap->iv_des_bssid), 148178354Ssam ni->ni_wdsvap->iv_ifp->if_xname); 149178354Ssam /* XXX stat? */ 150178354Ssam } else { 151178354Ssam /* 152178354Ssam * Committed to new node, setup state. 153178354Ssam */ 154178354Ssam obss = vap->iv_bss; 155178354Ssam vap->iv_bss = ni; 156178354Ssam ni->ni_wdsvap = vap; 157178354Ssam } 158178354Ssam IEEE80211_NODE_UNLOCK(nt); 159178354Ssam if (obss != NULL) { 160178354Ssam /* NB: deferred to avoid recursive lock */ 161178354Ssam ieee80211_free_node(obss); 162178354Ssam } 163178354Ssam } else { 164178354Ssam /* 165178354Ssam * Legacy WDS vap setup. 166178354Ssam */ 167178354Ssam /* 168178354Ssam * The far end does not associate so we just create 169178354Ssam * create a new node and install it as the vap's 170178354Ssam * bss node. We must simulate an association and 171178354Ssam * authorize the port for traffic to flow. 172178354Ssam * XXX check if node already in sta table? 173178354Ssam */ 174178354Ssam ni = ieee80211_node_create_wds(vap, vap->iv_des_bssid, chan); 175178354Ssam if (ni != NULL) { 176178354Ssam obss = vap->iv_bss; 177178354Ssam vap->iv_bss = ieee80211_ref_node(ni); 178178354Ssam ni->ni_flags |= IEEE80211_NODE_AREF; 179178354Ssam if (obss != NULL) 180178354Ssam ieee80211_free_node(obss); 181178354Ssam /* give driver a chance to setup state like ni_txrate */ 182178354Ssam if (ic->ic_newassoc != NULL) 183178354Ssam ic->ic_newassoc(ni, 1); 184178354Ssam /* tell the authenticator about new station */ 185178354Ssam if (vap->iv_auth->ia_node_join != NULL) 186178354Ssam vap->iv_auth->ia_node_join(ni); 187178354Ssam if (ni->ni_authmode != IEEE80211_AUTH_8021X) 188178354Ssam ieee80211_node_authorize(ni); 189178354Ssam 190178354Ssam ieee80211_notify_node_join(ni, 1 /*newassoc*/); 191178354Ssam /* XXX inject l2uf frame */ 192178354Ssam } 193178354Ssam } 194178354Ssam 195178354Ssam /* 196178354Ssam * Flush pending frames now that were setup. 197178354Ssam */ 198178354Ssam if (ni != NULL && IEEE80211_NODE_WDSQ_QLEN(ni) != 0) { 199178354Ssam int8_t rssi, noise; 200178354Ssam 201178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_WDS, ni, 202178354Ssam "flush wds queue, %u packets queued", 203178354Ssam IEEE80211_NODE_WDSQ_QLEN(ni)); 204178354Ssam ic->ic_node_getsignal(ni, &rssi, &noise); 205178354Ssam for (;;) { 206178354Ssam struct mbuf *m; 207178354Ssam 208178354Ssam IEEE80211_NODE_WDSQ_LOCK(ni); 209178354Ssam _IEEE80211_NODE_WDSQ_DEQUEUE_HEAD(ni, m); 210178354Ssam IEEE80211_NODE_WDSQ_UNLOCK(ni); 211178354Ssam if (m == NULL) 212178354Ssam break; 213178354Ssam /* XXX cheat and re-use last rstamp */ 214178354Ssam ieee80211_input(ni, m, rssi, noise, ni->ni_rstamp); 215178354Ssam } 216178354Ssam } 217178354Ssam return (ni == NULL ? ENOENT : 0); 218178354Ssam} 219178354Ssam 220178354Ssam/* 221178354Ssam * Propagate multicast frames of an ap vap to all DWDS links. 222178354Ssam * The caller is assumed to have verified this frame is multicast. 223178354Ssam */ 224178354Ssamvoid 225178354Ssamieee80211_dwds_mcast(struct ieee80211vap *vap0, struct mbuf *m) 226178354Ssam{ 227178354Ssam struct ieee80211com *ic = vap0->iv_ic; 228178354Ssam struct ifnet *parent = ic->ic_ifp; 229178354Ssam const struct ether_header *eh = mtod(m, const struct ether_header *); 230178354Ssam struct ieee80211_node *ni; 231178354Ssam struct ieee80211vap *vap; 232178354Ssam struct ifnet *ifp; 233178354Ssam struct mbuf *mcopy; 234178354Ssam int err; 235178354Ssam 236178354Ssam KASSERT(ETHER_IS_MULTICAST(eh->ether_dhost), 237178354Ssam ("%s not mcast", ether_sprintf(eh->ether_dhost))); 238178354Ssam 239178354Ssam /* XXX locking */ 240178354Ssam TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { 241178354Ssam /* only DWDS vaps are interesting */ 242178354Ssam if (vap->iv_opmode != IEEE80211_M_WDS || 243178354Ssam (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY)) 244178354Ssam continue; 245178354Ssam /* if it came in this interface, don't send it back out */ 246178354Ssam ifp = vap->iv_ifp; 247178354Ssam if (ifp == m->m_pkthdr.rcvif) 248178354Ssam continue; 249178354Ssam /* 250178354Ssam * Duplicate the frame and send it. We don't need 251178354Ssam * to classify or lookup the tx node; this was already 252178354Ssam * done by the caller so we can just re-use the info. 253178354Ssam */ 254178354Ssam mcopy = m_copypacket(m, M_DONTWAIT); 255178354Ssam if (mcopy == NULL) { 256178354Ssam ifp->if_oerrors++; 257178354Ssam /* XXX stat + msg */ 258178354Ssam continue; 259178354Ssam } 260178354Ssam ni = ieee80211_find_txnode(vap, eh->ether_dhost); 261178354Ssam if (ni == NULL) { 262178354Ssam /* NB: ieee80211_find_txnode does stat+msg */ 263178354Ssam ifp->if_oerrors++; 264178354Ssam m_freem(mcopy); 265178354Ssam continue; 266178354Ssam } 267178354Ssam if (ieee80211_classify(ni, mcopy)) { 268178354Ssam IEEE80211_DISCARD_MAC(vap, 269178354Ssam IEEE80211_MSG_OUTPUT | IEEE80211_MSG_WDS, 270178354Ssam eh->ether_dhost, NULL, 271178354Ssam "%s", "classification failure"); 272178354Ssam vap->iv_stats.is_tx_classify++; 273178354Ssam ifp->if_oerrors++; 274178354Ssam m_freem(mcopy); 275178354Ssam ieee80211_free_node(ni); 276178354Ssam continue; 277178354Ssam } 278178354Ssam mcopy->m_flags |= M_MCAST | M_WDS; 279178354Ssam mcopy->m_pkthdr.rcvif = (void *) ni; 280178354Ssam 281186658Ssam err = parent->if_transmit(parent, mcopy); 282178354Ssam if (err) { 283178354Ssam /* NB: IFQ_HANDOFF reclaims mbuf */ 284178354Ssam ifp->if_oerrors++; 285178354Ssam ieee80211_free_node(ni); 286178354Ssam } else 287178354Ssam ifp->if_opackets++; 288178354Ssam } 289178354Ssam} 290178354Ssam 291178354Ssam/* 292178354Ssam * Handle DWDS discovery on receipt of a 4-address frame in 293178354Ssam * ap mode. Queue the frame and post an event for someone 294178354Ssam * to plumb the necessary WDS vap for this station. Frames 295178354Ssam * received prior to the vap set running will then be reprocessed 296178354Ssam * as if they were just received. 297178354Ssam */ 298178354Ssamvoid 299178354Ssamieee80211_dwds_discover(struct ieee80211_node *ni, struct mbuf *m) 300178354Ssam{ 301178354Ssam struct ieee80211vap *vap = ni->ni_vap; 302178354Ssam struct ieee80211com *ic = ni->ni_ic; 303178354Ssam int qlen, age; 304178354Ssam 305178354Ssam IEEE80211_NODE_WDSQ_LOCK(ni); 306178354Ssam if (!_IF_QFULL(&ni->ni_wdsq)) { 307178354Ssam /* 308178354Ssam * Tag the frame with it's expiry time and insert 309178354Ssam * it in the queue. The aging interval is 4 times 310178354Ssam * the listen interval specified by the station. 311178354Ssam * Frames that sit around too long are reclaimed 312178354Ssam * using this information. 313178354Ssam */ 314178354Ssam /* XXX handle overflow? */ 315178354Ssam /* XXX per/vap beacon interval? */ 316178354Ssam /* NB: TU -> secs */ 317178354Ssam age = ((ni->ni_intval * ic->ic_lintval) << 2) / 1024; 318178354Ssam _IEEE80211_NODE_WDSQ_ENQUEUE(ni, m, qlen, age); 319178354Ssam IEEE80211_NODE_WDSQ_UNLOCK(ni); 320178354Ssam 321178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_WDS, ni, 322178354Ssam "save frame, %u now queued", qlen); 323178354Ssam } else { 324178354Ssam vap->iv_stats.is_dwds_qdrop++; 325178354Ssam _IF_DROP(&ni->ni_wdsq); 326178354Ssam IEEE80211_NODE_WDSQ_UNLOCK(ni); 327178354Ssam 328178354Ssam IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_WDS, 329178354Ssam mtod(m, struct ieee80211_frame *), "wds data", 330178354Ssam "pending q overflow, drops %d (len %d)", 331178354Ssam ni->ni_wdsq.ifq_drops, ni->ni_wdsq.ifq_len); 332178354Ssam 333178354Ssam#ifdef IEEE80211_DEBUG 334178354Ssam if (ieee80211_msg_dumppkts(vap)) 335178354Ssam ieee80211_dump_pkt(ic, mtod(m, caddr_t), 336178354Ssam m->m_len, -1, -1); 337178354Ssam#endif 338178354Ssam /* XXX tail drop? */ 339178354Ssam m_freem(m); 340178354Ssam } 341178354Ssam ieee80211_notify_wds_discover(ni); 342178354Ssam} 343178354Ssam 344178354Ssam/* 345178354Ssam * Age frames on the WDS pending queue. The aging interval is 346178354Ssam * 4 times the listen interval specified by the station. This 347178354Ssam * number is factored into the age calculations when the frame 348178354Ssam * is placed on the queue. We store ages as time differences 349178354Ssam * so we can check and/or adjust only the head of the list. 350178354Ssam * If a frame's age exceeds the threshold then discard it. 351178354Ssam * The number of frames discarded is returned to the caller. 352178354Ssam */ 353178354Ssamint 354178354Ssamieee80211_node_wdsq_age(struct ieee80211_node *ni) 355178354Ssam{ 356178354Ssam#ifdef IEEE80211_DEBUG 357178354Ssam struct ieee80211vap *vap = ni->ni_vap; 358178354Ssam#endif 359178354Ssam struct mbuf *m; 360178354Ssam int discard = 0; 361178354Ssam 362178354Ssam IEEE80211_NODE_WDSQ_LOCK(ni); 363178354Ssam while (_IF_POLL(&ni->ni_wdsq, m) != NULL && 364178354Ssam M_AGE_GET(m) < IEEE80211_INACT_WAIT) { 365178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_WDS, ni, 366178354Ssam "discard frame, age %u", M_AGE_GET(m)); 367178354Ssam 368178354Ssam /* XXX could be optimized */ 369178354Ssam _IEEE80211_NODE_WDSQ_DEQUEUE_HEAD(ni, m); 370178354Ssam m_freem(m); 371178354Ssam discard++; 372178354Ssam } 373178354Ssam if (m != NULL) 374178354Ssam M_AGE_SUB(m, IEEE80211_INACT_WAIT); 375178354Ssam IEEE80211_NODE_WDSQ_UNLOCK(ni); 376178354Ssam 377178354Ssam IEEE80211_NOTE(vap, IEEE80211_MSG_WDS, ni, 378178354Ssam "discard %u frames for age", discard); 379178354Ssam#if 0 380178354Ssam IEEE80211_NODE_STAT_ADD(ni, wds_discard, discard); 381178354Ssam#endif 382178354Ssam return discard; 383178354Ssam} 384178354Ssam 385178354Ssam/* 386178354Ssam * IEEE80211_M_WDS vap state machine handler. 387178354Ssam */ 388178354Ssamstatic int 389178354Ssamwds_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) 390178354Ssam{ 391178354Ssam struct ieee80211com *ic = vap->iv_ic; 392178354Ssam struct ieee80211_node *ni; 393178354Ssam enum ieee80211_state ostate; 394178354Ssam int error; 395178354Ssam 396178354Ssam IEEE80211_LOCK_ASSERT(ic); 397178354Ssam 398178354Ssam ostate = vap->iv_state; 399178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s\n", __func__, 400178354Ssam ieee80211_state_name[ostate], ieee80211_state_name[nstate]); 401178354Ssam vap->iv_state = nstate; /* state transition */ 402178354Ssam callout_stop(&vap->iv_mgtsend); /* XXX callout_drain */ 403178354Ssam if (ostate != IEEE80211_S_SCAN) 404178354Ssam ieee80211_cancel_scan(vap); /* background scan */ 405178354Ssam ni = vap->iv_bss; /* NB: no reference held */ 406178354Ssam error = 0; 407178354Ssam switch (nstate) { 408178354Ssam case IEEE80211_S_INIT: 409178354Ssam switch (ostate) { 410178354Ssam case IEEE80211_S_SCAN: 411178354Ssam ieee80211_cancel_scan(vap); 412178354Ssam break; 413178354Ssam default: 414178354Ssam break; 415178354Ssam } 416178354Ssam if (ostate != IEEE80211_S_INIT) { 417178354Ssam /* NB: optimize INIT -> INIT case */ 418178354Ssam ieee80211_reset_bss(vap); 419178354Ssam } 420178354Ssam break; 421178354Ssam case IEEE80211_S_SCAN: 422178354Ssam switch (ostate) { 423178354Ssam case IEEE80211_S_INIT: 424178354Ssam ieee80211_check_scan_current(vap); 425178354Ssam break; 426178354Ssam default: 427178354Ssam break; 428178354Ssam } 429178354Ssam break; 430178354Ssam case IEEE80211_S_RUN: 431178354Ssam if (ostate == IEEE80211_S_INIT) { 432178354Ssam /* 433178354Ssam * Already have a channel; bypass the scan 434178354Ssam * and startup immediately. 435178354Ssam */ 436178354Ssam error = ieee80211_create_wds(vap, ic->ic_curchan); 437178354Ssam } 438178354Ssam break; 439178354Ssam default: 440178354Ssam break; 441178354Ssam } 442178354Ssam return error; 443178354Ssam} 444178354Ssam 445178354Ssam/* 446178354Ssam * Process a received frame. The node associated with the sender 447178354Ssam * should be supplied. If nothing was found in the node table then 448178354Ssam * the caller is assumed to supply a reference to iv_bss instead. 449178354Ssam * The RSSI and a timestamp are also supplied. The RSSI data is used 450178354Ssam * during AP scanning to select a AP to associate with; it can have 451178354Ssam * any units so long as values have consistent units and higher values 452178354Ssam * mean ``better signal''. The receive timestamp is currently not used 453178354Ssam * by the 802.11 layer. 454178354Ssam */ 455178354Ssamstatic int 456178354Ssamwds_input(struct ieee80211_node *ni, struct mbuf *m, 457178354Ssam int rssi, int noise, uint32_t rstamp) 458178354Ssam{ 459178354Ssam#define SEQ_LEQ(a,b) ((int)((a)-(b)) <= 0) 460178354Ssam#define HAS_SEQ(type) ((type & 0x4) == 0) 461178354Ssam struct ieee80211vap *vap = ni->ni_vap; 462178354Ssam struct ieee80211com *ic = ni->ni_ic; 463178354Ssam struct ifnet *ifp = vap->iv_ifp; 464178354Ssam struct ieee80211_frame *wh; 465178354Ssam struct ieee80211_key *key; 466178354Ssam struct ether_header *eh; 467178354Ssam int hdrspace, need_tap; 468178354Ssam uint8_t dir, type, subtype, qos; 469178354Ssam uint16_t rxseq; 470178354Ssam 471183247Ssam if (m->m_flags & M_AMPDU_MPDU) { 472178354Ssam /* 473178354Ssam * Fastpath for A-MPDU reorder q resubmission. Frames 474183247Ssam * w/ M_AMPDU_MPDU marked have already passed through 475183247Ssam * here but were received out of order and been held on 476183247Ssam * the reorder queue. When resubmitted they are marked 477183247Ssam * with the M_AMPDU_MPDU flag and we can bypass most of 478183247Ssam * the normal processing. 479178354Ssam */ 480178354Ssam wh = mtod(m, struct ieee80211_frame *); 481178354Ssam type = IEEE80211_FC0_TYPE_DATA; 482178354Ssam dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; 483178354Ssam subtype = IEEE80211_FC0_SUBTYPE_QOS; 484178354Ssam hdrspace = ieee80211_hdrspace(ic, wh); /* XXX optimize? */ 485178354Ssam goto resubmit_ampdu; 486178354Ssam } 487178354Ssam 488178354Ssam KASSERT(ni != NULL, ("null node")); 489178354Ssam 490178354Ssam need_tap = 1; /* mbuf need to be tapped. */ 491178354Ssam type = -1; /* undefined */ 492178354Ssam 493178354Ssam if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_min)) { 494178354Ssam IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, 495178354Ssam ni->ni_macaddr, NULL, 496178354Ssam "too short (1): len %u", m->m_pkthdr.len); 497178354Ssam vap->iv_stats.is_rx_tooshort++; 498178354Ssam goto out; 499178354Ssam } 500178354Ssam /* 501178354Ssam * Bit of a cheat here, we use a pointer for a 3-address 502178354Ssam * frame format but don't reference fields past outside 503178354Ssam * ieee80211_frame_min w/o first validating the data is 504178354Ssam * present. 505178354Ssam */ 506178354Ssam wh = mtod(m, struct ieee80211_frame *); 507178354Ssam 508178354Ssam if ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) != 509178354Ssam IEEE80211_FC0_VERSION_0) { 510178354Ssam IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, 511178354Ssam ni->ni_macaddr, NULL, "wrong version %x", wh->i_fc[0]); 512178354Ssam vap->iv_stats.is_rx_badversion++; 513178354Ssam goto err; 514178354Ssam } 515178354Ssam 516178354Ssam dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; 517178354Ssam type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; 518178354Ssam subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; 519178354Ssam 520178354Ssam /* NB: WDS vap's do not scan */ 521178354Ssam if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_addr4)) { 522178354Ssam IEEE80211_DISCARD_MAC(vap, 523178354Ssam IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, 524178354Ssam "too short (3): len %u", m->m_pkthdr.len); 525178354Ssam vap->iv_stats.is_rx_tooshort++; 526178354Ssam goto out; 527178354Ssam } 528178354Ssam /* NB: the TA is implicitly verified by finding the wds peer node */ 529178354Ssam if (!IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr) && 530178354Ssam !IEEE80211_ADDR_EQ(wh->i_addr1, ifp->if_broadcastaddr)) { 531178354Ssam /* not interested in */ 532178354Ssam IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, 533178354Ssam wh->i_addr1, NULL, "%s", "not to bss"); 534178354Ssam vap->iv_stats.is_rx_wrongbss++; 535178354Ssam goto out; 536178354Ssam } 537178354Ssam IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); 538178354Ssam ni->ni_noise = noise; 539178354Ssam ni->ni_rstamp = rstamp; 540178354Ssam if (HAS_SEQ(type)) { 541178354Ssam uint8_t tid = ieee80211_gettid(wh); 542178354Ssam if (IEEE80211_QOS_HAS_SEQ(wh) && 543178354Ssam TID_TO_WME_AC(tid) >= WME_AC_VI) 544178354Ssam ic->ic_wme.wme_hipri_traffic++; 545178354Ssam rxseq = le16toh(*(uint16_t *)wh->i_seq); 546178354Ssam if ((ni->ni_flags & IEEE80211_NODE_HT) == 0 && 547178354Ssam (wh->i_fc[1] & IEEE80211_FC1_RETRY) && 548178354Ssam SEQ_LEQ(rxseq, ni->ni_rxseqs[tid])) { 549178354Ssam /* duplicate, discard */ 550178354Ssam IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, 551178354Ssam wh->i_addr1, "duplicate", 552178354Ssam "seqno <%u,%u> fragno <%u,%u> tid %u", 553178354Ssam rxseq >> IEEE80211_SEQ_SEQ_SHIFT, 554178354Ssam ni->ni_rxseqs[tid] >> IEEE80211_SEQ_SEQ_SHIFT, 555178354Ssam rxseq & IEEE80211_SEQ_FRAG_MASK, 556178354Ssam ni->ni_rxseqs[tid] & IEEE80211_SEQ_FRAG_MASK, 557178354Ssam tid); 558178354Ssam vap->iv_stats.is_rx_dup++; 559178354Ssam IEEE80211_NODE_STAT(ni, rx_dup); 560178354Ssam goto out; 561178354Ssam } 562178354Ssam ni->ni_rxseqs[tid] = rxseq; 563178354Ssam } 564178354Ssam switch (type) { 565178354Ssam case IEEE80211_FC0_TYPE_DATA: 566178354Ssam hdrspace = ieee80211_hdrspace(ic, wh); 567178354Ssam if (m->m_len < hdrspace && 568178354Ssam (m = m_pullup(m, hdrspace)) == NULL) { 569178354Ssam IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, 570178354Ssam ni->ni_macaddr, NULL, 571178354Ssam "data too short: expecting %u", hdrspace); 572178354Ssam vap->iv_stats.is_rx_tooshort++; 573178354Ssam goto out; /* XXX */ 574178354Ssam } 575178354Ssam if (dir != IEEE80211_FC1_DIR_DSTODS) { 576178354Ssam IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, 577178354Ssam wh, "data", "incorrect dir 0x%x", dir); 578178354Ssam vap->iv_stats.is_rx_wrongdir++; 579178354Ssam goto out; 580178354Ssam } 581178354Ssam /* 582178354Ssam * Only legacy WDS traffic should take this path. 583178354Ssam */ 584178354Ssam if ((vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY) == 0) { 585178354Ssam IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, 586178354Ssam wh, "data", "%s", "not legacy wds"); 587178354Ssam vap->iv_stats.is_rx_wrongdir++;/*XXX*/ 588178354Ssam goto out; 589178354Ssam } 590178354Ssam if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) 591178354Ssam ni->ni_inact = ni->ni_inact_reload; 592178354Ssam /* 593183247Ssam * Handle A-MPDU re-ordering. If the frame is to be 594183247Ssam * processed directly then ieee80211_ampdu_reorder 595178354Ssam * will return 0; otherwise it has consumed the mbuf 596178354Ssam * and we should do nothing more with it. 597178354Ssam */ 598183247Ssam if ((m->m_flags & M_AMPDU) && 599178354Ssam ieee80211_ampdu_reorder(ni, m) != 0) { 600178354Ssam m = NULL; 601178354Ssam goto out; 602178354Ssam } 603178354Ssam resubmit_ampdu: 604178354Ssam 605178354Ssam /* 606178354Ssam * Handle privacy requirements. Note that we 607178354Ssam * must not be preempted from here until after 608178354Ssam * we (potentially) call ieee80211_crypto_demic; 609178354Ssam * otherwise we may violate assumptions in the 610178354Ssam * crypto cipher modules used to do delayed update 611178354Ssam * of replay sequence numbers. 612178354Ssam */ 613178354Ssam if (wh->i_fc[1] & IEEE80211_FC1_WEP) { 614178354Ssam if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { 615178354Ssam /* 616178354Ssam * Discard encrypted frames when privacy is off. 617178354Ssam */ 618178354Ssam IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, 619178354Ssam wh, "WEP", "%s", "PRIVACY off"); 620178354Ssam vap->iv_stats.is_rx_noprivacy++; 621178354Ssam IEEE80211_NODE_STAT(ni, rx_noprivacy); 622178354Ssam goto out; 623178354Ssam } 624178354Ssam key = ieee80211_crypto_decap(ni, m, hdrspace); 625178354Ssam if (key == NULL) { 626178354Ssam /* NB: stats+msgs handled in crypto_decap */ 627178354Ssam IEEE80211_NODE_STAT(ni, rx_wepfail); 628178354Ssam goto out; 629178354Ssam } 630178354Ssam wh = mtod(m, struct ieee80211_frame *); 631178354Ssam wh->i_fc[1] &= ~IEEE80211_FC1_WEP; 632178354Ssam } else { 633178354Ssam /* XXX M_WEP and IEEE80211_F_PRIVACY */ 634178354Ssam key = NULL; 635178354Ssam } 636178354Ssam 637178354Ssam /* 638178354Ssam * Save QoS bits for use below--before we strip the header. 639178354Ssam */ 640178354Ssam if (subtype == IEEE80211_FC0_SUBTYPE_QOS) { 641178354Ssam qos = (dir == IEEE80211_FC1_DIR_DSTODS) ? 642178354Ssam ((struct ieee80211_qosframe_addr4 *)wh)->i_qos[0] : 643178354Ssam ((struct ieee80211_qosframe *)wh)->i_qos[0]; 644178354Ssam } else 645178354Ssam qos = 0; 646178354Ssam 647178354Ssam /* 648178354Ssam * Next up, any fragmentation. 649178354Ssam */ 650178354Ssam if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { 651178354Ssam m = ieee80211_defrag(ni, m, hdrspace); 652178354Ssam if (m == NULL) { 653178354Ssam /* Fragment dropped or frame not complete yet */ 654178354Ssam goto out; 655178354Ssam } 656178354Ssam } 657178354Ssam wh = NULL; /* no longer valid, catch any uses */ 658178354Ssam 659178354Ssam /* 660178354Ssam * Next strip any MSDU crypto bits. 661178354Ssam */ 662178354Ssam if (key != NULL && !ieee80211_crypto_demic(vap, key, m, 0)) { 663178354Ssam IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, 664178354Ssam ni->ni_macaddr, "data", "%s", "demic error"); 665178354Ssam vap->iv_stats.is_rx_demicfail++; 666178354Ssam IEEE80211_NODE_STAT(ni, rx_demicfail); 667178354Ssam goto out; 668178354Ssam } 669178354Ssam 670178354Ssam /* copy to listener after decrypt */ 671178354Ssam if (bpf_peers_present(vap->iv_rawbpf)) 672178354Ssam bpf_mtap(vap->iv_rawbpf, m); 673178354Ssam need_tap = 0; 674178354Ssam 675178354Ssam /* 676178354Ssam * Finally, strip the 802.11 header. 677178354Ssam */ 678178354Ssam m = ieee80211_decap(vap, m, hdrspace); 679178354Ssam if (m == NULL) { 680178354Ssam /* XXX mask bit to check for both */ 681178354Ssam /* don't count Null data frames as errors */ 682178354Ssam if (subtype == IEEE80211_FC0_SUBTYPE_NODATA || 683178354Ssam subtype == IEEE80211_FC0_SUBTYPE_QOS_NULL) 684178354Ssam goto out; 685178354Ssam IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, 686178354Ssam ni->ni_macaddr, "data", "%s", "decap error"); 687178354Ssam vap->iv_stats.is_rx_decap++; 688178354Ssam IEEE80211_NODE_STAT(ni, rx_decap); 689178354Ssam goto err; 690178354Ssam } 691178354Ssam eh = mtod(m, struct ether_header *); 692178354Ssam if (!ieee80211_node_is_authorized(ni)) { 693178354Ssam /* 694178354Ssam * Deny any non-PAE frames received prior to 695178354Ssam * authorization. For open/shared-key 696178354Ssam * authentication the port is mark authorized 697178354Ssam * after authentication completes. For 802.1x 698178354Ssam * the port is not marked authorized by the 699178354Ssam * authenticator until the handshake has completed. 700178354Ssam */ 701178354Ssam if (eh->ether_type != htons(ETHERTYPE_PAE)) { 702178354Ssam IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, 703178354Ssam eh->ether_shost, "data", 704178354Ssam "unauthorized port: ether type 0x%x len %u", 705178354Ssam eh->ether_type, m->m_pkthdr.len); 706178354Ssam vap->iv_stats.is_rx_unauth++; 707178354Ssam IEEE80211_NODE_STAT(ni, rx_unauth); 708178354Ssam goto err; 709178354Ssam } 710178354Ssam } else { 711178354Ssam /* 712178354Ssam * When denying unencrypted frames, discard 713178354Ssam * any non-PAE frames received without encryption. 714178354Ssam */ 715178354Ssam if ((vap->iv_flags & IEEE80211_F_DROPUNENC) && 716178354Ssam (key == NULL && (m->m_flags & M_WEP) == 0) && 717178354Ssam eh->ether_type != htons(ETHERTYPE_PAE)) { 718178354Ssam /* 719178354Ssam * Drop unencrypted frames. 720178354Ssam */ 721178354Ssam vap->iv_stats.is_rx_unencrypted++; 722178354Ssam IEEE80211_NODE_STAT(ni, rx_unencrypted); 723178354Ssam goto out; 724178354Ssam } 725178354Ssam } 726178354Ssam /* XXX require HT? */ 727178354Ssam if (qos & IEEE80211_QOS_AMSDU) { 728178354Ssam m = ieee80211_decap_amsdu(ni, m); 729178354Ssam if (m == NULL) 730178354Ssam return IEEE80211_FC0_TYPE_DATA; 731184480Ssam } else if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF) && 732178354Ssam#define FF_LLC_SIZE (sizeof(struct ether_header) + sizeof(struct llc)) 733178354Ssam m->m_pkthdr.len >= 3*FF_LLC_SIZE) { 734178354Ssam struct llc *llc; 735178354Ssam 736178354Ssam /* 737178354Ssam * Check for fast-frame tunnel encapsulation. 738178354Ssam */ 739178354Ssam if (m->m_len < FF_LLC_SIZE && 740178354Ssam (m = m_pullup(m, FF_LLC_SIZE)) == NULL) { 741178354Ssam IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, 742178354Ssam ni->ni_macaddr, "fast-frame", 743178354Ssam "%s", "m_pullup(llc) failed"); 744178354Ssam vap->iv_stats.is_rx_tooshort++; 745178354Ssam return IEEE80211_FC0_TYPE_DATA; 746178354Ssam } 747178354Ssam llc = (struct llc *)(mtod(m, uint8_t *) + 748178354Ssam sizeof(struct ether_header)); 749178354Ssam if (llc->llc_snap.ether_type == htons(ATH_FF_ETH_TYPE)) { 750178354Ssam m_adj(m, FF_LLC_SIZE); 751178354Ssam m = ieee80211_decap_fastframe(ni, m); 752178354Ssam if (m == NULL) 753178354Ssam return IEEE80211_FC0_TYPE_DATA; 754178354Ssam } 755178354Ssam } 756178354Ssam#undef FF_LLC_SIZE 757178354Ssam ieee80211_deliver_data(vap, ni, m); 758178354Ssam return IEEE80211_FC0_TYPE_DATA; 759178354Ssam 760178354Ssam case IEEE80211_FC0_TYPE_MGT: 761178354Ssam vap->iv_stats.is_rx_mgmt++; 762178354Ssam IEEE80211_NODE_STAT(ni, rx_mgmt); 763178354Ssam if (dir != IEEE80211_FC1_DIR_NODS) { 764178354Ssam IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, 765178354Ssam wh, "data", "incorrect dir 0x%x", dir); 766178354Ssam vap->iv_stats.is_rx_wrongdir++; 767178354Ssam goto err; 768178354Ssam } 769178354Ssam if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { 770178354Ssam IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, 771178354Ssam ni->ni_macaddr, "mgt", "too short: len %u", 772178354Ssam m->m_pkthdr.len); 773178354Ssam vap->iv_stats.is_rx_tooshort++; 774178354Ssam goto out; 775178354Ssam } 776178354Ssam#ifdef IEEE80211_DEBUG 777178354Ssam if (ieee80211_msg_debug(vap) || ieee80211_msg_dumppkts(vap)) { 778178354Ssam if_printf(ifp, "received %s from %s rssi %d\n", 779178354Ssam ieee80211_mgt_subtype_name[subtype >> 780178354Ssam IEEE80211_FC0_SUBTYPE_SHIFT], 781178354Ssam ether_sprintf(wh->i_addr2), rssi); 782178354Ssam } 783178354Ssam#endif 784178354Ssam if (wh->i_fc[1] & IEEE80211_FC1_WEP) { 785178354Ssam IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, 786178354Ssam wh, NULL, "%s", "WEP set but not permitted"); 787178354Ssam vap->iv_stats.is_rx_mgtdiscard++; /* XXX */ 788178354Ssam goto out; 789178354Ssam } 790178354Ssam if (bpf_peers_present(vap->iv_rawbpf)) 791178354Ssam bpf_mtap(vap->iv_rawbpf, m); 792178354Ssam vap->iv_recv_mgmt(ni, m, subtype, rssi, noise, rstamp); 793178354Ssam m_freem(m); 794178354Ssam return IEEE80211_FC0_TYPE_MGT; 795178354Ssam 796178354Ssam case IEEE80211_FC0_TYPE_CTL: 797178354Ssam vap->iv_stats.is_rx_ctl++; 798178354Ssam IEEE80211_NODE_STAT(ni, rx_ctrl); 799178354Ssam goto out; 800178354Ssam default: 801178354Ssam IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, 802178354Ssam wh, "bad", "frame type 0x%x", type); 803178354Ssam /* should not come here */ 804178354Ssam break; 805178354Ssam } 806178354Ssamerr: 807178354Ssam ifp->if_ierrors++; 808178354Ssamout: 809178354Ssam if (m != NULL) { 810178354Ssam if (bpf_peers_present(vap->iv_rawbpf) && need_tap) 811178354Ssam bpf_mtap(vap->iv_rawbpf, m); 812178354Ssam m_freem(m); 813178354Ssam } 814178354Ssam return type; 815178354Ssam#undef SEQ_LEQ 816178354Ssam} 817178354Ssam 818178354Ssamstatic void 819178354Ssamwds_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, 820178354Ssam int subtype, int rssi, int noise, u_int32_t rstamp) 821178354Ssam{ 822178354Ssam struct ieee80211vap *vap = ni->ni_vap; 823178354Ssam struct ieee80211com *ic = ni->ni_ic; 824178354Ssam struct ieee80211_frame *wh; 825178354Ssam u_int8_t *frm, *efrm; 826178354Ssam 827178354Ssam wh = mtod(m0, struct ieee80211_frame *); 828178354Ssam frm = (u_int8_t *)&wh[1]; 829178354Ssam efrm = mtod(m0, u_int8_t *) + m0->m_len; 830178354Ssam switch (subtype) { 831178354Ssam case IEEE80211_FC0_SUBTYPE_DEAUTH: 832178354Ssam case IEEE80211_FC0_SUBTYPE_PROBE_RESP: 833178354Ssam case IEEE80211_FC0_SUBTYPE_BEACON: 834178354Ssam case IEEE80211_FC0_SUBTYPE_PROBE_REQ: 835178354Ssam case IEEE80211_FC0_SUBTYPE_AUTH: 836178354Ssam case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: 837178354Ssam case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: 838178354Ssam case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: 839178354Ssam case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: 840178354Ssam case IEEE80211_FC0_SUBTYPE_DISASSOC: 841178354Ssam vap->iv_stats.is_rx_mgtdiscard++; 842178354Ssam break; 843178354Ssam case IEEE80211_FC0_SUBTYPE_ACTION: 844178354Ssam if (vap->iv_state != IEEE80211_S_RUN || 845178354Ssam IEEE80211_IS_MULTICAST(wh->i_addr1)) { 846178354Ssam vap->iv_stats.is_rx_mgtdiscard++; 847178354Ssam break; 848178354Ssam } 849178354Ssam ni->ni_inact = ni->ni_inact_reload; 850178354Ssam if (ieee80211_parse_action(ni, m0) == 0) 851178354Ssam ic->ic_recv_action(ni, frm, efrm); 852178354Ssam break; 853178354Ssam default: 854178354Ssam IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, 855178354Ssam wh, "mgt", "subtype 0x%x not handled", subtype); 856178354Ssam vap->iv_stats.is_rx_badsubtype++; 857178354Ssam break; 858178354Ssam } 859178354Ssam} 860