diff --git a/sys/net80211/ieee80211.c b/sys/net80211/ieee80211.c index 44d6a3294bdc..65ecb5d77607 100644 --- a/sys/net80211/ieee80211.c +++ b/sys/net80211/ieee80211.c @@ -1,2776 +1,2776 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 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 /* * IEEE 802.11 generic handler */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #include #include #include const char *ieee80211_phymode_name[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = "auto", [IEEE80211_MODE_11A] = "11a", [IEEE80211_MODE_11B] = "11b", [IEEE80211_MODE_11G] = "11g", [IEEE80211_MODE_FH] = "FH", [IEEE80211_MODE_TURBO_A] = "turboA", [IEEE80211_MODE_TURBO_G] = "turboG", [IEEE80211_MODE_STURBO_A] = "sturboA", [IEEE80211_MODE_HALF] = "half", [IEEE80211_MODE_QUARTER] = "quarter", [IEEE80211_MODE_11NA] = "11na", [IEEE80211_MODE_11NG] = "11ng", [IEEE80211_MODE_VHT_2GHZ] = "11acg", [IEEE80211_MODE_VHT_5GHZ] = "11ac", }; /* map ieee80211_opmode to the corresponding capability bit */ const int ieee80211_opcap[IEEE80211_OPMODE_MAX] = { [IEEE80211_M_IBSS] = IEEE80211_C_IBSS, [IEEE80211_M_WDS] = IEEE80211_C_WDS, [IEEE80211_M_STA] = IEEE80211_C_STA, [IEEE80211_M_AHDEMO] = IEEE80211_C_AHDEMO, [IEEE80211_M_HOSTAP] = IEEE80211_C_HOSTAP, [IEEE80211_M_MONITOR] = IEEE80211_C_MONITOR, #ifdef IEEE80211_SUPPORT_MESH [IEEE80211_M_MBSS] = IEEE80211_C_MBSS, #endif }; const uint8_t ieee80211broadcastaddr[IEEE80211_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; static void ieee80211_syncflag_locked(struct ieee80211com *ic, int flag); static void ieee80211_syncflag_ht_locked(struct ieee80211com *ic, int flag); static void ieee80211_syncflag_ext_locked(struct ieee80211com *ic, int flag); static void ieee80211_syncflag_vht_locked(struct ieee80211com *ic, int flag); static int ieee80211_media_setup(struct ieee80211com *ic, struct ifmedia *media, int caps, int addsta, ifm_change_cb_t media_change, ifm_stat_cb_t media_stat); static int media_status(enum ieee80211_opmode, const struct ieee80211_channel *); static uint64_t ieee80211_get_counter(struct ifnet *, ift_counter); MALLOC_DEFINE(M_80211_VAP, "80211vap", "802.11 vap state"); /* * Default supported rates for 802.11 operation (in IEEE .5Mb units). */ #define B(r) ((r) | IEEE80211_RATE_BASIC) static const struct ieee80211_rateset ieee80211_rateset_11a = { 8, { B(12), 18, B(24), 36, B(48), 72, 96, 108 } }; static const struct ieee80211_rateset ieee80211_rateset_half = { 8, { B(6), 9, B(12), 18, B(24), 36, 48, 54 } }; static const struct ieee80211_rateset ieee80211_rateset_quarter = { 8, { B(3), 4, B(6), 9, B(12), 18, 24, 27 } }; static const struct ieee80211_rateset ieee80211_rateset_11b = { 4, { B(2), B(4), B(11), B(22) } }; /* NB: OFDM rates are handled specially based on mode */ static const struct ieee80211_rateset ieee80211_rateset_11g = { 12, { B(2), B(4), B(11), B(22), 12, 18, 24, 36, 48, 72, 96, 108 } }; #undef B static int set_vht_extchan(struct ieee80211_channel *c); /* * Fill in 802.11 available channel set, mark * all available channels as active, and pick * a default channel if not already specified. */ void ieee80211_chan_init(struct ieee80211com *ic) { #define DEFAULTRATES(m, def) do { \ if (ic->ic_sup_rates[m].rs_nrates == 0) \ ic->ic_sup_rates[m] = def; \ } while (0) struct ieee80211_channel *c; int i; KASSERT(0 < ic->ic_nchans && ic->ic_nchans <= IEEE80211_CHAN_MAX, ("invalid number of channels specified: %u", ic->ic_nchans)); memset(ic->ic_chan_avail, 0, sizeof(ic->ic_chan_avail)); memset(ic->ic_modecaps, 0, sizeof(ic->ic_modecaps)); setbit(ic->ic_modecaps, IEEE80211_MODE_AUTO); for (i = 0; i < ic->ic_nchans; i++) { c = &ic->ic_channels[i]; KASSERT(c->ic_flags != 0, ("channel with no flags")); /* * Help drivers that work only with frequencies by filling * in IEEE channel #'s if not already calculated. Note this * mimics similar work done in ieee80211_setregdomain when * changing regulatory state. */ if (c->ic_ieee == 0) c->ic_ieee = ieee80211_mhz2ieee(c->ic_freq,c->ic_flags); /* * Setup the HT40/VHT40 upper/lower bits. * The VHT80/... math is done elsewhere. */ if (IEEE80211_IS_CHAN_HT40(c) && c->ic_extieee == 0) c->ic_extieee = ieee80211_mhz2ieee(c->ic_freq + (IEEE80211_IS_CHAN_HT40U(c) ? 20 : -20), c->ic_flags); /* Update VHT math */ /* * XXX VHT again, note that this assumes VHT80/... channels * are legit already. */ set_vht_extchan(c); /* default max tx power to max regulatory */ if (c->ic_maxpower == 0) c->ic_maxpower = 2*c->ic_maxregpower; setbit(ic->ic_chan_avail, c->ic_ieee); /* * Identify mode capabilities. */ if (IEEE80211_IS_CHAN_A(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11A); if (IEEE80211_IS_CHAN_B(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11B); if (IEEE80211_IS_CHAN_ANYG(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11G); if (IEEE80211_IS_CHAN_FHSS(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_FH); if (IEEE80211_IS_CHAN_108A(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_TURBO_A); if (IEEE80211_IS_CHAN_108G(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_TURBO_G); if (IEEE80211_IS_CHAN_ST(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_STURBO_A); if (IEEE80211_IS_CHAN_HALF(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_HALF); if (IEEE80211_IS_CHAN_QUARTER(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_QUARTER); if (IEEE80211_IS_CHAN_HTA(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11NA); if (IEEE80211_IS_CHAN_HTG(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11NG); if (IEEE80211_IS_CHAN_VHTA(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_VHT_5GHZ); if (IEEE80211_IS_CHAN_VHTG(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_VHT_2GHZ); } /* initialize candidate channels to all available */ memcpy(ic->ic_chan_active, ic->ic_chan_avail, sizeof(ic->ic_chan_avail)); /* sort channel table to allow lookup optimizations */ ieee80211_sort_channels(ic->ic_channels, ic->ic_nchans); /* invalidate any previous state */ ic->ic_bsschan = IEEE80211_CHAN_ANYC; ic->ic_prevchan = NULL; ic->ic_csa_newchan = NULL; /* arbitrarily pick the first channel */ ic->ic_curchan = &ic->ic_channels[0]; ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); /* fillin well-known rate sets if driver has not specified */ DEFAULTRATES(IEEE80211_MODE_11B, ieee80211_rateset_11b); DEFAULTRATES(IEEE80211_MODE_11G, ieee80211_rateset_11g); DEFAULTRATES(IEEE80211_MODE_11A, ieee80211_rateset_11a); DEFAULTRATES(IEEE80211_MODE_TURBO_A, ieee80211_rateset_11a); DEFAULTRATES(IEEE80211_MODE_TURBO_G, ieee80211_rateset_11g); DEFAULTRATES(IEEE80211_MODE_STURBO_A, ieee80211_rateset_11a); DEFAULTRATES(IEEE80211_MODE_HALF, ieee80211_rateset_half); DEFAULTRATES(IEEE80211_MODE_QUARTER, ieee80211_rateset_quarter); DEFAULTRATES(IEEE80211_MODE_11NA, ieee80211_rateset_11a); DEFAULTRATES(IEEE80211_MODE_11NG, ieee80211_rateset_11g); DEFAULTRATES(IEEE80211_MODE_VHT_2GHZ, ieee80211_rateset_11g); DEFAULTRATES(IEEE80211_MODE_VHT_5GHZ, ieee80211_rateset_11a); /* * Setup required information to fill the mcsset field, if driver did * not. Assume a 2T2R setup for historic reasons. */ if (ic->ic_rxstream == 0) ic->ic_rxstream = 2; if (ic->ic_txstream == 0) ic->ic_txstream = 2; ieee80211_init_suphtrates(ic); /* * Set auto mode to reset active channel state and any desired channel. */ (void) ieee80211_setmode(ic, IEEE80211_MODE_AUTO); #undef DEFAULTRATES } static void null_update_mcast(struct ieee80211com *ic) { ic_printf(ic, "need multicast update callback\n"); } static void null_update_promisc(struct ieee80211com *ic) { ic_printf(ic, "need promiscuous mode update callback\n"); } static void null_update_chw(struct ieee80211com *ic) { ic_printf(ic, "%s: need callback\n", __func__); } static LIST_HEAD(, ieee80211com) ic_head = LIST_HEAD_INITIALIZER(ic_head); static struct mtx ic_list_mtx; MTX_SYSINIT(ic_list, &ic_list_mtx, "ieee80211com list", MTX_DEF); static int sysctl_ieee80211coms(SYSCTL_HANDLER_ARGS) { struct ieee80211com *ic; struct sbuf sb; char *sp; int error; error = sysctl_wire_old_buffer(req, 0); if (error) return (error); sbuf_new_for_sysctl(&sb, NULL, 8, req); sbuf_clear_flags(&sb, SBUF_INCLUDENUL); sp = ""; mtx_lock(&ic_list_mtx); LIST_FOREACH(ic, &ic_head, ic_next) { sbuf_printf(&sb, "%s%s", sp, ic->ic_name); sp = " "; } mtx_unlock(&ic_list_mtx); error = sbuf_finish(&sb); sbuf_delete(&sb); return (error); } SYSCTL_PROC(_net_wlan, OID_AUTO, devices, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, 0, sysctl_ieee80211coms, "A", "names of available 802.11 devices"); /* * Attach/setup the common net80211 state. Called by * the driver on attach to prior to creating any vap's. */ void ieee80211_ifattach(struct ieee80211com *ic) { IEEE80211_LOCK_INIT(ic, ic->ic_name); IEEE80211_TX_LOCK_INIT(ic, ic->ic_name); TAILQ_INIT(&ic->ic_vaps); /* Create a taskqueue for all state changes */ ic->ic_tq = taskqueue_create("ic_taskq", IEEE80211_M_WAITOK | IEEE80211_M_ZERO, taskqueue_thread_enqueue, &ic->ic_tq); taskqueue_start_threads(&ic->ic_tq, 1, PI_NET, "%s net80211 taskq", ic->ic_name); ic->ic_ierrors = counter_u64_alloc(IEEE80211_M_WAITOK); ic->ic_oerrors = counter_u64_alloc(IEEE80211_M_WAITOK); /* * Fill in 802.11 available channel set, mark all * available channels as active, and pick a default * channel if not already specified. */ ieee80211_chan_init(ic); ic->ic_update_mcast = null_update_mcast; ic->ic_update_promisc = null_update_promisc; ic->ic_update_chw = null_update_chw; ic->ic_hash_key = arc4random(); ic->ic_bintval = IEEE80211_BINTVAL_DEFAULT; ic->ic_lintval = ic->ic_bintval; ic->ic_txpowlimit = IEEE80211_TXPOWER_MAX; ieee80211_crypto_attach(ic); ieee80211_node_attach(ic); ieee80211_power_attach(ic); ieee80211_proto_attach(ic); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_attach(ic); #endif ieee80211_ht_attach(ic); ieee80211_vht_attach(ic); ieee80211_scan_attach(ic); ieee80211_regdomain_attach(ic); ieee80211_dfs_attach(ic); ieee80211_sysctl_attach(ic); mtx_lock(&ic_list_mtx); LIST_INSERT_HEAD(&ic_head, ic, ic_next); mtx_unlock(&ic_list_mtx); } /* * Detach net80211 state on device detach. Tear down * all vap's and reclaim all common state prior to the * device state going away. Note we may call back into * driver; it must be prepared for this. */ void ieee80211_ifdetach(struct ieee80211com *ic) { struct ieee80211vap *vap; /* * We use this as an indicator that ifattach never had a chance to be * called, e.g. early driver attach failed and ifdetach was called * during subsequent detach. Never fear, for we have nothing to do * here. */ if (ic->ic_tq == NULL) return; mtx_lock(&ic_list_mtx); LIST_REMOVE(ic, ic_next); mtx_unlock(&ic_list_mtx); taskqueue_drain(taskqueue_thread, &ic->ic_restart_task); /* * The VAP is responsible for setting and clearing * the VIMAGE context. */ while ((vap = TAILQ_FIRST(&ic->ic_vaps)) != NULL) { ieee80211_com_vdetach(vap); ieee80211_vap_destroy(vap); } ieee80211_waitfor_parent(ic); ieee80211_sysctl_detach(ic); ieee80211_dfs_detach(ic); ieee80211_regdomain_detach(ic); ieee80211_scan_detach(ic); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_detach(ic); #endif ieee80211_vht_detach(ic); ieee80211_ht_detach(ic); /* NB: must be called before ieee80211_node_detach */ ieee80211_proto_detach(ic); ieee80211_crypto_detach(ic); ieee80211_power_detach(ic); ieee80211_node_detach(ic); counter_u64_free(ic->ic_ierrors); counter_u64_free(ic->ic_oerrors); taskqueue_free(ic->ic_tq); IEEE80211_TX_LOCK_DESTROY(ic); IEEE80211_LOCK_DESTROY(ic); } /* * Called by drivers during attach to set the supported * cipher set for software encryption. */ void ieee80211_set_software_ciphers(struct ieee80211com *ic, uint32_t cipher_suite) { ieee80211_crypto_set_supported_software_ciphers(ic, cipher_suite); } /* * Called by drivers during attach to set the supported * cipher set for hardware encryption. */ void ieee80211_set_hardware_ciphers(struct ieee80211com *ic, uint32_t cipher_suite) { ieee80211_crypto_set_supported_hardware_ciphers(ic, cipher_suite); } /* * Called by drivers during attach to set the supported * key management suites by the driver/hardware. */ void ieee80211_set_driver_keymgmt_suites(struct ieee80211com *ic, uint32_t keymgmt_set) { ieee80211_crypto_set_supported_driver_keymgmt(ic, keymgmt_set); } struct ieee80211com * ieee80211_find_com(const char *name) { struct ieee80211com *ic; mtx_lock(&ic_list_mtx); LIST_FOREACH(ic, &ic_head, ic_next) if (strcmp(ic->ic_name, name) == 0) break; mtx_unlock(&ic_list_mtx); return (ic); } void ieee80211_iterate_coms(ieee80211_com_iter_func *f, void *arg) { struct ieee80211com *ic; mtx_lock(&ic_list_mtx); LIST_FOREACH(ic, &ic_head, ic_next) (*f)(arg, ic); mtx_unlock(&ic_list_mtx); } /* * Default reset method for use with the ioctl support. This * method is invoked after any state change in the 802.11 * layer that should be propagated to the hardware but not * require re-initialization of the 802.11 state machine (e.g * rescanning for an ap). We always return ENETRESET which * should cause the driver to re-initialize the device. Drivers * can override this method to implement more optimized support. */ static int default_reset(struct ieee80211vap *vap, u_long cmd) { return ENETRESET; } /* * Default for updating the VAP default TX key index. * * Drivers that support TX offload as well as hardware encryption offload * may need to be informed of key index changes separate from the key * update. */ static void default_update_deftxkey(struct ieee80211vap *vap, ieee80211_keyix kid) { /* XXX assert validity */ /* XXX assert we're in a key update block */ vap->iv_def_txkey = kid; } /* * Add underlying device errors to vap errors. */ static uint64_t ieee80211_get_counter(struct ifnet *ifp, ift_counter cnt) { struct ieee80211vap *vap = ifp->if_softc; struct ieee80211com *ic = vap->iv_ic; uint64_t rv; rv = if_get_counter_default(ifp, cnt); switch (cnt) { case IFCOUNTER_OERRORS: rv += counter_u64_fetch(ic->ic_oerrors); break; case IFCOUNTER_IERRORS: rv += counter_u64_fetch(ic->ic_ierrors); break; default: break; } return (rv); } /* * Prepare a vap for use. Drivers use this call to * setup net80211 state in new vap's prior attaching * them with ieee80211_vap_attach (below). */ int ieee80211_vap_setup(struct ieee80211com *ic, struct ieee80211vap *vap, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN]) { struct ifnet *ifp; ifp = if_alloc(IFT_ETHER); if_initname(ifp, name, unit); ifp->if_softc = vap; /* back pointer */ if_setflags(ifp, IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST); ifp->if_transmit = ieee80211_vap_transmit; ifp->if_qflush = ieee80211_vap_qflush; ifp->if_ioctl = ieee80211_ioctl; ifp->if_init = ieee80211_init; ifp->if_get_counter = ieee80211_get_counter; vap->iv_ifp = ifp; vap->iv_ic = ic; vap->iv_flags = ic->ic_flags; /* propagate common flags */ vap->iv_flags_ext = ic->ic_flags_ext; vap->iv_flags_ven = ic->ic_flags_ven; vap->iv_caps = ic->ic_caps &~ IEEE80211_C_OPMODE; /* 11n capabilities - XXX methodize */ vap->iv_htcaps = ic->ic_htcaps; vap->iv_htextcaps = ic->ic_htextcaps; /* 11ac capabilities - XXX methodize */ vap->iv_vht_cap.vht_cap_info = ic->ic_vht_cap.vht_cap_info; vap->iv_vhtextcaps = ic->ic_vhtextcaps; vap->iv_opmode = opmode; vap->iv_caps |= ieee80211_opcap[opmode]; IEEE80211_ADDR_COPY(vap->iv_myaddr, ic->ic_macaddr); switch (opmode) { case IEEE80211_M_WDS: /* * WDS links must specify the bssid of the far end. * For legacy operation this is a static relationship. * For non-legacy operation the station must associate * and be authorized to pass traffic. Plumbing the * vap to the proper node happens when the vap * transitions to RUN state. */ IEEE80211_ADDR_COPY(vap->iv_des_bssid, bssid); vap->iv_flags |= IEEE80211_F_DESBSSID; if (flags & IEEE80211_CLONE_WDSLEGACY) vap->iv_flags_ext |= IEEE80211_FEXT_WDSLEGACY; break; #ifdef IEEE80211_SUPPORT_TDMA case IEEE80211_M_AHDEMO: if (flags & IEEE80211_CLONE_TDMA) { /* NB: checked before clone operation allowed */ KASSERT(ic->ic_caps & IEEE80211_C_TDMA, ("not TDMA capable, ic_caps 0x%x", ic->ic_caps)); /* * Propagate TDMA capability to mark vap; this * cannot be removed and is used to distinguish * regular ahdemo operation from ahdemo+tdma. */ vap->iv_caps |= IEEE80211_C_TDMA; } break; #endif default: break; } /* auto-enable s/w beacon miss support */ if (flags & IEEE80211_CLONE_NOBEACONS) vap->iv_flags_ext |= IEEE80211_FEXT_SWBMISS; /* auto-generated or user supplied MAC address */ if (flags & (IEEE80211_CLONE_BSSID|IEEE80211_CLONE_MACADDR)) vap->iv_flags_ext |= IEEE80211_FEXT_UNIQMAC; /* * Enable various functionality by default if we're * capable; the driver can override us if it knows better. */ if (vap->iv_caps & IEEE80211_C_WME) vap->iv_flags |= IEEE80211_F_WME; if (vap->iv_caps & IEEE80211_C_BURST) vap->iv_flags |= IEEE80211_F_BURST; /* NB: bg scanning only makes sense for station mode right now */ if (vap->iv_opmode == IEEE80211_M_STA && (vap->iv_caps & IEEE80211_C_BGSCAN)) vap->iv_flags |= IEEE80211_F_BGSCAN; vap->iv_flags |= IEEE80211_F_DOTH; /* XXX no cap, just ena */ /* NB: DFS support only makes sense for ap mode right now */ if (vap->iv_opmode == IEEE80211_M_HOSTAP && (vap->iv_caps & IEEE80211_C_DFS)) vap->iv_flags_ext |= IEEE80211_FEXT_DFS; /* NB: only flip on U-APSD for hostap/sta for now */ if ((vap->iv_opmode == IEEE80211_M_STA) || (vap->iv_opmode == IEEE80211_M_HOSTAP)) { if (vap->iv_caps & IEEE80211_C_UAPSD) vap->iv_flags_ext |= IEEE80211_FEXT_UAPSD; } vap->iv_des_chan = IEEE80211_CHAN_ANYC; /* any channel is ok */ vap->iv_bmissthreshold = IEEE80211_HWBMISS_DEFAULT; vap->iv_dtim_period = IEEE80211_DTIM_DEFAULT; /* * Install a default reset method for the ioctl support; * the driver can override this. */ vap->iv_reset = default_reset; /* * Install a default crypto key update method, the driver * can override this. */ vap->iv_update_deftxkey = default_update_deftxkey; ieee80211_sysctl_vattach(vap); ieee80211_crypto_vattach(vap); ieee80211_node_vattach(vap); ieee80211_power_vattach(vap); ieee80211_proto_vattach(vap); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_vattach(vap); #endif ieee80211_ht_vattach(vap); ieee80211_vht_vattach(vap); ieee80211_scan_vattach(vap); ieee80211_regdomain_vattach(vap); ieee80211_radiotap_vattach(vap); ieee80211_vap_reset_erp(vap); ieee80211_ratectl_set(vap, IEEE80211_RATECTL_NONE); return 0; } /* * Activate a vap. State should have been prepared with a * call to ieee80211_vap_setup and by the driver. On return * from this call the vap is ready for use. */ int ieee80211_vap_attach(struct ieee80211vap *vap, ifm_change_cb_t media_change, ifm_stat_cb_t media_stat, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ifnet *ifp = vap->iv_ifp; struct ieee80211com *ic = vap->iv_ic; struct ifmediareq imr; int maxrate; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s parent %s flags 0x%x flags_ext 0x%x\n", __func__, ieee80211_opmode_name[vap->iv_opmode], ic->ic_name, vap->iv_flags, vap->iv_flags_ext); /* * Do late attach work that cannot happen until after * the driver has had a chance to override defaults. */ ieee80211_node_latevattach(vap); ieee80211_power_latevattach(vap); maxrate = ieee80211_media_setup(ic, &vap->iv_media, vap->iv_caps, vap->iv_opmode == IEEE80211_M_STA, media_change, media_stat); ieee80211_media_status(ifp, &imr); /* NB: strip explicit mode; we're actually in autoselect */ ifmedia_set(&vap->iv_media, imr.ifm_active &~ (IFM_MMASK | IFM_IEEE80211_TURBO)); if (maxrate) ifp->if_baudrate = IF_Mbps(maxrate); ether_ifattach(ifp, macaddr); /* Do initial MAC address sync */ ieee80211_vap_copy_mac_address(vap); /* hook output method setup by ether_ifattach */ vap->iv_output = ifp->if_output; ifp->if_output = ieee80211_output; /* NB: if_mtu set by ether_ifattach to ETHERMTU */ IEEE80211_LOCK(ic); TAILQ_INSERT_TAIL(&ic->ic_vaps, vap, iv_next); ieee80211_syncflag_locked(ic, IEEE80211_F_WME); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_syncflag_locked(ic, IEEE80211_F_TURBOP); #endif ieee80211_syncflag_locked(ic, IEEE80211_F_PCF); ieee80211_syncflag_locked(ic, IEEE80211_F_BURST); ieee80211_syncflag_ht_locked(ic, IEEE80211_FHT_HT); ieee80211_syncflag_ht_locked(ic, IEEE80211_FHT_USEHT40); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_VHT); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT40); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT80); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT160); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT80P80); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_STBC_TX); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_STBC_RX); IEEE80211_UNLOCK(ic); return 1; } /* * Tear down vap state and reclaim the ifnet. * The driver is assumed to have prepared for * this; e.g. by turning off interrupts for the * underlying device. */ void ieee80211_vap_detach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ifnet *ifp = vap->iv_ifp; int i; CURVNET_SET(ifp->if_vnet); IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s parent %s\n", __func__, ieee80211_opmode_name[vap->iv_opmode], ic->ic_name); /* NB: bpfdetach is called by ether_ifdetach and claims all taps */ ether_ifdetach(ifp); ieee80211_stop(vap); /* * Flush any deferred vap tasks. */ for (i = 0; i < NET80211_IV_NSTATE_NUM; i++) ieee80211_draintask(ic, &vap->iv_nstate_task[i]); ieee80211_draintask(ic, &vap->iv_swbmiss_task); ieee80211_draintask(ic, &vap->iv_wme_task); ieee80211_draintask(ic, &ic->ic_parent_task); /* XXX band-aid until ifnet handles this for us */ taskqueue_drain(taskqueue_swi, &ifp->if_linktask); IEEE80211_LOCK(ic); KASSERT(vap->iv_state == IEEE80211_S_INIT , ("vap still running")); TAILQ_REMOVE(&ic->ic_vaps, vap, iv_next); ieee80211_syncflag_locked(ic, IEEE80211_F_WME); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_syncflag_locked(ic, IEEE80211_F_TURBOP); #endif ieee80211_syncflag_locked(ic, IEEE80211_F_PCF); ieee80211_syncflag_locked(ic, IEEE80211_F_BURST); ieee80211_syncflag_ht_locked(ic, IEEE80211_FHT_HT); ieee80211_syncflag_ht_locked(ic, IEEE80211_FHT_USEHT40); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_VHT); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT40); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT80); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT160); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT80P80); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_STBC_TX); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_STBC_RX); /* NB: this handles the bpfdetach done below */ ieee80211_syncflag_ext_locked(ic, IEEE80211_FEXT_BPF); if (vap->iv_ifflags & IFF_PROMISC) ieee80211_promisc(vap, false); if (vap->iv_ifflags & IFF_ALLMULTI) ieee80211_allmulti(vap, false); IEEE80211_UNLOCK(ic); ifmedia_removeall(&vap->iv_media); ieee80211_radiotap_vdetach(vap); ieee80211_regdomain_vdetach(vap); ieee80211_scan_vdetach(vap); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_vdetach(vap); #endif ieee80211_vht_vdetach(vap); ieee80211_ht_vdetach(vap); /* NB: must be before ieee80211_node_vdetach */ ieee80211_proto_vdetach(vap); ieee80211_crypto_vdetach(vap); ieee80211_power_vdetach(vap); ieee80211_node_vdetach(vap); ieee80211_sysctl_vdetach(vap); if_free(ifp); CURVNET_RESTORE(); } /* * Count number of vaps in promisc, and issue promisc on * parent respectively. */ void ieee80211_promisc(struct ieee80211vap *vap, bool on) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK_ASSERT(ic); if (on) { if (++ic->ic_promisc == 1) ieee80211_runtask(ic, &ic->ic_promisc_task); } else { KASSERT(ic->ic_promisc > 0, ("%s: ic %p not promisc", __func__, ic)); if (--ic->ic_promisc == 0) ieee80211_runtask(ic, &ic->ic_promisc_task); } } /* * Count number of vaps in allmulti, and issue allmulti on * parent respectively. */ void ieee80211_allmulti(struct ieee80211vap *vap, bool on) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK_ASSERT(ic); if (on) { if (++ic->ic_allmulti == 1) ieee80211_runtask(ic, &ic->ic_mcast_task); } else { KASSERT(ic->ic_allmulti > 0, ("%s: ic %p not allmulti", __func__, ic)); if (--ic->ic_allmulti == 0) ieee80211_runtask(ic, &ic->ic_mcast_task); } } /* * Synchronize flag bit state in the com structure * according to the state of all vap's. This is used, * for example, to handle state changes via ioctls. */ static void ieee80211_syncflag_locked(struct ieee80211com *ic, int flag) { struct ieee80211vap *vap; int bit; IEEE80211_LOCK_ASSERT(ic); bit = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_flags & flag) { bit = 1; break; } if (bit) ic->ic_flags |= flag; else ic->ic_flags &= ~flag; } void ieee80211_syncflag(struct ieee80211vap *vap, int flag) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); if (flag < 0) { flag = -flag; vap->iv_flags &= ~flag; } else vap->iv_flags |= flag; ieee80211_syncflag_locked(ic, flag); IEEE80211_UNLOCK(ic); } /* * Synchronize flags_ht bit state in the com structure * according to the state of all vap's. This is used, * for example, to handle state changes via ioctls. */ static void ieee80211_syncflag_ht_locked(struct ieee80211com *ic, int flag) { struct ieee80211vap *vap; int bit; IEEE80211_LOCK_ASSERT(ic); bit = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_flags_ht & flag) { bit = 1; break; } if (bit) ic->ic_flags_ht |= flag; else ic->ic_flags_ht &= ~flag; } void ieee80211_syncflag_ht(struct ieee80211vap *vap, int flag) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); if (flag < 0) { flag = -flag; vap->iv_flags_ht &= ~flag; } else vap->iv_flags_ht |= flag; ieee80211_syncflag_ht_locked(ic, flag); IEEE80211_UNLOCK(ic); } /* * Synchronize flags_vht bit state in the com structure * according to the state of all vap's. This is used, * for example, to handle state changes via ioctls. */ static void ieee80211_syncflag_vht_locked(struct ieee80211com *ic, int flag) { struct ieee80211vap *vap; int bit; IEEE80211_LOCK_ASSERT(ic); bit = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_vht_flags & flag) { bit = 1; break; } if (bit) ic->ic_vht_flags |= flag; else ic->ic_vht_flags &= ~flag; } void ieee80211_syncflag_vht(struct ieee80211vap *vap, int flag) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); if (flag < 0) { flag = -flag; vap->iv_vht_flags &= ~flag; } else vap->iv_vht_flags |= flag; ieee80211_syncflag_vht_locked(ic, flag); IEEE80211_UNLOCK(ic); } /* * Synchronize flags_ext bit state in the com structure * according to the state of all vap's. This is used, * for example, to handle state changes via ioctls. */ static void ieee80211_syncflag_ext_locked(struct ieee80211com *ic, int flag) { struct ieee80211vap *vap; int bit; IEEE80211_LOCK_ASSERT(ic); bit = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_flags_ext & flag) { bit = 1; break; } if (bit) ic->ic_flags_ext |= flag; else ic->ic_flags_ext &= ~flag; } void ieee80211_syncflag_ext(struct ieee80211vap *vap, int flag) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); if (flag < 0) { flag = -flag; vap->iv_flags_ext &= ~flag; } else vap->iv_flags_ext |= flag; ieee80211_syncflag_ext_locked(ic, flag); IEEE80211_UNLOCK(ic); } static __inline int mapgsm(u_int freq, u_int flags) { freq *= 10; if (flags & IEEE80211_CHAN_QUARTER) freq += 5; else if (flags & IEEE80211_CHAN_HALF) freq += 10; else freq += 20; /* NB: there is no 907/20 wide but leave room */ return (freq - 906*10) / 5; } static __inline int mappsb(u_int freq, u_int flags) { return 37 + ((freq * 10) + ((freq % 5) == 2 ? 5 : 0) - 49400) / 5; } /* * Convert MHz frequency to IEEE channel number. */ int ieee80211_mhz2ieee(u_int freq, u_int flags) { #define IS_FREQ_IN_PSB(_freq) ((_freq) > 4940 && (_freq) < 4990) if (flags & IEEE80211_CHAN_GSM) return mapgsm(freq, flags); if (flags & IEEE80211_CHAN_2GHZ) { /* 2GHz band */ if (freq == 2484) return 14; if (freq < 2484) return ((int) freq - 2407) / 5; else return 15 + ((freq - 2512) / 20); } else if (flags & IEEE80211_CHAN_5GHZ) { /* 5Ghz band */ if (freq <= 5000) { /* XXX check regdomain? */ if (IS_FREQ_IN_PSB(freq)) return mappsb(freq, flags); return (freq - 4000) / 5; } else return (freq - 5000) / 5; } else { /* either, guess */ if (freq == 2484) return 14; if (freq < 2484) { if (907 <= freq && freq <= 922) return mapgsm(freq, flags); return ((int) freq - 2407) / 5; } if (freq < 5000) { if (IS_FREQ_IN_PSB(freq)) return mappsb(freq, flags); else if (freq > 4900) return (freq - 4000) / 5; else return 15 + ((freq - 2512) / 20); } return (freq - 5000) / 5; } #undef IS_FREQ_IN_PSB } /* * Convert channel to IEEE channel number. */ int ieee80211_chan2ieee(struct ieee80211com *ic, const struct ieee80211_channel *c) { if (c == NULL) { ic_printf(ic, "invalid channel (NULL)\n"); return 0; /* XXX */ } return (c == IEEE80211_CHAN_ANYC ? IEEE80211_CHAN_ANY : c->ic_ieee); } /* * Convert IEEE channel number to MHz frequency. */ u_int ieee80211_ieee2mhz(u_int chan, u_int flags) { if (flags & IEEE80211_CHAN_GSM) return 907 + 5 * (chan / 10); if (flags & IEEE80211_CHAN_2GHZ) { /* 2GHz band */ if (chan == 14) return 2484; if (chan < 14) return 2407 + chan*5; else return 2512 + ((chan-15)*20); } else if (flags & IEEE80211_CHAN_5GHZ) {/* 5Ghz band */ if (flags & (IEEE80211_CHAN_HALF|IEEE80211_CHAN_QUARTER)) { chan -= 37; return 4940 + chan*5 + (chan % 5 ? 2 : 0); } return 5000 + (chan*5); } else { /* either, guess */ /* XXX can't distinguish PSB+GSM channels */ if (chan == 14) return 2484; if (chan < 14) /* 0-13 */ return 2407 + chan*5; if (chan < 27) /* 15-26 */ return 2512 + ((chan-15)*20); return 5000 + (chan*5); } } static __inline void set_extchan(struct ieee80211_channel *c) { /* * IEEE Std 802.11-2012, page 1738, subclause 20.3.15.4: * "the secondary channel number shall be 'N + [1,-1] * 4' */ if (c->ic_flags & IEEE80211_CHAN_HT40U) c->ic_extieee = c->ic_ieee + 4; else if (c->ic_flags & IEEE80211_CHAN_HT40D) c->ic_extieee = c->ic_ieee - 4; else c->ic_extieee = 0; } /* * Populate the freq1/freq2 fields as appropriate for VHT channels. * * This for now uses a hard-coded list of 80MHz wide channels. * * For HT20/HT40, freq1 just is the centre frequency of the 40MHz * wide channel we've already decided upon. * * For VHT80 and VHT160, there are only a small number of fixed * 80/160MHz wide channels, so we just use those. * * This is all likely very very wrong - both the regulatory code * and this code needs to ensure that all four channels are * available and valid before the VHT80 (and eight for VHT160) channel * is created. */ struct vht_chan_range { uint16_t freq_start; uint16_t freq_end; }; struct vht_chan_range vht80_chan_ranges[] = { { 5170, 5250 }, { 5250, 5330 }, { 5490, 5570 }, { 5570, 5650 }, { 5650, 5730 }, { 5735, 5815 }, { 5815, 5895 }, { 0, 0 } }; struct vht_chan_range vht160_chan_ranges[] = { { 5170, 5330 }, { 5490, 5650 }, { 5735, 5895 }, { 0, 0 } }; static int set_vht_extchan(struct ieee80211_channel *c) { int i; if (! IEEE80211_IS_CHAN_VHT(c)) return (0); if (IEEE80211_IS_CHAN_VHT80P80(c)) { - printf("%s: TODO VHT80+80 channel (ieee=%d, flags=0x%08x)\n", + net80211_printf("%s: TODO VHT80+80 channel (ieee=%d, flags=0x%08x)\n", __func__, c->ic_ieee, c->ic_flags); } if (IEEE80211_IS_CHAN_VHT160(c)) { for (i = 0; vht160_chan_ranges[i].freq_start != 0; i++) { if (c->ic_freq >= vht160_chan_ranges[i].freq_start && c->ic_freq < vht160_chan_ranges[i].freq_end) { int midpoint; midpoint = vht160_chan_ranges[i].freq_start + 80; c->ic_vht_ch_freq1 = ieee80211_mhz2ieee(midpoint, c->ic_flags); c->ic_vht_ch_freq2 = 0; #if 0 - printf("%s: %d, freq=%d, midpoint=%d, freq1=%d, freq2=%d\n", + net80211_printf("%s: %d, freq=%d, midpoint=%d, freq1=%d, freq2=%d\n", __func__, c->ic_ieee, c->ic_freq, midpoint, c->ic_vht_ch_freq1, c->ic_vht_ch_freq2); #endif return (1); } } return (0); } if (IEEE80211_IS_CHAN_VHT80(c)) { for (i = 0; vht80_chan_ranges[i].freq_start != 0; i++) { if (c->ic_freq >= vht80_chan_ranges[i].freq_start && c->ic_freq < vht80_chan_ranges[i].freq_end) { int midpoint; midpoint = vht80_chan_ranges[i].freq_start + 40; c->ic_vht_ch_freq1 = ieee80211_mhz2ieee(midpoint, c->ic_flags); c->ic_vht_ch_freq2 = 0; #if 0 - printf("%s: %d, freq=%d, midpoint=%d, freq1=%d, freq2=%d\n", + net80211_printf("%s: %d, freq=%d, midpoint=%d, freq1=%d, freq2=%d\n", __func__, c->ic_ieee, c->ic_freq, midpoint, c->ic_vht_ch_freq1, c->ic_vht_ch_freq2); #endif return (1); } } return (0); } if (IEEE80211_IS_CHAN_VHT40(c)) { if (IEEE80211_IS_CHAN_HT40U(c)) c->ic_vht_ch_freq1 = c->ic_ieee + 2; else if (IEEE80211_IS_CHAN_HT40D(c)) c->ic_vht_ch_freq1 = c->ic_ieee - 2; else return (0); return (1); } if (IEEE80211_IS_CHAN_VHT20(c)) { c->ic_vht_ch_freq1 = c->ic_ieee; return (1); } - printf("%s: unknown VHT channel type (ieee=%d, flags=0x%08x)\n", + net80211_printf("%s: unknown VHT channel type (ieee=%d, flags=0x%08x)\n", __func__, c->ic_ieee, c->ic_flags); return (0); } /* * Return whether the current channel could possibly be a part of * a VHT80/VHT160 channel. * * This doesn't check that the whole range is in the allowed list * according to regulatory. */ static bool is_vht160_valid_freq(uint16_t freq) { int i; for (i = 0; vht160_chan_ranges[i].freq_start != 0; i++) { if (freq >= vht160_chan_ranges[i].freq_start && freq < vht160_chan_ranges[i].freq_end) return (true); } return (false); } static int is_vht80_valid_freq(uint16_t freq) { int i; for (i = 0; vht80_chan_ranges[i].freq_start != 0; i++) { if (freq >= vht80_chan_ranges[i].freq_start && freq < vht80_chan_ranges[i].freq_end) return (1); } return (0); } static int addchan(struct ieee80211_channel chans[], int maxchans, int *nchans, uint8_t ieee, uint16_t freq, int8_t maxregpower, uint32_t flags) { struct ieee80211_channel *c; if (*nchans >= maxchans) return (ENOBUFS); #if 0 - printf("%s: %d of %d: ieee=%d, freq=%d, flags=0x%08x\n", + net80211_printf("%s: %d of %d: ieee=%d, freq=%d, flags=0x%08x\n", __func__, *nchans, maxchans, ieee, freq, flags); #endif c = &chans[(*nchans)++]; c->ic_ieee = ieee; c->ic_freq = freq != 0 ? freq : ieee80211_ieee2mhz(ieee, flags); c->ic_maxregpower = maxregpower; c->ic_maxpower = 2 * maxregpower; c->ic_flags = flags; c->ic_vht_ch_freq1 = 0; c->ic_vht_ch_freq2 = 0; set_extchan(c); set_vht_extchan(c); return (0); } static int copychan_prev(struct ieee80211_channel chans[], int maxchans, int *nchans, uint32_t flags) { struct ieee80211_channel *c; KASSERT(*nchans > 0, ("channel list is empty\n")); if (*nchans >= maxchans) return (ENOBUFS); #if 0 - printf("%s: %d of %d: flags=0x%08x\n", + net80211_printf("%s: %d of %d: flags=0x%08x\n", __func__, *nchans, maxchans, flags); #endif c = &chans[(*nchans)++]; c[0] = c[-1]; c->ic_flags = flags; c->ic_vht_ch_freq1 = 0; c->ic_vht_ch_freq2 = 0; set_extchan(c); set_vht_extchan(c); return (0); } /* * XXX VHT-2GHz */ static void getflags_2ghz(const uint8_t bands[], uint32_t flags[], int cbw_flags) { int nmodes; nmodes = 0; if (isset(bands, IEEE80211_MODE_11B)) flags[nmodes++] = IEEE80211_CHAN_B; if (isset(bands, IEEE80211_MODE_11G)) flags[nmodes++] = IEEE80211_CHAN_G; if (isset(bands, IEEE80211_MODE_11NG)) flags[nmodes++] = IEEE80211_CHAN_G | IEEE80211_CHAN_HT20; if (cbw_flags & NET80211_CBW_FLAG_HT40) { flags[nmodes++] = IEEE80211_CHAN_G | IEEE80211_CHAN_HT40U; flags[nmodes++] = IEEE80211_CHAN_G | IEEE80211_CHAN_HT40D; } flags[nmodes] = 0; } static void getflags_5ghz(const uint8_t bands[], uint32_t flags[], int cbw_flags) { int nmodes; /* * The addchan_list() function seems to expect the flags array to * be in channel width order, so the VHT bits are interspersed * as appropriate to maintain said order. * * It also assumes HT40U is before HT40D. */ nmodes = 0; /* 20MHz */ if (isset(bands, IEEE80211_MODE_11A)) flags[nmodes++] = IEEE80211_CHAN_A; if (isset(bands, IEEE80211_MODE_11NA)) flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT20; if (isset(bands, IEEE80211_MODE_VHT_5GHZ)) { flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT20 | IEEE80211_CHAN_VHT20; } /* 40MHz */ if (cbw_flags & NET80211_CBW_FLAG_HT40) flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U; if ((cbw_flags & NET80211_CBW_FLAG_HT40) && isset(bands, IEEE80211_MODE_VHT_5GHZ)) flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U | IEEE80211_CHAN_VHT40U; if (cbw_flags & NET80211_CBW_FLAG_HT40) flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D; if ((cbw_flags & NET80211_CBW_FLAG_HT40) && isset(bands, IEEE80211_MODE_VHT_5GHZ)) flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D | IEEE80211_CHAN_VHT40D; /* 80MHz */ if ((cbw_flags & NET80211_CBW_FLAG_VHT80) && isset(bands, IEEE80211_MODE_VHT_5GHZ)) { flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U | IEEE80211_CHAN_VHT80; flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D | IEEE80211_CHAN_VHT80; } /* VHT160 */ if ((cbw_flags & NET80211_CBW_FLAG_VHT160) && isset(bands, IEEE80211_MODE_VHT_5GHZ)) { flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U | IEEE80211_CHAN_VHT160; flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D | IEEE80211_CHAN_VHT160; } /* VHT80+80 */ if ((cbw_flags & NET80211_CBW_FLAG_VHT80P80) && isset(bands, IEEE80211_MODE_VHT_5GHZ)) { flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U | IEEE80211_CHAN_VHT80P80; flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D | IEEE80211_CHAN_VHT80P80; } flags[nmodes] = 0; } static void getflags(const uint8_t bands[], uint32_t flags[], int cbw_flags) { flags[0] = 0; if (isset(bands, IEEE80211_MODE_11A) || isset(bands, IEEE80211_MODE_11NA) || isset(bands, IEEE80211_MODE_VHT_5GHZ)) { if (isset(bands, IEEE80211_MODE_11B) || isset(bands, IEEE80211_MODE_11G) || isset(bands, IEEE80211_MODE_11NG) || isset(bands, IEEE80211_MODE_VHT_2GHZ)) return; getflags_5ghz(bands, flags, cbw_flags); } else getflags_2ghz(bands, flags, cbw_flags); } /* * Add one 20 MHz channel into specified channel list. * You MUST NOT mix bands when calling this. It will not add 5ghz * channels if you have any B/G/N band bit set. * The _cbw() variant does also support HT40/VHT80/160/80+80. */ int ieee80211_add_channel_cbw(struct ieee80211_channel chans[], int maxchans, int *nchans, uint8_t ieee, uint16_t freq, int8_t maxregpower, uint32_t chan_flags, const uint8_t bands[], int cbw_flags) { uint32_t flags[IEEE80211_MODE_MAX]; int i, error; getflags(bands, flags, cbw_flags); KASSERT(flags[0] != 0, ("%s: no correct mode provided\n", __func__)); error = addchan(chans, maxchans, nchans, ieee, freq, maxregpower, flags[0] | chan_flags); for (i = 1; flags[i] != 0 && error == 0; i++) { error = copychan_prev(chans, maxchans, nchans, flags[i] | chan_flags); } return (error); } int ieee80211_add_channel(struct ieee80211_channel chans[], int maxchans, int *nchans, uint8_t ieee, uint16_t freq, int8_t maxregpower, uint32_t chan_flags, const uint8_t bands[]) { return (ieee80211_add_channel_cbw(chans, maxchans, nchans, ieee, freq, maxregpower, chan_flags, bands, 0)); } static struct ieee80211_channel * findchannel(struct ieee80211_channel chans[], int nchans, uint16_t freq, uint32_t flags) { struct ieee80211_channel *c; int i; flags &= IEEE80211_CHAN_ALLTURBO; /* brute force search */ for (i = 0; i < nchans; i++) { c = &chans[i]; if (c->ic_freq == freq && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; } return NULL; } /* * Add 40 MHz channel pair into specified channel list. */ /* XXX VHT */ int ieee80211_add_channel_ht40(struct ieee80211_channel chans[], int maxchans, int *nchans, uint8_t ieee, int8_t maxregpower, uint32_t flags) { struct ieee80211_channel *cent, *extc; uint16_t freq; int error; freq = ieee80211_ieee2mhz(ieee, flags); /* * Each entry defines an HT40 channel pair; find the * center channel, then the extension channel above. */ flags |= IEEE80211_CHAN_HT20; cent = findchannel(chans, *nchans, freq, flags); if (cent == NULL) return (EINVAL); extc = findchannel(chans, *nchans, freq + 20, flags); if (extc == NULL) return (ENOENT); flags &= ~IEEE80211_CHAN_HT; error = addchan(chans, maxchans, nchans, cent->ic_ieee, cent->ic_freq, maxregpower, flags | IEEE80211_CHAN_HT40U); if (error != 0) return (error); error = addchan(chans, maxchans, nchans, extc->ic_ieee, extc->ic_freq, maxregpower, flags | IEEE80211_CHAN_HT40D); return (error); } /* * Fetch the center frequency for the primary channel. */ uint32_t ieee80211_get_channel_center_freq(const struct ieee80211_channel *c) { return (c->ic_freq); } /* * Fetch the center frequency for the primary BAND channel. * * For 5, 10, 20MHz channels it'll be the normally configured channel * frequency. * * For 40MHz, 80MHz, 160MHz channels it will be the centre of the * wide channel, not the centre of the primary channel (that's ic_freq). * * For 80+80MHz channels this will be the centre of the primary * 80MHz channel; the secondary 80MHz channel will be center_freq2(). */ uint32_t ieee80211_get_channel_center_freq1(const struct ieee80211_channel *c) { /* * VHT - use the pre-calculated centre frequency * of the given channel. */ if (IEEE80211_IS_CHAN_VHT(c)) return (ieee80211_ieee2mhz(c->ic_vht_ch_freq1, c->ic_flags)); if (IEEE80211_IS_CHAN_HT40U(c)) { return (c->ic_freq + 10); } if (IEEE80211_IS_CHAN_HT40D(c)) { return (c->ic_freq - 10); } return (c->ic_freq); } /* * For now, no 80+80 support; it will likely always return 0. */ uint32_t ieee80211_get_channel_center_freq2(const struct ieee80211_channel *c) { if (IEEE80211_IS_CHAN_VHT(c) && (c->ic_vht_ch_freq2 != 0)) return (ieee80211_ieee2mhz(c->ic_vht_ch_freq2, c->ic_flags)); return (0); } /* * Adds channels into specified channel list (ieee[] array must be sorted). * Channels are already sorted. */ static int add_chanlist(struct ieee80211_channel chans[], int maxchans, int *nchans, const uint8_t ieee[], int nieee, uint32_t flags[]) { uint16_t freq; int i, j, error; int is_vht; for (i = 0; i < nieee; i++) { freq = ieee80211_ieee2mhz(ieee[i], flags[0]); for (j = 0; flags[j] != 0; j++) { /* * Notes: * + HT40 and VHT40 channels occur together, so * we need to be careful that we actually allow that. * + VHT80, VHT160 will coexist with HT40/VHT40, so * make sure it's not skipped because of the overlap * check used for (V)HT40. */ is_vht = !! (flags[j] & IEEE80211_CHAN_VHT); /* XXX TODO FIXME VHT80P80. */ /* Test for VHT160 analogue to the VHT80 below. */ if (is_vht && flags[j] & IEEE80211_CHAN_VHT160) if (! is_vht160_valid_freq(freq)) continue; /* * Test for VHT80. * XXX This is all very broken right now. * What we /should/ do is: * * + check that the frequency is in the list of * allowed VHT80 ranges; and * + the other 3 channels in the list are actually * also available. */ if (is_vht && flags[j] & IEEE80211_CHAN_VHT80) if (! is_vht80_valid_freq(freq)) continue; /* * Test for (V)HT40. * * This is also a fall through from VHT80; as we only * allow a VHT80 channel if the VHT40 combination is * also valid. If the VHT40 form is not valid then * we certainly can't do VHT80.. */ if (flags[j] & IEEE80211_CHAN_HT40D) /* * Can't have a "lower" channel if we are the * first channel. * * Can't have a "lower" channel if it's below/ * within 20MHz of the first channel. * * Can't have a "lower" channel if the channel * below it is not 20MHz away. */ if (i == 0 || ieee[i] < ieee[0] + 4 || freq - 20 != ieee80211_ieee2mhz(ieee[i] - 4, flags[j])) continue; if (flags[j] & IEEE80211_CHAN_HT40U) /* * Can't have an "upper" channel if we are * the last channel. * * Can't have an "upper" channel be above the * last channel in the list. * * Can't have an "upper" channel if the next * channel according to the math isn't 20MHz * away. (Likely for channel 13/14.) */ if (i == nieee - 1 || ieee[i] + 4 > ieee[nieee - 1] || freq + 20 != ieee80211_ieee2mhz(ieee[i] + 4, flags[j])) continue; if (j == 0) { error = addchan(chans, maxchans, nchans, ieee[i], freq, 0, flags[j]); } else { error = copychan_prev(chans, maxchans, nchans, flags[j]); } if (error != 0) return (error); } } return (0); } int ieee80211_add_channel_list_2ghz(struct ieee80211_channel chans[], int maxchans, int *nchans, const uint8_t ieee[], int nieee, const uint8_t bands[], int cbw_flags) { uint32_t flags[IEEE80211_MODE_MAX]; /* XXX no VHT for now */ getflags_2ghz(bands, flags, cbw_flags); KASSERT(flags[0] != 0, ("%s: no correct mode provided\n", __func__)); return (add_chanlist(chans, maxchans, nchans, ieee, nieee, flags)); } int ieee80211_add_channels_default_2ghz(struct ieee80211_channel chans[], int maxchans, int *nchans, const uint8_t bands[], int cbw_flags) { const uint8_t default_chan_list[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }; return (ieee80211_add_channel_list_2ghz(chans, maxchans, nchans, default_chan_list, nitems(default_chan_list), bands, cbw_flags)); } int ieee80211_add_channel_list_5ghz(struct ieee80211_channel chans[], int maxchans, int *nchans, const uint8_t ieee[], int nieee, const uint8_t bands[], int cbw_flags) { /* * XXX-BZ with HT and VHT there is no 1:1 mapping anymore. Review all * uses of IEEE80211_MODE_MAX and add a new #define name for array size. */ uint32_t flags[2 * IEEE80211_MODE_MAX]; getflags_5ghz(bands, flags, cbw_flags); KASSERT(flags[0] != 0, ("%s: no correct mode provided\n", __func__)); return (add_chanlist(chans, maxchans, nchans, ieee, nieee, flags)); } /* * Locate a channel given a frequency+flags. We cache * the previous lookup to optimize switching between two * channels--as happens with dynamic turbo. */ struct ieee80211_channel * ieee80211_find_channel(struct ieee80211com *ic, int freq, int flags) { struct ieee80211_channel *c; flags &= IEEE80211_CHAN_ALLTURBO; c = ic->ic_prevchan; if (c != NULL && c->ic_freq == freq && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; /* brute force search */ return (findchannel(ic->ic_channels, ic->ic_nchans, freq, flags)); } /* * Locate a channel given a channel number+flags. We cache * the previous lookup to optimize switching between two * channels--as happens with dynamic turbo. */ struct ieee80211_channel * ieee80211_find_channel_byieee(struct ieee80211com *ic, int ieee, int flags) { struct ieee80211_channel *c; int i; flags &= IEEE80211_CHAN_ALLTURBO; c = ic->ic_prevchan; if (c != NULL && c->ic_ieee == ieee && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; /* brute force search */ for (i = 0; i < ic->ic_nchans; i++) { c = &ic->ic_channels[i]; if (c->ic_ieee == ieee && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; } return NULL; } /* * Lookup a channel suitable for the given rx status. * * This is used to find a channel for a frame (eg beacon, probe * response) based purely on the received PHY information. * * For now it tries to do it based on R_FREQ / R_IEEE. * This is enough for 11bg and 11a (and thus 11ng/11na) * but it will not be enough for GSM, PSB channels and the * like. It also doesn't know about legacy-turbog and * legacy-turbo modes, which some offload NICs actually * support in weird ways. * * Takes the ic and rxstatus; returns the channel or NULL * if not found. * * XXX TODO: Add support for that when the need arises. */ struct ieee80211_channel * ieee80211_lookup_channel_rxstatus(struct ieee80211vap *vap, const struct ieee80211_rx_stats *rxs) { struct ieee80211com *ic = vap->iv_ic; uint32_t flags; struct ieee80211_channel *c; if (rxs == NULL) return (NULL); /* * Strictly speaking we only use freq for now, * however later on we may wish to just store * the ieee for verification. */ if ((rxs->r_flags & IEEE80211_R_FREQ) == 0) return (NULL); if ((rxs->r_flags & IEEE80211_R_IEEE) == 0) return (NULL); if ((rxs->r_flags & IEEE80211_R_BAND) == 0) return (NULL); /* * If the rx status contains a valid ieee/freq, then * ensure we populate the correct channel information * in rxchan before passing it up to the scan infrastructure. * Offload NICs will pass up beacons from all channels * during background scans. */ /* Determine a band */ switch (rxs->c_band) { case IEEE80211_CHAN_2GHZ: flags = IEEE80211_CHAN_G; break; case IEEE80211_CHAN_5GHZ: flags = IEEE80211_CHAN_A; break; default: if (rxs->c_freq < 3000) { flags = IEEE80211_CHAN_G; } else { flags = IEEE80211_CHAN_A; } break; } /* Channel lookup */ c = ieee80211_find_channel(ic, rxs->c_freq, flags); IEEE80211_DPRINTF(vap, IEEE80211_MSG_INPUT, "%s: freq=%d, ieee=%d, flags=0x%08x; c=%p\n", __func__, (int) rxs->c_freq, (int) rxs->c_ieee, flags, c); return (c); } static void addmedia(struct ifmedia *media, int caps, int addsta, int mode, int mword) { #define ADD(_ic, _s, _o) \ ifmedia_add(media, \ IFM_MAKEWORD(IFM_IEEE80211, (_s), (_o), 0), 0, NULL) static const u_int mopts[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = IFM_AUTO, [IEEE80211_MODE_11A] = IFM_IEEE80211_11A, [IEEE80211_MODE_11B] = IFM_IEEE80211_11B, [IEEE80211_MODE_11G] = IFM_IEEE80211_11G, [IEEE80211_MODE_FH] = IFM_IEEE80211_FH, [IEEE80211_MODE_TURBO_A] = IFM_IEEE80211_11A|IFM_IEEE80211_TURBO, [IEEE80211_MODE_TURBO_G] = IFM_IEEE80211_11G|IFM_IEEE80211_TURBO, [IEEE80211_MODE_STURBO_A] = IFM_IEEE80211_11A|IFM_IEEE80211_TURBO, [IEEE80211_MODE_HALF] = IFM_IEEE80211_11A, /* XXX */ [IEEE80211_MODE_QUARTER] = IFM_IEEE80211_11A, /* XXX */ [IEEE80211_MODE_11NA] = IFM_IEEE80211_11NA, [IEEE80211_MODE_11NG] = IFM_IEEE80211_11NG, [IEEE80211_MODE_VHT_2GHZ] = IFM_IEEE80211_VHT2G, [IEEE80211_MODE_VHT_5GHZ] = IFM_IEEE80211_VHT5G, }; u_int mopt; mopt = mopts[mode]; if (addsta) ADD(ic, mword, mopt); /* STA mode has no cap */ if (caps & IEEE80211_C_IBSS) ADD(media, mword, mopt | IFM_IEEE80211_ADHOC); if (caps & IEEE80211_C_HOSTAP) ADD(media, mword, mopt | IFM_IEEE80211_HOSTAP); if (caps & IEEE80211_C_AHDEMO) ADD(media, mword, mopt | IFM_IEEE80211_ADHOC | IFM_FLAG0); if (caps & IEEE80211_C_MONITOR) ADD(media, mword, mopt | IFM_IEEE80211_MONITOR); if (caps & IEEE80211_C_WDS) ADD(media, mword, mopt | IFM_IEEE80211_WDS); if (caps & IEEE80211_C_MBSS) ADD(media, mword, mopt | IFM_IEEE80211_MBSS); #undef ADD } /* * Setup the media data structures according to the channel and * rate tables. */ static int ieee80211_media_setup(struct ieee80211com *ic, struct ifmedia *media, int caps, int addsta, ifm_change_cb_t media_change, ifm_stat_cb_t media_stat) { int i, j, rate, maxrate, mword, r; enum ieee80211_phymode mode; const struct ieee80211_rateset *rs; struct ieee80211_rateset allrates; struct ieee80211_node_txrate tn; /* * Fill in media characteristics. */ ifmedia_init(media, 0, media_change, media_stat); maxrate = 0; /* * Add media for legacy operating modes. */ memset(&allrates, 0, sizeof(allrates)); for (mode = IEEE80211_MODE_AUTO; mode < IEEE80211_MODE_11NA; mode++) { if (isclr(ic->ic_modecaps, mode)) continue; addmedia(media, caps, addsta, mode, IFM_AUTO); if (mode == IEEE80211_MODE_AUTO) continue; rs = &ic->ic_sup_rates[mode]; for (i = 0; i < rs->rs_nrates; i++) { rate = rs->rs_rates[i]; tn = IEEE80211_NODE_TXRATE_INIT_LEGACY(rate); mword = ieee80211_rate2media(ic, &tn, mode); if (mword == 0) continue; addmedia(media, caps, addsta, mode, mword); /* * Add legacy rate to the collection of all rates. */ r = rate & IEEE80211_RATE_VAL; for (j = 0; j < allrates.rs_nrates; j++) if (allrates.rs_rates[j] == r) break; if (j == allrates.rs_nrates) { /* unique, add to the set */ allrates.rs_rates[j] = r; allrates.rs_nrates++; } rate = (rate & IEEE80211_RATE_VAL) / 2; if (rate > maxrate) maxrate = rate; } } for (i = 0; i < allrates.rs_nrates; i++) { tn = IEEE80211_NODE_TXRATE_INIT_LEGACY(allrates.rs_rates[i]); mword = ieee80211_rate2media(ic, &tn, IEEE80211_MODE_AUTO); if (mword == 0) continue; /* NB: remove media options from mword */ addmedia(media, caps, addsta, IEEE80211_MODE_AUTO, IFM_SUBTYPE(mword)); } /* * Add HT/11n media. Note that we do not have enough * bits in the media subtype to express the MCS so we * use a "placeholder" media subtype and any fixed MCS * must be specified with a different mechanism. */ for (; mode <= IEEE80211_MODE_11NG; mode++) { if (isclr(ic->ic_modecaps, mode)) continue; addmedia(media, caps, addsta, mode, IFM_AUTO); addmedia(media, caps, addsta, mode, IFM_IEEE80211_MCS); } if (isset(ic->ic_modecaps, IEEE80211_MODE_11NA) || isset(ic->ic_modecaps, IEEE80211_MODE_11NG)) { addmedia(media, caps, addsta, IEEE80211_MODE_AUTO, IFM_IEEE80211_MCS); i = ic->ic_txstream * 8 - 1; if ((ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) && (ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI40)) rate = ieee80211_htrates[i].ht40_rate_400ns; else if ((ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40)) rate = ieee80211_htrates[i].ht40_rate_800ns; else if ((ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI20)) rate = ieee80211_htrates[i].ht20_rate_400ns; else rate = ieee80211_htrates[i].ht20_rate_800ns; if (rate > maxrate) maxrate = rate; } /* * Add VHT media. * XXX-BZ skip "VHT_2GHZ" for now. */ for (mode = IEEE80211_MODE_VHT_5GHZ; mode <= IEEE80211_MODE_VHT_5GHZ; mode++) { if (isclr(ic->ic_modecaps, mode)) continue; addmedia(media, caps, addsta, mode, IFM_AUTO); addmedia(media, caps, addsta, mode, IFM_IEEE80211_VHT); } if (isset(ic->ic_modecaps, IEEE80211_MODE_VHT_5GHZ)) { addmedia(media, caps, addsta, IEEE80211_MODE_AUTO, IFM_IEEE80211_VHT); /* XXX TODO: VHT maxrate */ } return maxrate; } /* XXX inline or eliminate? */ const struct ieee80211_rateset * ieee80211_get_suprates(struct ieee80211com *ic, const struct ieee80211_channel *c) { /* XXX does this work for 11ng basic rates? */ return &ic->ic_sup_rates[ieee80211_chan2mode(c)]; } /* XXX inline or eliminate? */ const struct ieee80211_htrateset * ieee80211_get_suphtrates(struct ieee80211com *ic, const struct ieee80211_channel *c) { return &ic->ic_sup_htrates; } void ieee80211_announce(struct ieee80211com *ic) { int i, rate, mword; enum ieee80211_phymode mode; const struct ieee80211_rateset *rs; struct ieee80211_node_txrate tn; /* NB: skip AUTO since it has no rates */ for (mode = IEEE80211_MODE_AUTO+1; mode < IEEE80211_MODE_11NA; mode++) { if (isclr(ic->ic_modecaps, mode)) continue; ic_printf(ic, "%s rates: ", ieee80211_phymode_name[mode]); rs = &ic->ic_sup_rates[mode]; for (i = 0; i < rs->rs_nrates; i++) { tn = IEEE80211_NODE_TXRATE_INIT_LEGACY(rs->rs_rates[i]); mword = ieee80211_rate2media(ic, &tn, mode); if (mword == 0) continue; rate = ieee80211_media2rate(mword); - printf("%s%d%sMbps", (i != 0 ? " " : ""), + net80211_printf("%s%d%sMbps", (i != 0 ? " " : ""), rate / 2, ((rate & 0x1) != 0 ? ".5" : "")); } - printf("\n"); + net80211_printf("\n"); } ieee80211_ht_announce(ic); ieee80211_vht_announce(ic); } void ieee80211_announce_channels(struct ieee80211com *ic) { const struct ieee80211_channel *c; char type; int i, cw; - printf("Chan Freq CW RegPwr MinPwr MaxPwr\n"); + net80211_printf("Chan Freq CW RegPwr MinPwr MaxPwr\n"); for (i = 0; i < ic->ic_nchans; i++) { c = &ic->ic_channels[i]; if (IEEE80211_IS_CHAN_ST(c)) type = 'S'; else if (IEEE80211_IS_CHAN_108A(c)) type = 'T'; else if (IEEE80211_IS_CHAN_108G(c)) type = 'G'; else if (IEEE80211_IS_CHAN_HT(c)) type = 'n'; else if (IEEE80211_IS_CHAN_A(c)) type = 'a'; else if (IEEE80211_IS_CHAN_ANYG(c)) type = 'g'; else if (IEEE80211_IS_CHAN_B(c)) type = 'b'; else type = 'f'; if (IEEE80211_IS_CHAN_HT40(c) || IEEE80211_IS_CHAN_TURBO(c)) cw = 40; else if (IEEE80211_IS_CHAN_HALF(c)) cw = 10; else if (IEEE80211_IS_CHAN_QUARTER(c)) cw = 5; else cw = 20; - printf("%4d %4d%c %2d%c %6d %4d.%d %4d.%d\n" + net80211_printf("%4d %4d%c %2d%c %6d %4d.%d %4d.%d\n" , c->ic_ieee, c->ic_freq, type , cw , IEEE80211_IS_CHAN_HT40U(c) ? '+' : IEEE80211_IS_CHAN_HT40D(c) ? '-' : ' ' , c->ic_maxregpower , c->ic_minpower / 2, c->ic_minpower & 1 ? 5 : 0 , c->ic_maxpower / 2, c->ic_maxpower & 1 ? 5 : 0 ); } } static int media2mode(const struct ifmedia_entry *ime, uint32_t flags, uint16_t *mode) { switch (IFM_MODE(ime->ifm_media)) { case IFM_IEEE80211_11A: *mode = IEEE80211_MODE_11A; break; case IFM_IEEE80211_11B: *mode = IEEE80211_MODE_11B; break; case IFM_IEEE80211_11G: *mode = IEEE80211_MODE_11G; break; case IFM_IEEE80211_FH: *mode = IEEE80211_MODE_FH; break; case IFM_IEEE80211_11NA: *mode = IEEE80211_MODE_11NA; break; case IFM_IEEE80211_11NG: *mode = IEEE80211_MODE_11NG; break; case IFM_IEEE80211_VHT2G: *mode = IEEE80211_MODE_VHT_2GHZ; break; case IFM_IEEE80211_VHT5G: *mode = IEEE80211_MODE_VHT_5GHZ; break; case IFM_AUTO: *mode = IEEE80211_MODE_AUTO; break; default: return 0; } /* * Turbo mode is an ``option''. * XXX does not apply to AUTO */ if (ime->ifm_media & IFM_IEEE80211_TURBO) { if (*mode == IEEE80211_MODE_11A) { if (flags & IEEE80211_F_TURBOP) *mode = IEEE80211_MODE_TURBO_A; else *mode = IEEE80211_MODE_STURBO_A; } else if (*mode == IEEE80211_MODE_11G) *mode = IEEE80211_MODE_TURBO_G; else return 0; } /* XXX HT40 +/- */ return 1; } /* * Handle a media change request on the vap interface. */ int ieee80211_media_change(struct ifnet *ifp) { struct ieee80211vap *vap = ifp->if_softc; struct ifmedia_entry *ime = vap->iv_media.ifm_cur; uint16_t newmode; if (!media2mode(ime, vap->iv_flags, &newmode)) return EINVAL; if (vap->iv_des_mode != newmode) { vap->iv_des_mode = newmode; /* XXX kick state machine if up+running */ } return 0; } /* * Common code to calculate the media status word * from the operating mode and channel state. */ static int media_status(enum ieee80211_opmode opmode, const struct ieee80211_channel *chan) { int status; status = IFM_IEEE80211; switch (opmode) { case IEEE80211_M_STA: break; case IEEE80211_M_IBSS: status |= IFM_IEEE80211_ADHOC; break; case IEEE80211_M_HOSTAP: status |= IFM_IEEE80211_HOSTAP; break; case IEEE80211_M_MONITOR: status |= IFM_IEEE80211_MONITOR; break; case IEEE80211_M_AHDEMO: status |= IFM_IEEE80211_ADHOC | IFM_FLAG0; break; case IEEE80211_M_WDS: status |= IFM_IEEE80211_WDS; break; case IEEE80211_M_MBSS: status |= IFM_IEEE80211_MBSS; break; } if (IEEE80211_IS_CHAN_VHT_5GHZ(chan)) { status |= IFM_IEEE80211_VHT5G; } else if (IEEE80211_IS_CHAN_VHT_2GHZ(chan)) { status |= IFM_IEEE80211_VHT2G; } else if (IEEE80211_IS_CHAN_HTA(chan)) { status |= IFM_IEEE80211_11NA; } else if (IEEE80211_IS_CHAN_HTG(chan)) { status |= IFM_IEEE80211_11NG; } else if (IEEE80211_IS_CHAN_A(chan)) { status |= IFM_IEEE80211_11A; } else if (IEEE80211_IS_CHAN_B(chan)) { status |= IFM_IEEE80211_11B; } else if (IEEE80211_IS_CHAN_ANYG(chan)) { status |= IFM_IEEE80211_11G; } else if (IEEE80211_IS_CHAN_FHSS(chan)) { status |= IFM_IEEE80211_FH; } /* XXX else complain? */ if (IEEE80211_IS_CHAN_TURBO(chan)) status |= IFM_IEEE80211_TURBO; #if 0 if (IEEE80211_IS_CHAN_HT20(chan)) status |= IFM_IEEE80211_HT20; if (IEEE80211_IS_CHAN_HT40(chan)) status |= IFM_IEEE80211_HT40; #endif return status; } void ieee80211_media_status(struct ifnet *ifp, struct ifmediareq *imr) { struct ieee80211vap *vap = ifp->if_softc; struct ieee80211com *ic = vap->iv_ic; enum ieee80211_phymode mode; struct ieee80211_node_txrate tn; imr->ifm_status = IFM_AVALID; /* * NB: use the current channel's mode to lock down a xmit * rate only when running; otherwise we may have a mismatch * in which case the rate will not be convertible. */ if (vap->iv_state == IEEE80211_S_RUN || vap->iv_state == IEEE80211_S_SLEEP) { imr->ifm_status |= IFM_ACTIVE; mode = ieee80211_chan2mode(ic->ic_curchan); } else mode = IEEE80211_MODE_AUTO; imr->ifm_active = media_status(vap->iv_opmode, ic->ic_curchan); /* * Calculate a current rate if possible. */ if (vap->iv_txparms[mode].ucastrate != IEEE80211_FIXED_RATE_NONE) { /* * A fixed rate is set, report that. */ tn = IEEE80211_NODE_TXRATE_INIT_LEGACY( vap->iv_txparms[mode].ucastrate); imr->ifm_active |= ieee80211_rate2media(ic, &tn, mode); } else if (vap->iv_opmode == IEEE80211_M_STA) { /* * In station mode report the current transmit rate. */ ieee80211_node_get_txrate(vap->iv_bss, &tn); imr->ifm_active |= ieee80211_rate2media(ic, &tn, mode); } else imr->ifm_active |= IFM_AUTO; if (imr->ifm_status & IFM_ACTIVE) imr->ifm_current = imr->ifm_active; } /* * Set the current phy mode and recalculate the active channel * set based on the available channels for this mode. Also * select a new default/current channel if the current one is * inappropriate for this mode. */ int ieee80211_setmode(struct ieee80211com *ic, enum ieee80211_phymode mode) { /* * Adjust basic rates in 11b/11g supported rate set. * Note that if operating on a hal/quarter rate channel * this is a noop as those rates sets are different * and used instead. */ if (mode == IEEE80211_MODE_11G || mode == IEEE80211_MODE_11B) ieee80211_setbasicrates(&ic->ic_sup_rates[mode], mode); ic->ic_curmode = mode; ieee80211_reset_erp(ic); /* reset global ERP state */ return 0; } /* * Return the phy mode for with the specified channel. */ enum ieee80211_phymode ieee80211_chan2mode(const struct ieee80211_channel *chan) { if (IEEE80211_IS_CHAN_VHT_2GHZ(chan)) return IEEE80211_MODE_VHT_2GHZ; else if (IEEE80211_IS_CHAN_VHT_5GHZ(chan)) return IEEE80211_MODE_VHT_5GHZ; else if (IEEE80211_IS_CHAN_HTA(chan)) return IEEE80211_MODE_11NA; else if (IEEE80211_IS_CHAN_HTG(chan)) return IEEE80211_MODE_11NG; else if (IEEE80211_IS_CHAN_108G(chan)) return IEEE80211_MODE_TURBO_G; else if (IEEE80211_IS_CHAN_ST(chan)) return IEEE80211_MODE_STURBO_A; else if (IEEE80211_IS_CHAN_TURBO(chan)) return IEEE80211_MODE_TURBO_A; else if (IEEE80211_IS_CHAN_HALF(chan)) return IEEE80211_MODE_HALF; else if (IEEE80211_IS_CHAN_QUARTER(chan)) return IEEE80211_MODE_QUARTER; else if (IEEE80211_IS_CHAN_A(chan)) return IEEE80211_MODE_11A; else if (IEEE80211_IS_CHAN_ANYG(chan)) return IEEE80211_MODE_11G; else if (IEEE80211_IS_CHAN_B(chan)) return IEEE80211_MODE_11B; else if (IEEE80211_IS_CHAN_FHSS(chan)) return IEEE80211_MODE_FH; /* NB: should not get here */ - printf("%s: cannot map channel to mode; freq %u flags 0x%x\n", + net80211_printf("%s: cannot map channel to mode; freq %u flags 0x%x\n", __func__, chan->ic_freq, chan->ic_flags); return IEEE80211_MODE_11B; } struct ratemedia { u_int match; /* rate + mode */ u_int media; /* if_media rate */ }; static int findmedia(const struct ratemedia rates[], int n, u_int match) { int i; for (i = 0; i < n; i++) if (rates[i].match == match) return rates[i].media; return IFM_AUTO; } /* * Convert IEEE80211 rate value to ifmedia subtype. * Rate is either a legacy rate in units of 0.5Mbps * or an MCS index. */ int ieee80211_rate2media(struct ieee80211com *ic, const struct ieee80211_node_txrate *tr, enum ieee80211_phymode mode) { static const struct ratemedia rates[] = { { 2 | IFM_IEEE80211_FH, IFM_IEEE80211_FH1 }, { 4 | IFM_IEEE80211_FH, IFM_IEEE80211_FH2 }, { 2 | IFM_IEEE80211_11B, IFM_IEEE80211_DS1 }, { 4 | IFM_IEEE80211_11B, IFM_IEEE80211_DS2 }, { 11 | IFM_IEEE80211_11B, IFM_IEEE80211_DS5 }, { 22 | IFM_IEEE80211_11B, IFM_IEEE80211_DS11 }, { 44 | IFM_IEEE80211_11B, IFM_IEEE80211_DS22 }, { 12 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM6 }, { 18 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM9 }, { 24 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM12 }, { 36 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM18 }, { 48 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM24 }, { 72 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM36 }, { 96 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM48 }, { 108 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM54 }, { 2 | IFM_IEEE80211_11G, IFM_IEEE80211_DS1 }, { 4 | IFM_IEEE80211_11G, IFM_IEEE80211_DS2 }, { 11 | IFM_IEEE80211_11G, IFM_IEEE80211_DS5 }, { 22 | IFM_IEEE80211_11G, IFM_IEEE80211_DS11 }, { 12 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM6 }, { 18 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM9 }, { 24 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM12 }, { 36 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM18 }, { 48 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM24 }, { 72 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM36 }, { 96 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM48 }, { 108 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM54 }, { 6 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM3 }, { 9 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM4 }, { 54 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM27 }, /* NB: OFDM72 doesn't really exist so we don't handle it */ }; static const struct ratemedia htrates[] = { { 0, IFM_IEEE80211_MCS }, { 1, IFM_IEEE80211_MCS }, { 2, IFM_IEEE80211_MCS }, { 3, IFM_IEEE80211_MCS }, { 4, IFM_IEEE80211_MCS }, { 5, IFM_IEEE80211_MCS }, { 6, IFM_IEEE80211_MCS }, { 7, IFM_IEEE80211_MCS }, { 8, IFM_IEEE80211_MCS }, { 9, IFM_IEEE80211_MCS }, { 10, IFM_IEEE80211_MCS }, { 11, IFM_IEEE80211_MCS }, { 12, IFM_IEEE80211_MCS }, { 13, IFM_IEEE80211_MCS }, { 14, IFM_IEEE80211_MCS }, { 15, IFM_IEEE80211_MCS }, { 16, IFM_IEEE80211_MCS }, { 17, IFM_IEEE80211_MCS }, { 18, IFM_IEEE80211_MCS }, { 19, IFM_IEEE80211_MCS }, { 20, IFM_IEEE80211_MCS }, { 21, IFM_IEEE80211_MCS }, { 22, IFM_IEEE80211_MCS }, { 23, IFM_IEEE80211_MCS }, { 24, IFM_IEEE80211_MCS }, { 25, IFM_IEEE80211_MCS }, { 26, IFM_IEEE80211_MCS }, { 27, IFM_IEEE80211_MCS }, { 28, IFM_IEEE80211_MCS }, { 29, IFM_IEEE80211_MCS }, { 30, IFM_IEEE80211_MCS }, { 31, IFM_IEEE80211_MCS }, { 32, IFM_IEEE80211_MCS }, { 33, IFM_IEEE80211_MCS }, { 34, IFM_IEEE80211_MCS }, { 35, IFM_IEEE80211_MCS }, { 36, IFM_IEEE80211_MCS }, { 37, IFM_IEEE80211_MCS }, { 38, IFM_IEEE80211_MCS }, { 39, IFM_IEEE80211_MCS }, { 40, IFM_IEEE80211_MCS }, { 41, IFM_IEEE80211_MCS }, { 42, IFM_IEEE80211_MCS }, { 43, IFM_IEEE80211_MCS }, { 44, IFM_IEEE80211_MCS }, { 45, IFM_IEEE80211_MCS }, { 46, IFM_IEEE80211_MCS }, { 47, IFM_IEEE80211_MCS }, { 48, IFM_IEEE80211_MCS }, { 49, IFM_IEEE80211_MCS }, { 50, IFM_IEEE80211_MCS }, { 51, IFM_IEEE80211_MCS }, { 52, IFM_IEEE80211_MCS }, { 53, IFM_IEEE80211_MCS }, { 54, IFM_IEEE80211_MCS }, { 55, IFM_IEEE80211_MCS }, { 56, IFM_IEEE80211_MCS }, { 57, IFM_IEEE80211_MCS }, { 58, IFM_IEEE80211_MCS }, { 59, IFM_IEEE80211_MCS }, { 60, IFM_IEEE80211_MCS }, { 61, IFM_IEEE80211_MCS }, { 62, IFM_IEEE80211_MCS }, { 63, IFM_IEEE80211_MCS }, { 64, IFM_IEEE80211_MCS }, { 65, IFM_IEEE80211_MCS }, { 66, IFM_IEEE80211_MCS }, { 67, IFM_IEEE80211_MCS }, { 68, IFM_IEEE80211_MCS }, { 69, IFM_IEEE80211_MCS }, { 70, IFM_IEEE80211_MCS }, { 71, IFM_IEEE80211_MCS }, { 72, IFM_IEEE80211_MCS }, { 73, IFM_IEEE80211_MCS }, { 74, IFM_IEEE80211_MCS }, { 75, IFM_IEEE80211_MCS }, { 76, IFM_IEEE80211_MCS }, }; static const struct ratemedia vhtrates[] = { { 0, IFM_IEEE80211_VHT }, { 1, IFM_IEEE80211_VHT }, { 2, IFM_IEEE80211_VHT }, { 3, IFM_IEEE80211_VHT }, { 4, IFM_IEEE80211_VHT }, { 5, IFM_IEEE80211_VHT }, { 6, IFM_IEEE80211_VHT }, { 7, IFM_IEEE80211_VHT }, { 8, IFM_IEEE80211_VHT }, /* Optional. */ { 9, IFM_IEEE80211_VHT }, /* Optional. */ #if 0 /* Some QCA and BRCM seem to support this; offspec. */ { 10, IFM_IEEE80211_VHT }, { 11, IFM_IEEE80211_VHT }, #endif }; int m, rate; /* * Check 11ac/11n rates first for match as an MCS. */ if (mode == IEEE80211_MODE_VHT_5GHZ) { if (tr->type == IEEE80211_NODE_TXRATE_VHT) { m = findmedia(vhtrates, nitems(vhtrates), tr->mcs); if (m != IFM_AUTO) return (m | IFM_IEEE80211_VHT); } } else if (mode == IEEE80211_MODE_11NA) { /* NB: 12 is ambiguous, it will be treated as an MCS */ if (tr->type == IEEE80211_NODE_TXRATE_HT) { m = findmedia(htrates, nitems(htrates), tr->dot11rate & ~IEEE80211_RATE_MCS); if (m != IFM_AUTO) return m | IFM_IEEE80211_11NA; } } else if (mode == IEEE80211_MODE_11NG) { /* NB: 12 is ambiguous, it will be treated as an MCS */ if (tr->type == IEEE80211_NODE_TXRATE_HT) { m = findmedia(htrates, nitems(htrates), tr->dot11rate & ~IEEE80211_RATE_MCS); if (m != IFM_AUTO) return m | IFM_IEEE80211_11NG; } } /* * At this point it needs to be a dot11rate (legacy/HT) for the * rest of the logic to work. */ if ((tr->type != IEEE80211_NODE_TXRATE_LEGACY) && (tr->type != IEEE80211_NODE_TXRATE_HT)) return (IFM_AUTO); rate = tr->dot11rate & IEEE80211_RATE_VAL; switch (mode) { case IEEE80211_MODE_11A: case IEEE80211_MODE_HALF: /* XXX good 'nuf */ case IEEE80211_MODE_QUARTER: case IEEE80211_MODE_11NA: case IEEE80211_MODE_TURBO_A: case IEEE80211_MODE_STURBO_A: return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_11A); case IEEE80211_MODE_11B: return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_11B); case IEEE80211_MODE_FH: return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_FH); case IEEE80211_MODE_AUTO: /* NB: ic may be NULL for some drivers */ if (ic != NULL && ic->ic_phytype == IEEE80211_T_FH) return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_FH); /* NB: hack, 11g matches both 11b+11a rates */ /* fall thru... */ case IEEE80211_MODE_11G: case IEEE80211_MODE_11NG: case IEEE80211_MODE_TURBO_G: return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_11G); case IEEE80211_MODE_VHT_2GHZ: case IEEE80211_MODE_VHT_5GHZ: /* XXX TODO: need to figure out mapping for VHT rates */ return IFM_AUTO; } return IFM_AUTO; } int ieee80211_media2rate(int mword) { static const int ieeerates[] = { -1, /* IFM_AUTO */ 0, /* IFM_MANUAL */ 0, /* IFM_NONE */ 2, /* IFM_IEEE80211_FH1 */ 4, /* IFM_IEEE80211_FH2 */ 2, /* IFM_IEEE80211_DS1 */ 4, /* IFM_IEEE80211_DS2 */ 11, /* IFM_IEEE80211_DS5 */ 22, /* IFM_IEEE80211_DS11 */ 44, /* IFM_IEEE80211_DS22 */ 12, /* IFM_IEEE80211_OFDM6 */ 18, /* IFM_IEEE80211_OFDM9 */ 24, /* IFM_IEEE80211_OFDM12 */ 36, /* IFM_IEEE80211_OFDM18 */ 48, /* IFM_IEEE80211_OFDM24 */ 72, /* IFM_IEEE80211_OFDM36 */ 96, /* IFM_IEEE80211_OFDM48 */ 108, /* IFM_IEEE80211_OFDM54 */ 144, /* IFM_IEEE80211_OFDM72 */ 0, /* IFM_IEEE80211_DS354k */ 0, /* IFM_IEEE80211_DS512k */ 6, /* IFM_IEEE80211_OFDM3 */ 9, /* IFM_IEEE80211_OFDM4 */ 54, /* IFM_IEEE80211_OFDM27 */ -1, /* IFM_IEEE80211_MCS */ -1, /* IFM_IEEE80211_VHT */ }; return IFM_SUBTYPE(mword) < nitems(ieeerates) ? ieeerates[IFM_SUBTYPE(mword)] : 0; } /* * The following hash function is adapted from "Hash Functions" by Bob Jenkins * ("Algorithm Alley", Dr. Dobbs Journal, September 1997). */ #define mix(a, b, c) \ do { \ a -= b; a -= c; a ^= (c >> 13); \ b -= c; b -= a; b ^= (a << 8); \ c -= a; c -= b; c ^= (b >> 13); \ a -= b; a -= c; a ^= (c >> 12); \ b -= c; b -= a; b ^= (a << 16); \ c -= a; c -= b; c ^= (b >> 5); \ a -= b; a -= c; a ^= (c >> 3); \ b -= c; b -= a; b ^= (a << 10); \ c -= a; c -= b; c ^= (b >> 15); \ } while (/*CONSTCOND*/0) uint32_t ieee80211_mac_hash(const struct ieee80211com *ic, const uint8_t addr[IEEE80211_ADDR_LEN]) { uint32_t a = 0x9e3779b9, b = 0x9e3779b9, c = ic->ic_hash_key; b += addr[5] << 8; b += addr[4]; a += addr[3] << 24; a += addr[2] << 16; a += addr[1] << 8; a += addr[0]; mix(a, b, c); return c; } #undef mix char ieee80211_channel_type_char(const struct ieee80211_channel *c) { if (IEEE80211_IS_CHAN_ST(c)) return 'S'; if (IEEE80211_IS_CHAN_108A(c)) return 'T'; if (IEEE80211_IS_CHAN_108G(c)) return 'G'; if (IEEE80211_IS_CHAN_VHT(c)) return 'v'; if (IEEE80211_IS_CHAN_HT(c)) return 'n'; if (IEEE80211_IS_CHAN_A(c)) return 'a'; if (IEEE80211_IS_CHAN_ANYG(c)) return 'g'; if (IEEE80211_IS_CHAN_B(c)) return 'b'; return 'f'; } /* * Determine whether the given key in the given VAP is a global key. * (key index 0..3, shared between all stations on a VAP.) * * This is either a WEP key or a GROUP key. * * Note this will NOT return true if it is a IGTK key. */ bool ieee80211_is_key_global(const struct ieee80211vap *vap, const struct ieee80211_key *key) { return (&vap->iv_nw_keys[0] <= key && key < &vap->iv_nw_keys[IEEE80211_WEP_NKID]); } /* * Determine whether the given key in the given VAP is a unicast key. */ bool ieee80211_is_key_unicast(const struct ieee80211vap *vap, const struct ieee80211_key *key) { /* * This is a short-cut for now; eventually we will need * to support multiple unicast keys, IGTK, etc) so we * will absolutely need to fix the key flags. */ return (!ieee80211_is_key_global(vap, key)); } /** * Determine whether the given control frame is from a known node * and destined to us. * * In some instances a control frame won't have a TA (eg ACKs), so * we should only verify the RA for those. * * @param ni ieee80211_node representing the sender, or BSS node * @param m0 mbuf representing the 802.11 frame. * @returns false if the frame is not a CTL frame (with a warning logged); * true if the frame is from a known sender / valid recipient, * false otherwise. */ bool ieee80211_is_ctl_frame_for_vap(struct ieee80211_node *ni, const struct mbuf *m0) { const struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_frame *wh; uint8_t subtype; wh = mtod(m0, const struct ieee80211_frame *); subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; /* Verify it's a ctl frame. */ KASSERT(IEEE80211_IS_CTL(wh), ("%s: not a CTL frame (fc[0]=0x%04x)", __func__, wh->i_fc[0])); if (!IEEE80211_IS_CTL(wh)) { net80211_vap_printf(vap, "%s: not a control frame (fc[0]=0x%04x)\n", __func__, wh->i_fc[0]); return (false); } /* Verify the TA if present. */ switch (subtype) { case IEEE80211_FC0_SUBTYPE_CTS: case IEEE80211_FC0_SUBTYPE_ACK: /* No TA. */ break; default: /* * Verify TA matches ni->ni_macaddr; for unknown * sources it will be the BSS node and ni->ni_macaddr * will the BSS MAC. */ if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) return (false); break; } /* Verify the RA */ return (IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)); } diff --git a/sys/net80211/ieee80211_adhoc.c b/sys/net80211/ieee80211_adhoc.c index 85baa132ebcf..adb83004d815 100644 --- a/sys/net80211/ieee80211_adhoc.c +++ b/sys/net80211/ieee80211_adhoc.c @@ -1,1059 +1,1059 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2007-2009 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. */ /* * IEEE 802.11 IBSS mode support. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #ifdef IEEE80211_SUPPORT_TDMA #include #endif #include #define IEEE80211_RATE2MBS(r) (((r) & IEEE80211_RATE_VAL) / 2) static void adhoc_vattach(struct ieee80211vap *); static int adhoc_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int adhoc_input(struct ieee80211_node *, struct mbuf *, const struct ieee80211_rx_stats *, int, int); static void adhoc_recv_mgmt(struct ieee80211_node *, struct mbuf *, int subtype, const struct ieee80211_rx_stats *, int, int); static void ahdemo_recv_mgmt(struct ieee80211_node *, struct mbuf *, int subtype, const struct ieee80211_rx_stats *rxs, int, int); static void adhoc_recv_ctl(struct ieee80211_node *, struct mbuf *, int subtype); void ieee80211_adhoc_attach(struct ieee80211com *ic) { ic->ic_vattach[IEEE80211_M_IBSS] = adhoc_vattach; ic->ic_vattach[IEEE80211_M_AHDEMO] = adhoc_vattach; } void ieee80211_adhoc_detach(struct ieee80211com *ic) { } static void adhoc_vdetach(struct ieee80211vap *vap) { } static void adhoc_vattach(struct ieee80211vap *vap) { vap->iv_newstate = adhoc_newstate; vap->iv_input = adhoc_input; if (vap->iv_opmode == IEEE80211_M_IBSS) vap->iv_recv_mgmt = adhoc_recv_mgmt; else vap->iv_recv_mgmt = ahdemo_recv_mgmt; vap->iv_recv_ctl = adhoc_recv_ctl; vap->iv_opdetach = adhoc_vdetach; #ifdef IEEE80211_SUPPORT_TDMA /* * Throw control to tdma support. Note we do this * after setting up our callbacks so it can piggyback * on top of us. */ if (vap->iv_caps & IEEE80211_C_TDMA) ieee80211_tdma_vattach(vap); #endif } static void sta_leave(void *arg, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; if (ni != vap->iv_bss) ieee80211_node_leave(ni); } /* * IEEE80211_M_IBSS+IEEE80211_M_AHDEMO vap state machine handler. */ static int adhoc_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; enum ieee80211_state ostate; IEEE80211_LOCK_ASSERT(vap->iv_ic); ostate = vap->iv_state; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (%d)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg); vap->iv_state = nstate; /* state transition */ if (ostate != IEEE80211_S_SCAN) ieee80211_cancel_scan(vap); /* background scan */ ni = vap->iv_bss; /* NB: no reference held */ 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_RUN: /* beacon miss */ /* purge station table; entries are stale */ ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, sta_leave, NULL); /* fall thru... */ case IEEE80211_S_INIT: if (vap->iv_des_chan != IEEE80211_CHAN_ANYC && !IEEE80211_IS_CHAN_RADAR(vap->iv_des_chan)) { /* * Already have a channel; bypass the * scan and startup immediately. */ ieee80211_create_ibss(vap, ieee80211_ht_adjust_channel(ic, vap->iv_des_chan, vap->iv_flags_ht)); break; } /* * Initiate a scan. We can come here as a result * of an IEEE80211_IOC_SCAN_REQ too in which case * the vap will be marked with IEEE80211_FEXT_SCANREQ * and the scan request parameters will be present * in iv_scanreq. Otherwise we do the default. */ if (vap->iv_flags_ext & IEEE80211_FEXT_SCANREQ) { ieee80211_check_scan(vap, vap->iv_scanreq_flags, vap->iv_scanreq_duration, vap->iv_scanreq_mindwell, vap->iv_scanreq_maxdwell, vap->iv_scanreq_nssid, vap->iv_scanreq_ssid); vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANREQ; } else ieee80211_check_scan_current(vap); break; case IEEE80211_S_SCAN: /* * This can happen because of a change in state * that requires a reset. Trigger a new scan * unless we're in manual roaming mode in which * case an application must issue an explicit request. */ if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) ieee80211_check_scan_current(vap); break; default: goto invalid; } break; case IEEE80211_S_RUN: if (vap->iv_flags & IEEE80211_F_WPA) { /* XXX validate prerequisites */ } switch (ostate) { case IEEE80211_S_INIT: /* * Already have a channel; bypass the * scan and startup immediately. * Note that ieee80211_create_ibss will call * back to do a RUN->RUN state change. */ ieee80211_create_ibss(vap, ieee80211_ht_adjust_channel(ic, ic->ic_curchan, vap->iv_flags_ht)); /* NB: iv_bss is changed on return */ ni = vap->iv_bss; break; case IEEE80211_S_SCAN: #ifdef IEEE80211_DEBUG if (ieee80211_msg_debug(vap)) { ieee80211_note(vap, "synchronized with %s ssid ", ether_sprintf(ni->ni_bssid)); ieee80211_print_essid(vap->iv_bss->ni_essid, ni->ni_esslen); - printf(" channel %d start %uMbit/s\n", + net80211_printf(" channel %d start %uMbit/s\n", ieee80211_chan2ieee(ic, ic->ic_curchan), ieee80211_node_get_txrate_kbit(ni) / 1000); } #endif break; case IEEE80211_S_RUN: /* IBSS merge */ break; default: goto invalid; } /* * 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); /* * Fake association when joining an existing bss. */ if (!IEEE80211_ADDR_EQ(ni->ni_macaddr, vap->iv_myaddr) && ic->ic_newassoc != NULL) ic->ic_newassoc(ni, ostate != IEEE80211_S_RUN); break; case IEEE80211_S_SLEEP: vap->iv_sta_ps(vap, 0); break; default: invalid: IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: unexpected state transition %s -> %s\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); break; } return 0; } /* * 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 1; } return 1; } /* * 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 adhoc_input(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { 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 = 1; /* mbuf need to be tapped. */ uint8_t dir, type, subtype, qos; uint8_t *bssid; int is_hw_decrypted = 0; int has_decrypted = 0; /* * Some devices do hardware decryption all the way through * to pretending the frame wasn't encrypted in the first place. * So, tag it appropriately so it isn't discarded inappropriately. */ if ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_DECRYPTED)) is_hw_decrypted = 1; if (m->m_flags & M_AMPDU_MPDU) { /* * Fastpath for A-MPDU reorder q resubmission. Frames * w/ M_AMPDU_MPDU 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_MPDU 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_DATA; hdrspace = ieee80211_hdrspace(ic, wh); /* XXX optimize? */ goto resubmit_ampdu; } KASSERT(ni != NULL, ("null node")); ni->ni_inact = ni->ni_inact_reload; 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 (!IEEE80211_IS_FC0_CHECK_VER(wh, IEEE80211_FC0_VERSION_0)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "wrong version, fc %02x:%02x", wh->i_fc[0], wh->i_fc[1]); 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) { if (dir != IEEE80211_FC1_DIR_NODS) bssid = wh->i_addr1; else if (type == IEEE80211_FC0_TYPE_CTL) bssid = wh->i_addr1; else { if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "too short (2): len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto out; } bssid = wh->i_addr3; } /* * Validate the bssid. */ if (!(type == IEEE80211_FC0_TYPE_MGT && (subtype == IEEE80211_FC0_SUBTYPE_BEACON || subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP)) && !IEEE80211_ADDR_EQ(bssid, vap->iv_bss->ni_bssid) && !IEEE80211_ADDR_EQ(bssid, ieee80211_vap_get_broadcast_address(vap))) { /* not interested in */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, bssid, NULL, "%s", "not to bss"); vap->iv_stats.is_rx_wrongbss++; goto out; } /* * Data frame, cons up a node when it doesn't * exist. This should probably done after an ACL check. */ if (type == IEEE80211_FC0_TYPE_DATA && ni == vap->iv_bss && !IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) { /* * Beware of frames that come in too early; we * can receive broadcast frames and creating sta * entries will blow up because there is no bss * channel yet. */ if (vap->iv_state != IEEE80211_S_RUN) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "not in RUN state (%s)", ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_rx_badstate++; goto err; } /* * Fake up a node for this newly discovered member * of the IBSS. * * Note: This doesn't "upgrade" the node to 11n; * that will happen after a probe request/response * exchange. */ ni = ieee80211_fakeup_adhoc_node(vap, wh->i_addr2); if (ni == NULL) { /* NB: stat kept for alloc failure */ goto err; } } IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; if (IEEE80211_HAS_SEQ(type, subtype) && IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) { 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++; if (! ieee80211_check_rxseq(ni, wh, bssid, rxs)) goto out; } } 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_NODS) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "incorrect dir 0x%x", dir); vap->iv_stats.is_rx_wrongdir++; goto out; } /* XXX no power-save support */ /* * Handle 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 ((m->m_flags & M_AMPDU) && ieee80211_ampdu_reorder(ni, m, rxs) != 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 (is_hw_decrypted || IEEE80211_IS_PROTECTED(wh)) { 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; } if (ieee80211_crypto_decap(ni, m, hdrspace, &key) == 0) { /* 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_PROTECTED; has_decrypted = 1; } 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_DATA) qos = ieee80211_getqos(wh)[0]; else qos = 0; /* * Next up, any fragmentation. */ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { m = ieee80211_defrag(ni, m, hdrspace, has_decrypted); 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 (!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 (ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); need_tap = 0; /* * Finally, strip the 802.11 header. */ m = ieee80211_decap(vap, m, hdrspace, qos); 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; } if (!(qos & IEEE80211_QOS_AMSDU)) eh = mtod(m, struct ether_header *); else eh = NULL; 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 == NULL || eh->ether_type != htons(ETHERTYPE_PAE)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "unauthorized or " "unknown port: ether type 0x%x len %u", eh == NULL ? -1 : 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) && ((has_decrypted == 0) && (m->m_flags & M_WEP) == 0) && (is_hw_decrypted == 0) && (eh == NULL || 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 { #ifdef IEEE80211_SUPPORT_SUPERG m = ieee80211_decap_fastframe(vap, ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; #endif } if (dir == IEEE80211_FC1_DIR_DSTODS && ni->ni_wdsvap != NULL) ieee80211_deliver_data(ni->ni_wdsvap, ni, m); else 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) && doprint(vap, subtype)) || ieee80211_msg_dumppkts(vap)) { net80211_vap_printf(vap, "received %s from %s rssi %d\n", ieee80211_mgt_subtype_name(subtype), ether_sprintf(wh->i_addr2), rssi); } #endif if (IEEE80211_IS_PROTECTED(wh)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "WEP set but not permitted"); vap->iv_stats.is_rx_mgtdiscard++; /* XXX */ goto out; } vap->iv_recv_mgmt(ni, m, subtype, rxs, rssi, nf); goto out; case IEEE80211_FC0_TYPE_CTL: vap->iv_stats.is_rx_ctl++; IEEE80211_NODE_STAT(ni, rx_ctrl); if (ieee80211_is_ctl_frame_for_vap(ni, m)) vap->iv_recv_ctl(ni, m, subtype); goto out; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "bad", "frame type 0x%x", type); /* should not come here */ break; } err: if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); out: if (m != NULL) { if (need_tap && ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); m_freem(m); } return type; } static int is11bclient(const uint8_t *rates, const uint8_t *xrates) { static const uint32_t brates = (1<<2*1)|(1<<2*2)|(1<<11)|(1<<2*11); int i; /* NB: the 11b clients we care about will not have xrates */ if (xrates != NULL || rates == NULL) return 0; for (i = 0; i < rates[1]; i++) { int r = rates[2+i] & IEEE80211_RATE_VAL; if (r > 2*11 || ((1<ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_channel *rxchan = ic->ic_curchan; struct ieee80211_frame *wh; uint8_t *frm, *efrm; uint8_t *ssid, *rates, *xrates; #if 0 int ht_state_change = 0; #endif wh = mtod(m0, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; efrm = mtod(m0, uint8_t *) + m0->m_len; IEEE80211_DPRINTF(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_DEBUG, "%s: recv mgmt frame, addr2=%6D, ni=%p (%6D) fc=%.02x %.02x\n", __func__, wh->i_addr2, ":", ni, ni->ni_macaddr, ":", wh->i_fc[0], wh->i_fc[1]); switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: case IEEE80211_FC0_SUBTYPE_BEACON: { struct ieee80211_scanparams scan; struct ieee80211_channel *c; /* * We process beacon/probe response * frames to discover neighbors. */ if (rxs != NULL) { c = ieee80211_lookup_channel_rxstatus(vap, rxs); if (c != NULL) rxchan = c; } if (ieee80211_parse_beacon(ni, m0, rxchan, &scan) != 0) return; /* * Count frame now that we know it's to be processed. */ if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) { vap->iv_stats.is_rx_beacon++; /* XXX remove */ IEEE80211_NODE_STAT(ni, rx_beacons); } else IEEE80211_NODE_STAT(ni, rx_proberesp); /* * If scanning, just pass information to the scan module. */ if (ic->ic_flags & IEEE80211_F_SCAN) { if (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN) { /* * Actively scanning a channel marked passive; * send a probe request now that we know there * is 802.11 traffic present. * * XXX check if the beacon we recv'd gives * us what we need and suppress the probe req */ ieee80211_probe_curchan(vap, true); ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN; } ieee80211_add_scan(vap, rxchan, &scan, wh, subtype, rssi, nf); return; } if (scan.capinfo & IEEE80211_CAPINFO_IBSS) { if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) { /* * Create a new entry in the neighbor table. * * XXX TODO: * * Here we're not scanning; so if we have an * SSID then make sure it matches our SSID. * Otherwise this code will match on all IBSS * beacons/probe requests for all SSIDs, * filling the node table with nodes that * aren't ours. */ if (ieee80211_ibss_node_check_new(ni, &scan)) { ni = ieee80211_add_neighbor(vap, wh, &scan); /* * Send a probe request so we announce 11n * capabilities. */ ieee80211_send_probereq(ni, /* node */ vap->iv_myaddr, /* SA */ ni->ni_macaddr, /* DA */ vap->iv_bss->ni_bssid, /* BSSID */ vap->iv_bss->ni_essid, vap->iv_bss->ni_esslen); /* SSID */ } else ni = NULL; /* * Send a probe request so we announce 11n * capabilities. * * Don't do this if we're scanning. */ if (! (ic->ic_flags & IEEE80211_F_SCAN)) ieee80211_send_probereq(ni, /* node */ vap->iv_myaddr, /* SA */ ni->ni_macaddr, /* DA */ vap->iv_bss->ni_bssid, /* BSSID */ vap->iv_bss->ni_essid, vap->iv_bss->ni_esslen); /* SSID */ } else if (ni->ni_capinfo == 0) { /* * Update faked node created on transmit. * Note this also updates the tsf. */ ieee80211_init_neighbor(ni, wh, &scan); /* * Send a probe request so we announce 11n * capabilities. */ ieee80211_send_probereq(ni, /* node */ vap->iv_myaddr, /* SA */ ni->ni_macaddr, /* DA */ vap->iv_bss->ni_bssid, /* BSSID */ vap->iv_bss->ni_essid, vap->iv_bss->ni_esslen); /* SSID */ } else { /* * Record tsf for potential resync. */ memcpy(ni->ni_tstamp.data, scan.tstamp, sizeof(ni->ni_tstamp)); } /* * This isn't enabled yet - otherwise it would * update the HT parameters and channel width * from any node, which could lead to lots of * strange behaviour if the 11n nodes aren't * exactly configured to match. */ #if 0 if (scan.htcap != NULL && scan.htinfo != NULL && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { ieee80211_ht_updateparams(ni, scan.htcap, scan.htinfo)); if (ieee80211_ht_updateparams_final(ni, scan.htcap, scan.htinfo)) ht_state_change = 1; } /* XXX same for VHT? */ #endif if (ni != NULL) { IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; } /* * Same here - the channel width change should * be applied to the specific peer node, not * to the ic. Ie, the interface configuration * should stay in its current channel width; * but it should change the rate control and * any queued frames for the given node only. * * Since there's no (current) way to inform * the driver that a channel width change has * occurred for a single node, just stub this * out. */ #if 0 if (ht_state_change) ieee80211_update_chw(ic); #endif } break; } case IEEE80211_FC0_SUBTYPE_PROBE_REQ: if (vap->iv_state != IEEE80211_S_RUN) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "wrong state %s", ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_rx_mgtdiscard++; return; } if (IEEE80211_IS_MULTICAST(wh->i_addr2)) { /* frame must be directed */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not unicast"); vap->iv_stats.is_rx_mgtdiscard++; /* XXX stat */ return; } /* * prreq frame format * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates */ ssid = rates = xrates = NULL; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); switch (*frm) { case IEEE80211_ELEMID_SSID: ssid = frm; break; case IEEE80211_ELEMID_RATES: rates = frm; break; case IEEE80211_ELEMID_XRATES: xrates = frm; break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE, return); if (xrates != NULL) IEEE80211_VERIFY_ELEMENT(xrates, IEEE80211_RATE_MAXSIZE - rates[1], return); IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN, return); IEEE80211_VERIFY_SSID(vap->iv_bss, ssid, return); if ((vap->iv_flags & IEEE80211_F_HIDESSID) && ssid[1] == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "no ssid with ssid suppression enabled"); vap->iv_stats.is_rx_ssidmismatch++; /*XXX*/ return; } /* XXX find a better class or define it's own */ IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, wh->i_addr2, "%s", "recv probe req"); /* * Some legacy 11b clients cannot hack a complete * probe response frame. When the request includes * only a bare-bones rate set, communicate this to * the transmit side. */ ieee80211_send_proberesp(vap, wh->i_addr2, is11bclient(rates, xrates) ? IEEE80211_SEND_LEGACY_11B : 0); /* * Note: we don't benefit from stashing the probe request * IEs away to use for IBSS negotiation, because we * typically don't get all of the IEs. */ break; case IEEE80211_FC0_SUBTYPE_ACTION: case IEEE80211_FC0_SUBTYPE_ACTION_NOACK: if ((ni == vap->iv_bss) && !IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "unknown node"); vap->iv_stats.is_rx_mgtdiscard++; } else if (!IEEE80211_ADDR_EQ(vap->iv_myaddr, wh->i_addr1) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_DEBUG, wh, NULL, "%s", "not for us"); vap->iv_stats.is_rx_mgtdiscard++; } else if (vap->iv_state != IEEE80211_S_RUN) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_DEBUG, wh, NULL, "wrong state %s", ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_rx_mgtdiscard++; } else { if (ieee80211_parse_action(ni, m0) == 0) (void)ic->ic_recv_action(ni, wh, frm, efrm); } break; case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: case IEEE80211_FC0_SUBTYPE_TIMING_ADV: case IEEE80211_FC0_SUBTYPE_ATIM: case IEEE80211_FC0_SUBTYPE_DISASSOC: case IEEE80211_FC0_SUBTYPE_AUTH: case IEEE80211_FC0_SUBTYPE_DEAUTH: IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not handled"); vap->iv_stats.is_rx_mgtdiscard++; break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "mgt", "subtype 0x%x not handled", subtype); vap->iv_stats.is_rx_badsubtype++; break; } } #undef IEEE80211_VERIFY_LENGTH #undef IEEE80211_VERIFY_ELEMENT static void ahdemo_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; /* * Process management frames when scanning; useful for doing * a site-survey. */ if (ic->ic_flags & IEEE80211_F_SCAN) adhoc_recv_mgmt(ni, m0, subtype, rxs, rssi, nf); else { #ifdef IEEE80211_DEBUG struct ieee80211_frame *wh; wh = mtod(m0, struct ieee80211_frame *); #endif switch (subtype) { case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: case IEEE80211_FC0_SUBTYPE_PROBE_REQ: case IEEE80211_FC0_SUBTYPE_PROBE_RESP: case IEEE80211_FC0_SUBTYPE_TIMING_ADV: case IEEE80211_FC0_SUBTYPE_BEACON: case IEEE80211_FC0_SUBTYPE_ATIM: case IEEE80211_FC0_SUBTYPE_DISASSOC: case IEEE80211_FC0_SUBTYPE_AUTH: case IEEE80211_FC0_SUBTYPE_DEAUTH: case IEEE80211_FC0_SUBTYPE_ACTION: case IEEE80211_FC0_SUBTYPE_ACTION_NOACK: IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not handled"); vap->iv_stats.is_rx_mgtdiscard++; break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "mgt", "subtype 0x%x not handled", subtype); vap->iv_stats.is_rx_badsubtype++; break; } } } static void adhoc_recv_ctl(struct ieee80211_node *ni, struct mbuf *m, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_BAR: ieee80211_recv_bar(ni, m); break; } } diff --git a/sys/net80211/ieee80211_alq.c b/sys/net80211/ieee80211_alq.c index c25ef11163d0..95a607a12265 100644 --- a/sys/net80211/ieee80211_alq.c +++ b/sys/net80211/ieee80211_alq.c @@ -1,174 +1,174 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 Adrian Chadd, Xenion Lty Ltd * 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. */ /* * net80211 fast-logging support, primarily for debugging. * * This implements a single debugging queue which includes * per-device enumeration where needed. */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct alq *ieee80211_alq; static int ieee80211_alq_lost; static int ieee80211_alq_logged; static char ieee80211_alq_logfile[MAXPATHLEN] = "/tmp/net80211.log"; static unsigned int ieee80211_alq_qsize = 64*1024; static int ieee80211_alq_setlogging(int enable) { int error; if (enable) { if (ieee80211_alq) alq_close(ieee80211_alq); error = alq_open(&ieee80211_alq, ieee80211_alq_logfile, curthread->td_ucred, ALQ_DEFAULT_CMODE, ieee80211_alq_qsize, 0); ieee80211_alq_lost = 0; ieee80211_alq_logged = 0; - printf("net80211: logging to %s enabled; " + net80211_printf("net80211: logging to %s enabled; " "struct size %d bytes\n", ieee80211_alq_logfile, (int) sizeof(struct ieee80211_alq_rec)); } else { if (ieee80211_alq) alq_close(ieee80211_alq); ieee80211_alq = NULL; - printf("net80211: logging disabled\n"); + net80211_printf("net80211: logging disabled\n"); error = 0; } return (error); } static int sysctl_ieee80211_alq_log(SYSCTL_HANDLER_ARGS) { int error, enable; enable = (ieee80211_alq != NULL); error = sysctl_handle_int(oidp, &enable, 0, req); if (error || !req->newptr) return (error); else return (ieee80211_alq_setlogging(enable)); } SYSCTL_PROC(_net_wlan, OID_AUTO, alq, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, 0, 0, sysctl_ieee80211_alq_log, "I", "Enable net80211 alq logging"); SYSCTL_INT(_net_wlan, OID_AUTO, alq_size, CTLFLAG_RW, &ieee80211_alq_qsize, 0, "In-memory log size (bytes)"); SYSCTL_INT(_net_wlan, OID_AUTO, alq_lost, CTLFLAG_RW, &ieee80211_alq_lost, 0, "Debugging operations not logged"); SYSCTL_INT(_net_wlan, OID_AUTO, alq_logged, CTLFLAG_RW, &ieee80211_alq_logged, 0, "Debugging operations logged"); static struct ale * ieee80211_alq_get(size_t len) { struct ale *ale; ale = alq_getn(ieee80211_alq, len + sizeof(struct ieee80211_alq_rec), ALQ_NOWAIT); if (!ale) ieee80211_alq_lost++; else ieee80211_alq_logged++; return ale; } int ieee80211_alq_log(struct ieee80211com *ic, struct ieee80211vap *vap, uint32_t op, uint32_t flags, uint16_t srcid, const uint8_t *src, size_t len) { struct ale *ale; struct ieee80211_alq_rec *r; char *dst; /* Don't log if we're disabled */ if (ieee80211_alq == NULL) return (0); if (len > IEEE80211_ALQ_MAX_PAYLOAD) return (ENOMEM); ale = ieee80211_alq_get(len); if (! ale) return (ENOMEM); r = (struct ieee80211_alq_rec *) ale->ae_data; dst = ((char *) r) + sizeof(struct ieee80211_alq_rec); r->r_timestamp = htobe64(ticks); if (vap != NULL) { r->r_wlan = htobe16(vap->iv_ifp->if_dunit); } else { r->r_wlan = 0xffff; } r->r_src = htobe16(srcid); r->r_flags = htobe32(flags); r->r_op = htobe32(op); r->r_len = htobe32(len + sizeof(struct ieee80211_alq_rec)); r->r_threadid = htobe32((uint32_t) curthread->td_tid); if (src != NULL) memcpy(dst, src, len); alq_post(ieee80211_alq, ale); return (0); } diff --git a/sys/net80211/ieee80211_crypto.c b/sys/net80211/ieee80211_crypto.c index 6b636da9fa2c..1e63ca46f28f 100644 --- a/sys/net80211/ieee80211_crypto.c +++ b/sys/net80211/ieee80211_crypto.c @@ -1,1014 +1,1014 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-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 /* * IEEE 802.11 generic crypto support. */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include /* XXX ETHER_HDR_LEN */ #include MALLOC_DEFINE(M_80211_CRYPTO, "80211crypto", "802.11 crypto state"); static int _ieee80211_crypto_delkey(struct ieee80211vap *, struct ieee80211_key *); /* * Table of registered cipher modules. */ static const struct ieee80211_cipher *ciphers[IEEE80211_CIPHER_MAX]; /* * Default "null" key management routines. */ static int null_key_alloc(struct ieee80211vap *vap, struct ieee80211_key *k, ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix) { if (!ieee80211_is_key_global(vap, k)) { /* * Not in the global key table, the driver should handle this * by allocating a slot in the h/w key table/cache. In * lieu of that return key slot 0 for any unicast key * request. We disallow the request if this is a group key. * This default policy does the right thing for legacy hardware * with a 4 key table. It also handles devices that pass * packets through untouched when marked with the WEP bit * and key index 0. */ if (k->wk_flags & IEEE80211_KEY_GROUP) return 0; *keyix = 0; /* NB: use key index 0 for ucast key */ } else { *keyix = ieee80211_crypto_get_key_wepidx(vap, k); } *rxkeyix = IEEE80211_KEYIX_NONE; /* XXX maybe *keyix? */ return 1; } static int null_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k) { return 1; } static int null_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k) { return 1; } static void null_key_update(struct ieee80211vap *vap) {} /* * Write-arounds for common operations. */ static __inline void cipher_detach(struct ieee80211_key *key) { key->wk_cipher->ic_detach(key); } static __inline void * cipher_attach(struct ieee80211vap *vap, struct ieee80211_key *key) { return key->wk_cipher->ic_attach(vap, key); } /* * Wrappers for driver key management methods. */ static __inline int dev_key_alloc(struct ieee80211vap *vap, struct ieee80211_key *key, ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix) { return vap->iv_key_alloc(vap, key, keyix, rxkeyix); } static __inline int dev_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *key) { return vap->iv_key_delete(vap, key); } static __inline int dev_key_set(struct ieee80211vap *vap, const struct ieee80211_key *key) { return vap->iv_key_set(vap, key); } /* * Setup crypto support for a device/shared instance. */ void ieee80211_crypto_attach(struct ieee80211com *ic) { /* NB: we assume everything is pre-zero'd */ ciphers[IEEE80211_CIPHER_NONE] = &ieee80211_cipher_none; /* * Default set of net80211 supported ciphers. * * These are the default set that all drivers are expected to * support, either/or in hardware and software. * * Drivers can add their own support to this and the * hardware cipher list (ic_cryptocaps.) */ ic->ic_sw_cryptocaps = IEEE80211_CRYPTO_WEP | IEEE80211_CRYPTO_TKIP | IEEE80211_CRYPTO_AES_CCM; /* * Default set of key management types supported by net80211. * * These are supported by software net80211 and announced/ * driven by hostapd + wpa_supplicant. * * Drivers doing full supplicant offload must not set * anything here. * * Note that IEEE80211_C_WPA1 and IEEE80211_C_WPA2 are the * "old" style way of drivers announcing key management * capabilities. There are many, many more key management * suites in 802.11-2016 (see 9.4.2.25.3 - AKM suites.) * For now they still need to be set - these flags are checked * when assembling a beacon to reserve space for the WPA * vendor IE (WPA 1) and RSN IE (WPA 2). */ ic->ic_sw_keymgmtcaps = 0; } /* * Teardown crypto support. */ void ieee80211_crypto_detach(struct ieee80211com *ic) { } /* * Set the supported ciphers for software encryption. */ void ieee80211_crypto_set_supported_software_ciphers(struct ieee80211com *ic, uint32_t cipher_set) { ic->ic_sw_cryptocaps = cipher_set; } /* * Set the supported ciphers for hardware encryption. */ void ieee80211_crypto_set_supported_hardware_ciphers(struct ieee80211com *ic, uint32_t cipher_set) { ic->ic_cryptocaps = cipher_set; } /* * Set the supported software key management by the driver. * * These are the key management suites that are supported via * the driver via hostapd/wpa_supplicant. * * Key management which is completely offloaded (ie, the supplicant * runs in hardware/firmware) must not be set here. */ void ieee80211_crypto_set_supported_driver_keymgmt(struct ieee80211com *ic, uint32_t keymgmt_set) { ic->ic_sw_keymgmtcaps = keymgmt_set; } /* * Setup crypto support for a vap. */ void ieee80211_crypto_vattach(struct ieee80211vap *vap) { int i; /* NB: we assume everything is pre-zero'd */ vap->iv_max_keyix = IEEE80211_WEP_NKID; vap->iv_def_txkey = IEEE80211_KEYIX_NONE; for (i = 0; i < IEEE80211_WEP_NKID; i++) ieee80211_crypto_resetkey(vap, &vap->iv_nw_keys[i], IEEE80211_KEYIX_NONE); /* * Initialize the driver key support routines to noop entries. * This is useful especially for the cipher test modules. */ vap->iv_key_alloc = null_key_alloc; vap->iv_key_set = null_key_set; vap->iv_key_delete = null_key_delete; vap->iv_key_update_begin = null_key_update; vap->iv_key_update_end = null_key_update; } /* * Teardown crypto support for a vap. */ void ieee80211_crypto_vdetach(struct ieee80211vap *vap) { ieee80211_crypto_delglobalkeys(vap); } /* * Register a crypto cipher module. */ void ieee80211_crypto_register(const struct ieee80211_cipher *cip) { if (cip->ic_cipher >= IEEE80211_CIPHER_MAX) { - printf("%s: cipher %s has an invalid cipher index %u\n", + net80211_printf("%s: cipher %s has an invalid cipher index %u\n", __func__, cip->ic_name, cip->ic_cipher); return; } if (ciphers[cip->ic_cipher] != NULL && ciphers[cip->ic_cipher] != cip) { - printf("%s: cipher %s registered with a different template\n", + net80211_printf("%s: cipher %s registered with a different template\n", __func__, cip->ic_name); return; } ciphers[cip->ic_cipher] = cip; } /* * Unregister a crypto cipher module. */ void ieee80211_crypto_unregister(const struct ieee80211_cipher *cip) { if (cip->ic_cipher >= IEEE80211_CIPHER_MAX) { - printf("%s: cipher %s has an invalid cipher index %u\n", + net80211_printf("%s: cipher %s has an invalid cipher index %u\n", __func__, cip->ic_name, cip->ic_cipher); return; } if (ciphers[cip->ic_cipher] != NULL && ciphers[cip->ic_cipher] != cip) { - printf("%s: cipher %s registered with a different template\n", + net80211_printf("%s: cipher %s registered with a different template\n", __func__, cip->ic_name); return; } /* NB: don't complain about not being registered */ /* XXX disallow if references */ ciphers[cip->ic_cipher] = NULL; } int ieee80211_crypto_available(u_int cipher) { return cipher < IEEE80211_CIPHER_MAX && ciphers[cipher] != NULL; } /* XXX well-known names! */ static const char *cipher_modnames[IEEE80211_CIPHER_MAX] = { [IEEE80211_CIPHER_WEP] = "wlan_wep", [IEEE80211_CIPHER_TKIP] = "wlan_tkip", [IEEE80211_CIPHER_AES_OCB] = "wlan_aes_ocb", [IEEE80211_CIPHER_AES_CCM] = "wlan_ccmp", [IEEE80211_CIPHER_TKIPMIC] = "#4", /* NB: reserved */ [IEEE80211_CIPHER_CKIP] = "wlan_ckip", [IEEE80211_CIPHER_NONE] = "wlan_none", [IEEE80211_CIPHER_AES_CCM_256] = "wlan_ccmp", [IEEE80211_CIPHER_BIP_CMAC_128] = "wlan_bip_cmac", [IEEE80211_CIPHER_BIP_CMAC_256] = "wlan_bip_cmac", [IEEE80211_CIPHER_BIP_GMAC_128] = "wlan_bip_gmac", [IEEE80211_CIPHER_BIP_GMAC_256] = "wlan_bip_gmac", [IEEE80211_CIPHER_AES_GCM_128] = "wlan_gcmp", [IEEE80211_CIPHER_AES_GCM_256] = "wlan_gcmp", }; /* NB: there must be no overlap between user-supplied and device-owned flags */ CTASSERT((IEEE80211_KEY_COMMON & IEEE80211_KEY_DEVICE) == 0); /* * Establish a relationship between the specified key and cipher * and, if necessary, allocate a hardware index from the driver. * Note that when a fixed key index is required it must be specified. * * This must be the first call applied to a key; all the other key * routines assume wk_cipher is setup. * * Locking must be handled by the caller using: * ieee80211_key_update_begin(vap); * ieee80211_key_update_end(vap); */ int ieee80211_crypto_newkey(struct ieee80211vap *vap, int cipher, int flags, struct ieee80211_key *key) { struct ieee80211com *ic = vap->iv_ic; const struct ieee80211_cipher *cip; ieee80211_keyix keyix, rxkeyix; void *keyctx; int oflags; IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: cipher %u flags 0x%x keyix %u\n", __func__, cipher, flags, key->wk_keyix); /* * Validate cipher and set reference to cipher routines. */ if (cipher >= IEEE80211_CIPHER_MAX) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: invalid cipher %u\n", __func__, cipher); vap->iv_stats.is_crypto_badcipher++; return 0; } cip = ciphers[cipher]; if (cip == NULL) { /* * Auto-load cipher module if we have a well-known name * for it. It might be better to use string names rather * than numbers and craft a module name based on the cipher * name; e.g. wlan_cipher_. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: unregistered cipher %u, load module %s\n", __func__, cipher, cipher_modnames[cipher]); ieee80211_load_module(cipher_modnames[cipher]); /* * If cipher module loaded it should immediately * call ieee80211_crypto_register which will fill * in the entry in the ciphers array. */ cip = ciphers[cipher]; if (cip == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: unable to load cipher %u, module %s\n", __func__, cipher, cipher_modnames[cipher]); vap->iv_stats.is_crypto_nocipher++; return 0; } } oflags = key->wk_flags; flags &= IEEE80211_KEY_COMMON; /* NB: preserve device attributes */ flags |= (oflags & IEEE80211_KEY_DEVICE); /* * If the hardware does not support the cipher then * fallback to a host-based implementation. */ if ((ic->ic_cryptocaps & (1<ic_name); flags |= IEEE80211_KEY_SWCRYPT; } /* * Check if the software cipher is available; if not then * fail it early. * * Some devices do not support all ciphers in software * (for example they don't support a "raw" data path.) */ if ((flags & IEEE80211_KEY_SWCRYPT) && (ic->ic_sw_cryptocaps & (1<ic_name); vap->iv_stats.is_crypto_swcipherfail++; return (0); } /* * Hardware TKIP with software MIC is an important * combination; we handle it by flagging each key, * the cipher modules honor it. */ if (cipher == IEEE80211_CIPHER_TKIP && (ic->ic_cryptocaps & IEEE80211_CRYPTO_TKIPMIC) == 0) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: no h/w support for TKIP MIC, falling back to s/w\n", __func__); flags |= IEEE80211_KEY_SWMIC; } /* * Bind cipher to key instance. Note we do this * after checking the device capabilities so the * cipher module can optimize space usage based on * whether or not it needs to do the cipher work. */ if (key->wk_cipher != cip || key->wk_flags != flags) { /* * Fillin the flags so cipher modules can see s/w * crypto requirements and potentially allocate * different state and/or attach different method * pointers. */ key->wk_flags = flags; keyctx = cip->ic_attach(vap, key); if (keyctx == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: unable to attach cipher %s\n", __func__, cip->ic_name); key->wk_flags = oflags; /* restore old flags */ vap->iv_stats.is_crypto_attachfail++; return 0; } cipher_detach(key); key->wk_cipher = cip; /* XXX refcnt? */ key->wk_private = keyctx; } /* * Ask the driver for a key index if we don't have one. * Note that entries in the global key table always have * an index; this means it's safe to call this routine * for these entries just to setup the reference to the * cipher template. Note also that when using software * crypto we also call the driver to give us a key index. */ if ((key->wk_flags & IEEE80211_KEY_DEVKEY) == 0) { if (!dev_key_alloc(vap, key, &keyix, &rxkeyix)) { /* * Unable to setup driver state. */ vap->iv_stats.is_crypto_keyfail++; IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: unable to setup cipher %s\n", __func__, cip->ic_name); return 0; } if (key->wk_flags != flags) { /* * Driver overrode flags we setup; typically because * resources were unavailable to handle _this_ key. * Re-attach the cipher context to allow cipher * modules to handle differing requirements. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: driver override for cipher %s, flags " "%b -> %b\n", __func__, cip->ic_name, oflags, IEEE80211_KEY_BITS, key->wk_flags, IEEE80211_KEY_BITS); keyctx = cip->ic_attach(vap, key); if (keyctx == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: unable to attach cipher %s with " "flags %b\n", __func__, cip->ic_name, key->wk_flags, IEEE80211_KEY_BITS); key->wk_flags = oflags; /* restore old flags */ vap->iv_stats.is_crypto_attachfail++; return 0; } cipher_detach(key); key->wk_cipher = cip; /* XXX refcnt? */ key->wk_private = keyctx; } key->wk_keyix = keyix; key->wk_rxkeyix = rxkeyix; key->wk_flags |= IEEE80211_KEY_DEVKEY; } return 1; } /* * Remove the key (no locking, for internal use). */ static int _ieee80211_crypto_delkey(struct ieee80211vap *vap, struct ieee80211_key *key) { KASSERT(key->wk_cipher != NULL, ("No cipher!")); IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: %s keyix %u flags %b rsc %ju tsc %ju len %u\n", __func__, key->wk_cipher->ic_name, key->wk_keyix, key->wk_flags, IEEE80211_KEY_BITS, key->wk_keyrsc[IEEE80211_NONQOS_TID], key->wk_keytsc, key->wk_keylen); if (key->wk_flags & IEEE80211_KEY_DEVKEY) { /* * Remove hardware entry. */ /* XXX key cache */ if (!dev_key_delete(vap, key)) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: driver did not delete key index %u\n", __func__, key->wk_keyix); vap->iv_stats.is_crypto_delkey++; /* XXX recovery? */ } } cipher_detach(key); memset(key, 0, sizeof(*key)); ieee80211_crypto_resetkey(vap, key, IEEE80211_KEYIX_NONE); return 1; } /* * Remove the specified key. */ int ieee80211_crypto_delkey(struct ieee80211vap *vap, struct ieee80211_key *key) { int status; ieee80211_key_update_begin(vap); status = _ieee80211_crypto_delkey(vap, key); ieee80211_key_update_end(vap); return status; } /* * Clear the global key table. */ void ieee80211_crypto_delglobalkeys(struct ieee80211vap *vap) { int i; ieee80211_key_update_begin(vap); for (i = 0; i < IEEE80211_WEP_NKID; i++) (void) _ieee80211_crypto_delkey(vap, &vap->iv_nw_keys[i]); ieee80211_key_update_end(vap); } /* * Set the contents of the specified key. * * Locking must be handled by the caller using: * ieee80211_key_update_begin(vap); * ieee80211_key_update_end(vap); */ int ieee80211_crypto_setkey(struct ieee80211vap *vap, struct ieee80211_key *key) { const struct ieee80211_cipher *cip = key->wk_cipher; KASSERT(cip != NULL, ("No cipher!")); IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: %s keyix %u flags %b mac %s rsc %ju tsc %ju len %u\n", __func__, cip->ic_name, key->wk_keyix, key->wk_flags, IEEE80211_KEY_BITS, ether_sprintf(key->wk_macaddr), key->wk_keyrsc[IEEE80211_NONQOS_TID], key->wk_keytsc, key->wk_keylen); if ((key->wk_flags & IEEE80211_KEY_DEVKEY) == 0) { /* XXX nothing allocated, should not happen */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: no device key setup done; should not happen!\n", __func__); vap->iv_stats.is_crypto_setkey_nokey++; return 0; } /* * Give cipher a chance to validate key contents. * XXX should happen before modifying state. */ if (!cip->ic_setkey(key)) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_CRYPTO, "%s: cipher %s rejected key index %u len %u flags %b\n", __func__, cip->ic_name, key->wk_keyix, key->wk_keylen, key->wk_flags, IEEE80211_KEY_BITS); vap->iv_stats.is_crypto_setkey_cipher++; return 0; } return dev_key_set(vap, key); } /* * Return index if the key is a WEP key (0..3); -1 otherwise. * * This is different to "get_keyid" which defaults to returning * 0 for unicast keys; it assumes that it won't be used for WEP. */ int ieee80211_crypto_get_key_wepidx(const struct ieee80211vap *vap, const struct ieee80211_key *k) { if (ieee80211_is_key_global(vap, k)) { return (k - vap->iv_nw_keys); } return (-1); } /* * Note: only supports a single unicast key (0). */ uint8_t ieee80211_crypto_get_keyid(struct ieee80211vap *vap, struct ieee80211_key *k) { if (ieee80211_is_key_global(vap, k)) { return (k - vap->iv_nw_keys); } return (0); } struct ieee80211_key * ieee80211_crypto_get_txkey(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_frame *wh; /* * Multicast traffic always uses the multicast key. * * Historically we would fall back to the default * transmit key if there was no unicast key. This * behaviour was documented up to IEEE Std 802.11-2016, * 12.9.2.2 Per-MSDU/Per-A-MSDU Tx pseudocode, in the * 'else' case but is no longer in later versions of * the standard. Additionally falling back to the * group key for unicast was a security risk. */ wh = mtod(m, struct ieee80211_frame *); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { if (vap->iv_def_txkey == IEEE80211_KEYIX_NONE) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, wh->i_addr1, "no default transmit key (%s) deftxkey %u", __func__, vap->iv_def_txkey); vap->iv_stats.is_tx_nodefkey++; return NULL; } return &vap->iv_nw_keys[vap->iv_def_txkey]; } if (IEEE80211_KEY_UNDEFINED(&ni->ni_ucastkey)) return NULL; return &ni->ni_ucastkey; } /* * Add privacy headers appropriate for the specified key. */ struct ieee80211_key * ieee80211_crypto_encap(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211_key *k; const struct ieee80211_cipher *cip; if ((k = ieee80211_crypto_get_txkey(ni, m)) != NULL) { cip = k->wk_cipher; return (cip->ic_encap(k, m) ? k : NULL); } return NULL; } /* * Validate and strip privacy headers (and trailer) for a * received frame that has the WEP/Privacy bit set. */ int ieee80211_crypto_decap(struct ieee80211_node *ni, struct mbuf *m, int hdrlen, struct ieee80211_key **key) { #define IEEE80211_WEP_HDRLEN (IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN) #define IEEE80211_WEP_MINLEN \ (sizeof(struct ieee80211_frame) + \ IEEE80211_WEP_HDRLEN + IEEE80211_WEP_CRCLEN) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_key *k; struct ieee80211_frame *wh; const struct ieee80211_rx_stats *rxs; const struct ieee80211_cipher *cip; uint8_t keyid; /* * Check for hardware decryption and IV stripping. * If the IV is stripped then we definitely can't find a key. * Set the key to NULL but return true; upper layers * will need to handle a NULL key for a successful * decrypt. */ rxs = ieee80211_get_rx_params_ptr(m); if ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_DECRYPTED)) { if (rxs->c_pktflags & IEEE80211_RX_F_IV_STRIP) { /* * Hardware decrypted, IV stripped. * We can't find a key with a stripped IV. * Return successful. */ *key = NULL; return (1); } } /* NB: this minimum size data frame could be bigger */ if (m->m_pkthdr.len < IEEE80211_WEP_MINLEN) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_ANY, "%s: WEP data frame too short, len %u\n", __func__, m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; /* XXX need unique stat? */ *key = NULL; return (0); } /* * Locate the key. If unicast and there is no unicast * key then we fall back to the key id in the header. * This assumes unicast keys are only configured when * the key id in the header is meaningless (typically 0). */ wh = mtod(m, struct ieee80211_frame *); m_copydata(m, hdrlen + IEEE80211_WEP_IVLEN, sizeof(keyid), &keyid); if (IEEE80211_IS_MULTICAST(wh->i_addr1) || IEEE80211_KEY_UNDEFINED(&ni->ni_ucastkey)) k = &vap->iv_nw_keys[keyid >> 6]; else k = &ni->ni_ucastkey; /* * Ensure crypto header is contiguous and long enough for all * decap work. */ cip = k->wk_cipher; if (m->m_len < hdrlen + cip->ic_header) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, wh->i_addr2, "frame is too short (%d < %u) for crypto decap", cip->ic_name, m->m_len, hdrlen + cip->ic_header); vap->iv_stats.is_rx_tooshort++; *key = NULL; return (0); } /* * Attempt decryption. * * If we fail then don't return the key - return NULL * and an error. */ if (cip->ic_decap(k, m, hdrlen)) { /* success */ *key = k; return (1); } /* Failure */ *key = NULL; return (0); #undef IEEE80211_WEP_MINLEN #undef IEEE80211_WEP_HDRLEN } /** * @brief Check and remove any post-defragmentation MIC from an MSDU. * * This is called after defragmentation. Crypto types that implement * a MIC/ICV check per MSDU will not implement this function. * * As an example, TKIP decapsulation covers both MIC/ICV checks per * MPDU (the "WEP" ICV) and then a Michael MIC verification on the * defragmented MSDU. Please see 802.11-2020 12.5.2.1.3 (TKIP decapsulation) * for more information. * * @param vap the current VAP * @param k the current key * @param m the mbuf representing the MSDU * @param f set to 1 to force a MSDU MIC check, even if HW decrypted * @returns 0 if error / MIC check failed, 1 if OK */ int ieee80211_crypto_demic(struct ieee80211vap *vap, struct ieee80211_key *k, struct mbuf *m, int force) { const struct ieee80211_cipher *cip; const struct ieee80211_rx_stats *rxs; struct ieee80211_frame *wh; rxs = ieee80211_get_rx_params_ptr(m); wh = mtod(m, struct ieee80211_frame *); /* * Handle demic / mic errors from hardware-decrypted offload devices. */ if ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_DECRYPTED)) { if ((rxs->c_pktflags & IEEE80211_RX_F_FAIL_MMIC) != 0) { /* * Hardware has said MMIC failed. We don't care about * whether it was stripped or not. * * Eventually - teach the demic methods in crypto * modules to handle a NULL key and not to dereference * it. */ ieee80211_notify_michael_failure(vap, wh, IEEE80211_KEYIX_NONE); return (0); } if ((rxs->c_pktflags & (IEEE80211_RX_F_MIC_STRIP|IEEE80211_RX_F_MMIC_STRIP)) != 0) { /* * Hardware has decrypted and not indicated a * MIC failure and has stripped the MIC. * We may not have a key, so for now just * return OK. */ return (1); } } /* * If we don't have a key at this point then we don't * have to demic anything. */ if (k == NULL) return (1); cip = k->wk_cipher; return (cip->ic_miclen > 0 ? cip->ic_demic(k, m, force) : 1); } static void load_ucastkey(void *arg, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_key *k; if (vap->iv_state != IEEE80211_S_RUN) return; k = &ni->ni_ucastkey; if (k->wk_flags & IEEE80211_KEY_DEVKEY) dev_key_set(vap, k); } /* * Re-load all keys known to the 802.11 layer that may * have hardware state backing them. This is used by * drivers on resume to push keys down into the device. */ void ieee80211_crypto_reload_keys(struct ieee80211com *ic) { struct ieee80211vap *vap; int i; /* * Keys in the global key table of each vap. */ /* NB: used only during resume so don't lock for now */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { if (vap->iv_state != IEEE80211_S_RUN) continue; for (i = 0; i < IEEE80211_WEP_NKID; i++) { const struct ieee80211_key *k = &vap->iv_nw_keys[i]; if (k->wk_flags & IEEE80211_KEY_DEVKEY) dev_key_set(vap, k); } } /* * Unicast keys. */ ieee80211_iterate_nodes(&ic->ic_sta, load_ucastkey, NULL); } /* * Set the default key index for WEP, or KEYIX_NONE for no default TX key. * * This should be done as part of a key update block (iv_key_update_begin / * iv_key_update_end.) */ void ieee80211_crypto_set_deftxkey(struct ieee80211vap *vap, ieee80211_keyix kid) { /* XXX TODO: assert we're in a key update block */ vap->iv_update_deftxkey(vap, kid); } /** * @brief Calculate the AAD required for this frame for AES-GCM/AES-CCM. * * The contents are described in 802.11-2020 12.5.3.3.3 (Construct AAD) * under AES-CCM and are shared with AES-GCM as covered in 12.5.5.3.3 * (Construct AAD) (AES-GCM). * * NOTE: the first two bytes are a 16 bit big-endian length, which are used * by AES-CCM as part of the Adata field (RFC 3610, section 2.2 * (Authentication)) to indicate the length of the Adata field itself. * Since this is small and fits in 0xfeff bytes, the length field * uses the two byte big endian option. * * AES-GCM doesn't require the length at the beginning and will need to * skip it. * * TODO: net80211 currently doesn't support negotiating SPP (Signaling * and Payload Protected A-MSDUs) and thus bit 7 of the QoS control field * is always masked. * * TODO: net80211 currently doesn't support DMG (802.11ad) so bit 7 * (A-MSDU present) and bit 8 (A-MSDU type) are always masked. * * @param wh 802.11 frame to calculate the AAD over * @param aad AAD (additional authentication data) buffer * @param len The AAD buffer length in bytes. * @returns The number of AAD payload bytes (ignoring the first two * bytes, which are the AAD payload length in big-endian). */ uint16_t ieee80211_crypto_init_aad(const struct ieee80211_frame *wh, uint8_t *aad, int len) { uint16_t aad_len; memset(aad, 0, len); /* * AAD for PV0 MPDUs: * * FC with bits 4..6 and 11..13 masked to zero; 14 is always one * A1 | A2 | A3 * SC with bits 4..15 (seq#) masked to zero * A4 (if present) * QC (if present) */ aad[0] = 0; /* AAD length >> 8 */ /* NB: aad[1] set below */ aad[2] = wh->i_fc[0] & 0x8f; /* see above for bitfields */ aad[3] = wh->i_fc[1] & 0xc7; /* see above for bitfields */ /* mask aad[3] b7 if frame is data frame w/ QoS control field */ if (IEEE80211_IS_QOS_ANY(wh)) aad[3] &= 0x7f; /* NB: we know 3 addresses are contiguous */ memcpy(aad + 4, wh->i_addr1, 3 * IEEE80211_ADDR_LEN); aad[22] = wh->i_seq[0] & IEEE80211_SEQ_FRAG_MASK; aad[23] = 0; /* all bits masked */ /* * Construct variable-length portion of AAD based * on whether this is a 4-address frame/QOS frame. * We always zero-pad to 32 bytes before running it * through the cipher. */ if (IEEE80211_IS_DSTODS(wh)) { IEEE80211_ADDR_COPY(aad + 24, ((const struct ieee80211_frame_addr4 *)wh)->i_addr4); if (IEEE80211_IS_QOS_ANY(wh)) { const struct ieee80211_qosframe_addr4 *qwh4 = (const struct ieee80211_qosframe_addr4 *) wh; /* TODO: SPP A-MSDU / A-MSDU present bit */ aad[30] = qwh4->i_qos[0] & 0x0f;/* just priority bits */ aad[31] = 0; aad_len = aad[1] = 22 + IEEE80211_ADDR_LEN + 2; } else { *(uint16_t *)&aad[30] = 0; aad_len = aad[1] = 22 + IEEE80211_ADDR_LEN; } } else { if (IEEE80211_IS_QOS_ANY(wh)) { const struct ieee80211_qosframe *qwh = (const struct ieee80211_qosframe*) wh; /* TODO: SPP A-MSDU / A-MSDU present bit */ aad[24] = qwh->i_qos[0] & 0x0f; /* just priority bits */ aad[25] = 0; aad_len = aad[1] = 22 + 2; } else { *(uint16_t *)&aad[24] = 0; aad_len = aad[1] = 22; } *(uint16_t *)&aad[26] = 0; *(uint32_t *)&aad[28] = 0; } return (aad_len); } diff --git a/sys/net80211/ieee80211_hostap.c b/sys/net80211/ieee80211_hostap.c index 7f5cffcbb145..990b07f90032 100644 --- a/sys/net80211/ieee80211_hostap.c +++ b/sys/net80211/ieee80211_hostap.c @@ -1,2513 +1,2513 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * 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. */ /* * IEEE 802.11 HOSTAP mode support. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #include #include #include /* for parse_wmeie */ static void hostap_vattach(struct ieee80211vap *); static int hostap_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int hostap_input(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *, int rssi, int nf); static void hostap_deliver_data(struct ieee80211vap *, struct ieee80211_node *, struct mbuf *); static void hostap_recv_mgmt(struct ieee80211_node *, struct mbuf *, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf); static void hostap_recv_ctl(struct ieee80211_node *, struct mbuf *, int); void ieee80211_hostap_attach(struct ieee80211com *ic) { ic->ic_vattach[IEEE80211_M_HOSTAP] = hostap_vattach; } void ieee80211_hostap_detach(struct ieee80211com *ic) { } static void hostap_vdetach(struct ieee80211vap *vap) { } static void hostap_vattach(struct ieee80211vap *vap) { vap->iv_newstate = hostap_newstate; vap->iv_input = hostap_input; vap->iv_recv_mgmt = hostap_recv_mgmt; vap->iv_recv_ctl = hostap_recv_ctl; vap->iv_opdetach = hostap_vdetach; vap->iv_deliver_data = hostap_deliver_data; vap->iv_recv_pspoll = ieee80211_recv_pspoll; } static void sta_disassoc(void *arg, struct ieee80211_node *ni) { if (ni->ni_associd != 0) { IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DISASSOC, IEEE80211_REASON_ASSOC_LEAVE); ieee80211_node_leave(ni); } } static void sta_csa(void *arg, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; if (ni->ni_associd != 0) if (ni->ni_inact > vap->iv_inact_init) { ni->ni_inact = vap->iv_inact_init; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact %u", __func__, ni->ni_inact); } } static void sta_drop(void *arg, struct ieee80211_node *ni) { if (ni->ni_associd != 0) ieee80211_node_leave(ni); } /* * Does a channel change require associated stations to re-associate * so protocol state is correct. This is used when doing CSA across * bands or similar (e.g. HT -> legacy). */ static int isbandchange(struct ieee80211com *ic) { return ((ic->ic_bsschan->ic_flags ^ ic->ic_csa_newchan->ic_flags) & (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_HALF | IEEE80211_CHAN_QUARTER | IEEE80211_CHAN_HT)) != 0; } /* * IEEE80211_M_HOSTAP vap state machine handler. */ static int hostap_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic = vap->iv_ic; enum ieee80211_state ostate; IEEE80211_LOCK_ASSERT(ic); ostate = vap->iv_state; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (%d)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg); vap->iv_state = nstate; /* state transition */ if (ostate != IEEE80211_S_SCAN) ieee80211_cancel_scan(vap); /* background scan */ switch (nstate) { case IEEE80211_S_INIT: switch (ostate) { case IEEE80211_S_SCAN: ieee80211_cancel_scan(vap); break; case IEEE80211_S_CAC: ieee80211_dfs_cac_stop(vap); break; case IEEE80211_S_RUN: ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, sta_disassoc, NULL); break; default: break; } if (ostate != IEEE80211_S_INIT) { /* NB: optimize INIT -> INIT case */ ieee80211_reset_bss(vap); } if (vap->iv_auth->ia_detach != NULL) vap->iv_auth->ia_detach(vap); break; case IEEE80211_S_SCAN: switch (ostate) { case IEEE80211_S_CSA: case IEEE80211_S_RUN: ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, sta_disassoc, NULL); /* * Clear overlapping BSS state; the beacon frame * will be reconstructed on transition to the RUN * state and the timeout routines check if the flag * is set before doing anything so this is sufficient. */ vap->iv_flags_ext &= ~IEEE80211_FEXT_NONERP_PR; vap->iv_flags_ht &= ~IEEE80211_FHT_NONHT_PR; /* XXX TODO: schedule deferred update? */ /* fall thru... */ case IEEE80211_S_CAC: /* * NB: We may get here because of a manual channel * change in which case we need to stop CAC * XXX no need to stop if ostate RUN but it's ok */ ieee80211_dfs_cac_stop(vap); /* fall thru... */ case IEEE80211_S_INIT: if (vap->iv_des_chan != IEEE80211_CHAN_ANYC && !IEEE80211_IS_CHAN_RADAR(vap->iv_des_chan)) { /* * Already have a channel; bypass the * scan and startup immediately. * ieee80211_create_ibss will call back to * move us to RUN state. */ ieee80211_create_ibss(vap, vap->iv_des_chan); break; } /* * Initiate a scan. We can come here as a result * of an IEEE80211_IOC_SCAN_REQ too in which case * the vap will be marked with IEEE80211_FEXT_SCANREQ * and the scan request parameters will be present * in iv_scanreq. Otherwise we do the default. */ if (vap->iv_flags_ext & IEEE80211_FEXT_SCANREQ) { ieee80211_check_scan(vap, vap->iv_scanreq_flags, vap->iv_scanreq_duration, vap->iv_scanreq_mindwell, vap->iv_scanreq_maxdwell, vap->iv_scanreq_nssid, vap->iv_scanreq_ssid); vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANREQ; } else ieee80211_check_scan_current(vap); break; case IEEE80211_S_SCAN: /* * A state change requires a reset; scan. */ ieee80211_check_scan_current(vap); break; default: break; } break; case IEEE80211_S_CAC: /* * Start CAC on a DFS channel. We come here when starting * a bss on a DFS channel (see ieee80211_create_ibss). */ ieee80211_dfs_cac_start(vap); break; case IEEE80211_S_RUN: if (vap->iv_flags & IEEE80211_F_WPA) { /* XXX validate prerequisites */ } switch (ostate) { case IEEE80211_S_INIT: /* * Already have a channel; bypass the * scan and startup immediately. * Note that ieee80211_create_ibss will call * back to do a RUN->RUN state change. */ ieee80211_create_ibss(vap, ieee80211_ht_adjust_channel(ic, ic->ic_curchan, vap->iv_flags_ht)); /* NB: iv_bss is changed on return */ break; case IEEE80211_S_CAC: /* * NB: This is the normal state change when CAC * expires and no radar was detected; no need to * clear the CAC timer as it's already expired. */ /* fall thru... */ case IEEE80211_S_CSA: /* * Shorten inactivity timer of associated stations * to weed out sta's that don't follow a CSA. */ ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, sta_csa, NULL); /* * Update bss node channel to reflect where * we landed after CSA. */ ieee80211_node_set_chan(vap->iv_bss, ieee80211_ht_adjust_channel(ic, ic->ic_curchan, ieee80211_htchanflags(vap->iv_bss->ni_chan))); /* XXX bypass debug msgs */ break; case IEEE80211_S_SCAN: case IEEE80211_S_RUN: #ifdef IEEE80211_DEBUG if (ieee80211_msg_debug(vap)) { struct ieee80211_node *ni = vap->iv_bss; ieee80211_note(vap, "synchronized with %s ssid ", ether_sprintf(ni->ni_bssid)); ieee80211_print_essid(ni->ni_essid, ni->ni_esslen); - printf(" channel %d start %uMbit/s\n", + net80211_printf(" channel %d start %uMbit/s\n", ieee80211_chan2ieee(ic, ic->ic_curchan), ieee80211_node_get_txrate_kbit(ni) / 1000); } #endif break; default: break; } /* * Start/stop the authenticator. We delay until here * to allow configuration to happen out of order. */ if (vap->iv_auth->ia_attach != NULL) { /* XXX check failure */ vap->iv_auth->ia_attach(vap); } else if (vap->iv_auth->ia_detach != NULL) { vap->iv_auth->ia_detach(vap); } ieee80211_node_authorize(vap->iv_bss); break; case IEEE80211_S_CSA: if (ostate == IEEE80211_S_RUN && isbandchange(ic)) { /* * On a ``band change'' silently drop associated * stations as they must re-associate before they * can pass traffic (as otherwise protocol state * such as capabilities and the negotiated rate * set may/will be wrong). */ ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, sta_drop, NULL); } break; default: break; } return 0; } static void hostap_deliver_data(struct ieee80211vap *vap, struct ieee80211_node *ni, struct mbuf *m) { struct ether_header *eh = mtod(m, struct ether_header *); struct ifnet *ifp = vap->iv_ifp; /* clear driver/net80211 flags before passing up */ m->m_flags &= ~(M_MCAST | M_BCAST); m_clrprotoflags(m); KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP, ("gack, opmode %d", vap->iv_opmode)); /* * Do accounting. */ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); IEEE80211_NODE_STAT(ni, rx_data); IEEE80211_NODE_STAT_ADD(ni, rx_bytes, m->m_pkthdr.len); if (ETHER_IS_MULTICAST(eh->ether_dhost)) { m->m_flags |= M_MCAST; /* XXX M_BCAST? */ IEEE80211_NODE_STAT(ni, rx_mcast); } else IEEE80211_NODE_STAT(ni, rx_ucast); /* perform as a bridge within the AP */ if ((vap->iv_flags & IEEE80211_F_NOBRIDGE) == 0) { struct mbuf *mcopy = NULL; if (m->m_flags & M_MCAST) { mcopy = m_dup(m, IEEE80211_M_NOWAIT); if (mcopy == NULL) if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); else mcopy->m_flags |= M_MCAST; } else { /* * Check if the 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 *sta = ieee80211_find_vap_node( &vap->iv_ic->ic_sta, vap, eh->ether_dhost); if (sta != NULL) { if (ieee80211_node_is_authorized(sta)) { /* * Beware of sending to ourself; this * needs to happen via the normal * input path. */ if (sta != vap->iv_bss) { mcopy = m; m = NULL; } } else { vap->iv_stats.is_rx_unauth++; IEEE80211_NODE_STAT(sta, rx_unauth); } ieee80211_free_node(sta); } } if (mcopy != NULL) (void) ieee80211_vap_xmitpkt(vap, mcopy); } if (m != NULL) { /* * Mark frame as coming from vap's interface. */ m->m_pkthdr.rcvif = ifp; if (m->m_flags & M_MCAST) { /* * Spam DWDS vap's w/ multicast traffic. */ /* XXX only if dwds in use? */ ieee80211_dwds_mcast(vap, m); } if (ni->ni_vlan != 0) { /* attach vlan tag */ m->m_pkthdr.ether_vtag = ni->ni_vlan; m->m_flags |= M_VLANTAG; } ieee80211_vap_deliver_data(vap, m); } } /* * 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 0; } return 1; } /* * 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 hostap_input(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { 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 = 1; /* mbuf need to be tapped. */ uint8_t dir, type, subtype, qos; uint8_t *bssid; int is_hw_decrypted = 0; int has_decrypted = 0; /* * Some devices do hardware decryption all the way through * to pretending the frame wasn't encrypted in the first place. * So, tag it appropriately so it isn't discarded inappropriately. */ if ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_DECRYPTED)) is_hw_decrypted = 1; if (m->m_flags & M_AMPDU_MPDU) { /* * Fastpath for A-MPDU reorder q resubmission. Frames * w/ M_AMPDU_MPDU 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_MPDU 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_DATA; hdrspace = ieee80211_hdrspace(ic, wh); /* XXX optimize? */ goto resubmit_ampdu; } KASSERT(ni != NULL, ("null node")); ni->ni_inact = ni->ni_inact_reload; 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 (!IEEE80211_IS_FC0_CHECK_VER(wh, IEEE80211_FC0_VERSION_0)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "wrong version, fc %02x:%02x", wh->i_fc[0], wh->i_fc[1]); 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) { if (dir != IEEE80211_FC1_DIR_NODS) bssid = wh->i_addr1; else if (type == IEEE80211_FC0_TYPE_CTL) bssid = wh->i_addr1; else { if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "too short (2): len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto out; } bssid = wh->i_addr3; } /* * Validate the bssid. */ if (!(type == IEEE80211_FC0_TYPE_MGT && subtype == IEEE80211_FC0_SUBTYPE_BEACON) && !IEEE80211_ADDR_EQ(bssid, vap->iv_bss->ni_bssid) && !IEEE80211_ADDR_EQ(bssid, ieee80211_vap_get_broadcast_address(vap))) { /* not interested in */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, bssid, NULL, "%s", "not to bss"); vap->iv_stats.is_rx_wrongbss++; goto out; } IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; if (IEEE80211_HAS_SEQ(type, subtype)) { 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++; if (! ieee80211_check_rxseq(ni, wh, bssid, rxs)) goto out; } } 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_TODS || (dir == IEEE80211_FC1_DIR_DSTODS && (vap->iv_flags & IEEE80211_F_DWDS)))) { if (dir != IEEE80211_FC1_DIR_DSTODS) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "incorrect dir 0x%x", dir); } else { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_WDS, wh, "4-address data", "%s", "DWDS not enabled"); } 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"); 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; } /* * Check for power save state change. * XXX out-of-order A-MPDU frames? */ if (((wh->i_fc[1] & IEEE80211_FC1_PWR_MGT) ^ (ni->ni_flags & IEEE80211_NODE_PWR_MGT))) vap->iv_node_ps(ni, wh->i_fc[1] & IEEE80211_FC1_PWR_MGT); /* * For 4-address packets handle WDS discovery * notifications. Once a WDS link is setup frames * are just delivered to the WDS vap (see below). */ if (dir == IEEE80211_FC1_DIR_DSTODS && ni->ni_wdsvap == NULL) { if (!ieee80211_node_is_authorized(ni)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_WDS, wh, "4-address data", "%s", "unauthorized port"); vap->iv_stats.is_rx_unauth++; IEEE80211_NODE_STAT(ni, rx_unauth); goto err; } ieee80211_dwds_discover(ni, m); return type; } /* * Handle 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 ((m->m_flags & M_AMPDU) && ieee80211_ampdu_reorder(ni, m, rxs) != 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 (is_hw_decrypted || IEEE80211_IS_PROTECTED(wh)) { 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; } if (ieee80211_crypto_decap(ni, m, hdrspace, &key) == 0) { /* 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_PROTECTED; has_decrypted = 1; } 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_DATA) qos = ieee80211_getqos(wh)[0]; else qos = 0; /* * Next up, any fragmentation. */ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { m = ieee80211_defrag(ni, m, hdrspace, has_decrypted); 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 (ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); need_tap = 0; /* * Finally, strip the 802.11 header. */ m = ieee80211_decap(vap, m, hdrspace, qos); 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; } if (!(qos & IEEE80211_QOS_AMSDU)) eh = mtod(m, struct ether_header *); else eh = NULL; 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 == NULL || eh->ether_type != htons(ETHERTYPE_PAE)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "unauthorized or " "unknown port: ether type 0x%x len %u", eh == NULL ? -1 : 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) && ((has_decrypted == 0) && (m->m_flags & M_WEP) == 0) && (is_hw_decrypted == 0) && (eh == NULL || 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 { #ifdef IEEE80211_SUPPORT_SUPERG m = ieee80211_decap_fastframe(vap, ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; #endif } if (dir == IEEE80211_FC1_DIR_DSTODS && ni->ni_wdsvap != NULL) ieee80211_deliver_data(ni->ni_wdsvap, ni, m); else hostap_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, "mgt", "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; } if (IEEE80211_IS_MULTICAST(wh->i_addr2)) { /* ensure return frames are unicast */ IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "source is multicast: %s", ether_sprintf(wh->i_addr2)); vap->iv_stats.is_rx_mgtdiscard++; /* XXX stat */ goto out; } #ifdef IEEE80211_DEBUG if ((ieee80211_msg_debug(vap) && doprint(vap, subtype)) || ieee80211_msg_dumppkts(vap)) { net80211_vap_printf(vap, "received %s from %s rssi %d\n", ieee80211_mgt_subtype_name(subtype), ether_sprintf(wh->i_addr2), rssi); } #endif if (IEEE80211_IS_PROTECTED(wh)) { 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, NULL, "%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, NULL, "%s", "WEP set but PRIVACY off"); vap->iv_stats.is_rx_noprivacy++; goto out; } hdrspace = ieee80211_hdrspace(ic, wh); if (ieee80211_crypto_decap(ni, m, hdrspace, &key) == 0) { /* NB: stats+msgs handled in crypto_decap */ goto out; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; has_decrypted = 1; } /* * Pass the packet to radiotap before calling iv_recv_mgmt(). * Otherwise iv_recv_mgmt() might pass another packet to * radiotap, resulting in out of order packet captures. */ if (ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); need_tap = 0; vap->iv_recv_mgmt(ni, m, subtype, rxs, rssi, nf); goto out; case IEEE80211_FC0_TYPE_CTL: vap->iv_stats.is_rx_ctl++; IEEE80211_NODE_STAT(ni, rx_ctrl); if (ieee80211_is_ctl_frame_for_vap(ni, m)) vap->iv_recv_ctl(ni, m, subtype); goto out; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "bad", "frame type 0x%x", type); /* should not come here */ break; } err: if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); out: if (m != NULL) { if (need_tap && ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); m_freem(m); } return type; } static void hostap_auth_open(struct ieee80211_node *ni, struct ieee80211_frame *wh, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; KASSERT(vap->iv_state == IEEE80211_S_RUN, ("state %d", vap->iv_state)); 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 */ /* * Clear any challenge text that may be there if * a previous shared key auth failed and then an * open auth is attempted. */ if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } /* XXX hack to workaround calling convention */ ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, (seq + 1) | (IEEE80211_STATUS_ALG<<16)); return; } if (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); if (ni == NULL) return; } else if ((ni->ni_flags & IEEE80211_NODE_AREF) == 0) (void) ieee80211_ref_node(ni); /* * Mark the node as referenced to reflect that it's * reference count has been bumped to insure it remains * after the transaction completes. */ ni->ni_flags |= IEEE80211_NODE_AREF; /* * Mark the node as requiring a valid association id * before outbound traffic is permitted. */ ni->ni_flags |= IEEE80211_NODE_ASSOCID; if (vap->iv_acl != NULL && vap->iv_acl->iac_getpolicy(vap) == IEEE80211_MACCMD_POLICY_RADIUS) { /* * When the ACL policy is set to RADIUS we defer the * authorization to a user agent. Dispatch an event, * a subsequent MLME call will decide the fate of the * station. If the user agent is not present then the * node will be reclaimed due to inactivity. */ IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH | IEEE80211_MSG_ACL, ni->ni_macaddr, "%s", "station authentication deferred (radius acl)"); ieee80211_notify_node_auth(ni); } else { IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni->ni_macaddr, "%s", "station authenticated (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); } } static void hostap_auth_shared(struct ieee80211_node *ni, struct ieee80211_frame *wh, uint8_t *frm, uint8_t *efrm, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; uint8_t *challenge; int estatus; KASSERT(vap->iv_state == IEEE80211_S_RUN, ("state %d", vap->iv_state)); /* * 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 Mac 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 (seq) { case IEEE80211_AUTH_SHARED_REQUEST: { #ifdef IEEE80211_DEBUG bool allocbs; #endif if (ni == vap->iv_bss) { ni = ieee80211_dup_bss(vap, wh->i_addr2); if (ni == NULL) { /* NB: no way to return an error */ return; } #ifdef IEEE80211_DEBUG allocbs = 1; #endif } else { if ((ni->ni_flags & IEEE80211_NODE_AREF) == 0) (void) ieee80211_ref_node(ni); #ifdef IEEE80211_DEBUG allocbs = 0; #endif } /* * Mark the node as referenced to reflect that it's * reference count has been bumped to insure it remains * after the transaction completes. */ ni->ni_flags |= IEEE80211_NODE_AREF; /* * Mark the node as requiring a valid association id * before outbound traffic is permitted. */ ni->ni_flags |= IEEE80211_NODE_ASSOCID; IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; if (!ieee80211_alloc_challenge(ni)) { /* NB: don't return error so they rexmit */ return; } net80211_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"); /* * When the ACL policy is set to RADIUS we defer the * authorization to a user agent. Dispatch an event, * a subsequent MLME call will decide the fate of the * station. If the user agent is not present then the * node will be reclaimed due to inactivity. */ if (vap->iv_acl != NULL && vap->iv_acl->iac_getpolicy(vap) == IEEE80211_MACCMD_POLICY_RADIUS) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH | IEEE80211_MSG_ACL, ni->ni_macaddr, "%s", "station authentication deferred (radius acl)"); ieee80211_notify_node_auth(ni); return; } 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, "%s", "station authenticated (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); return; bad: /* * Send an error response; but only when operating as an AP. */ /* XXX hack to workaround calling convention */ ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, (seq + 1) | (estatus<<16)); } /* * Convert a WPA cipher selector OUI to an internal * cipher algorithm. Where appropriate we also * record any key length. */ static int wpa_cipher(const uint8_t *sel, uint8_t *keylen, uint8_t *cipher) { #define WPA_SEL(x) (((x)<<24)|WPA_OUI) uint32_t w = le32dec(sel); switch (w) { case WPA_SEL(WPA_CSE_NULL): *cipher = IEEE80211_CIPHER_NONE; break; case WPA_SEL(WPA_CSE_WEP40): if (keylen) *keylen = 40 / NBBY; *cipher = IEEE80211_CIPHER_WEP; break; case WPA_SEL(WPA_CSE_WEP104): if (keylen) *keylen = 104 / NBBY; *cipher = IEEE80211_CIPHER_WEP; break; case WPA_SEL(WPA_CSE_TKIP): *cipher = IEEE80211_CIPHER_TKIP; break; case WPA_SEL(WPA_CSE_CCMP): *cipher = IEEE80211_CIPHER_AES_CCM; break; /* Note: no GCM cipher in the legacy WPA1 OUI */ default: return (EINVAL); } return (0); #undef WPA_SEL } /* * Convert a WPA key management/authentication algorithm * to an internal code. */ static int wpa_keymgmt(const uint8_t *sel) { #define WPA_SEL(x) (((x)<<24)|WPA_OUI) uint32_t w = le32dec(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. * Note that we do not validate security parameters; that * is handled by the authenticator; the parsing done here * is just for internal use in making operational decisions. */ static int ieee80211_parse_wpa(struct ieee80211vap *vap, const uint8_t *frm, struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh) { uint8_t len = frm[1]; uint32_t w; int error, 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) == 0) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "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: iswpaoui already validated the OUI and type */ w = le16dec(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; memset(rsn, 0, sizeof(*rsn)); /* multicast/group cipher */ error = wpa_cipher(frm, &rsn->rsn_mcastkeylen, &rsn->rsn_mcastcipher); if (error != 0) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "unknown mcast cipher suite %08X", le32dec(frm)); return IEEE80211_REASON_GROUP_CIPHER_INVALID; } frm += 4, len -= 4; /* unicast ciphers */ n = le16dec(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--) { uint8_t cipher; error = wpa_cipher(frm, &rsn->rsn_ucastkeylen, &cipher); if (error == 0) w |= 1 << cipher; frm += 4, len -= 4; } if (w == 0) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "no usable pairwise cipher suite found (w=%d)", w); return IEEE80211_REASON_PAIRWISE_CIPHER_INVALID; } /* XXX other? */ if (w & (1 << IEEE80211_CIPHER_AES_CCM)) rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; else rsn->rsn_ucastcipher = IEEE80211_CIPHER_TKIP; /* key management algorithms */ n = le16dec(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; } if (w & WPA_ASE_8021X_UNSPEC) rsn->rsn_keymgmt = WPA_ASE_8021X_UNSPEC; else rsn->rsn_keymgmt = WPA_ASE_8021X_PSK; if (len > 2) /* optional capabilities */ rsn->rsn_caps = le16dec(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(const uint8_t *sel, uint8_t *keylen, uint8_t *cipher) { #define RSN_SEL(x) (((x)<<24)|RSN_OUI) uint32_t w = le32dec(sel); switch (w) { case RSN_SEL(RSN_CSE_NULL): *cipher = IEEE80211_CIPHER_NONE; break; case RSN_SEL(RSN_CSE_WEP40): if (keylen) *keylen = 40 / NBBY; *cipher = IEEE80211_CIPHER_WEP; break; case RSN_SEL(RSN_CSE_WEP104): if (keylen) *keylen = 104 / NBBY; *cipher = IEEE80211_CIPHER_WEP; break; case RSN_SEL(RSN_CSE_TKIP): *cipher = IEEE80211_CIPHER_TKIP; break; case RSN_SEL(RSN_CSE_CCMP): *cipher = IEEE80211_CIPHER_AES_CCM; break; case RSN_SEL(RSN_CSE_WRAP): *cipher = IEEE80211_CIPHER_AES_OCB; break; case RSN_SEL(RSN_CSE_GCMP_128): *cipher = IEEE80211_CIPHER_AES_GCM_128; break; default: return (EINVAL); } return (0); #undef WPA_SEL } /* * Convert an RSN key management/authentication algorithm * to an internal code. */ static int rsn_keymgmt(const uint8_t *sel) { #define RSN_SEL(x) (((x)<<24)|RSN_OUI) uint32_t w = le32dec(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, const uint8_t *frm, struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh) { uint8_t len = frm[1]; uint32_t w; int error, 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) == 0) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "not RSN, flags 0x%x", vap->iv_flags); return IEEE80211_REASON_IE_INVALID; } /* XXX may be shorter */ 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 = le16dec(frm); if (w != RSN_VERSION) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "bad version %u", w); return IEEE80211_REASON_UNSUPP_RSN_IE_VERSION; } frm += 2, len -= 2; memset(rsn, 0, sizeof(*rsn)); /* multicast/group cipher */ error = rsn_cipher(frm, &rsn->rsn_mcastkeylen, &rsn->rsn_mcastcipher); if (error != 0) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "unknown mcast cipher suite %08X", le32dec(frm)); return IEEE80211_REASON_GROUP_CIPHER_INVALID; } if (rsn->rsn_mcastcipher == IEEE80211_CIPHER_NONE) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "invalid mcast cipher suite %d", rsn->rsn_mcastcipher); return IEEE80211_REASON_GROUP_CIPHER_INVALID; } frm += 4, len -= 4; /* unicast ciphers */ n = le16dec(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--) { uint8_t cipher; error = rsn_cipher(frm, &rsn->rsn_ucastkeylen, &cipher); if (error == 0) w |= 1 << cipher; frm += 4, len -= 4; } if (w & (1 << IEEE80211_CIPHER_AES_GCM_128)) rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_GCM_128; else if (w & (1 << IEEE80211_CIPHER_AES_CCM)) rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; else if (w & (1 << IEEE80211_CIPHER_AES_OCB)) rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_OCB; else if (w & (1 << IEEE80211_CIPHER_TKIP)) rsn->rsn_ucastcipher = IEEE80211_CIPHER_TKIP; else if ((w & (1 << IEEE80211_CIPHER_NONE)) && (rsn->rsn_mcastcipher == IEEE80211_CIPHER_WEP || rsn->rsn_mcastcipher == IEEE80211_CIPHER_TKIP)) rsn->rsn_ucastcipher = IEEE80211_CIPHER_NONE; else { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "no usable pairwise cipher suite found (w=%d)", w); return IEEE80211_REASON_PAIRWISE_CIPHER_INVALID; } /* key management algorithms */ n = le16dec(frm); frm += 2, len -= 2; if (len < n*4) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "key mgmt alg data too short; len %u, n %u", len, n); return IEEE80211_REASON_IE_INVALID; } w = 0; for (; n > 0; n--) { w |= rsn_keymgmt(frm); frm += 4, len -= 4; } if (w & RSN_ASE_8021X_UNSPEC) rsn->rsn_keymgmt = RSN_ASE_8021X_UNSPEC; else rsn->rsn_keymgmt = RSN_ASE_8021X_PSK; /* optional RSN capabilities */ if (len >= 2) { rsn->rsn_caps = le16dec(frm); frm += 2, len -= 2; } /* XXX PMK Count / PMKID */ /* XXX Group Cipher Management Suite */ return 0; } /* * WPA/802.11i association request processing. */ static int wpa_assocreq(struct ieee80211_node *ni, struct ieee80211_rsnparms *rsnparms, const struct ieee80211_frame *wh, const uint8_t *wpa, const uint8_t *rsn, uint16_t capinfo) { struct ieee80211vap *vap = ni->ni_vap; uint8_t reason; int badwparsn; ni->ni_flags &= ~(IEEE80211_NODE_WPS|IEEE80211_NODE_TSN); if (wpa == NULL && rsn == NULL) { if (vap->iv_flags_ext & IEEE80211_FEXT_WPS) { /* * W-Fi Protected Setup (WPS) permits * clients to associate and pass EAPOL frames * to establish initial credentials. */ ni->ni_flags |= IEEE80211_NODE_WPS; return 1; } if ((vap->iv_flags_ext & IEEE80211_FEXT_TSN) && (capinfo & IEEE80211_CAPINFO_PRIVACY)) { /* * Transitional Security Network. Permits clients * to associate and use WEP while WPA is configured. */ ni->ni_flags |= IEEE80211_NODE_TSN; return 1; } IEEE80211_DISCARD(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, wh, NULL, "%s", "no WPA/RSN IE in association request"); vap->iv_stats.is_rx_assoc_badwpaie++; reason = IEEE80211_REASON_IE_INVALID; goto bad; } /* assert right association security credentials */ badwparsn = 0; /* NB: to silence compiler */ switch (vap->iv_flags & IEEE80211_F_WPA) { case IEEE80211_F_WPA1: badwparsn = (wpa == NULL); break; case IEEE80211_F_WPA2: badwparsn = (rsn == NULL); break; case IEEE80211_F_WPA1|IEEE80211_F_WPA2: badwparsn = (wpa == NULL && rsn == NULL); break; } if (badwparsn) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, wh, NULL, "%s", "missing WPA/RSN IE in association request"); vap->iv_stats.is_rx_assoc_badwpaie++; reason = IEEE80211_REASON_IE_INVALID; goto bad; } /* * Parse WPA/RSN information element. */ if (wpa != NULL) reason = ieee80211_parse_wpa(vap, wpa, rsnparms, wh); else reason = ieee80211_parse_rsn(vap, rsn, rsnparms, wh); if (reason != 0) { /* XXX wpa->rsn fallback? */ /* XXX distinguish WPA/RSN? */ vap->iv_stats.is_rx_assoc_badwpaie++; goto bad; } IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, ni, "%s ie: mc %u/%u uc %u/%u key %u caps 0x%x", wpa != NULL ? "WPA" : "RSN", rsnparms->rsn_mcastcipher, rsnparms->rsn_mcastkeylen, rsnparms->rsn_ucastcipher, rsnparms->rsn_ucastkeylen, rsnparms->rsn_keymgmt, rsnparms->rsn_caps); return 1; bad: ieee80211_node_deauth(ni, reason); return 0; } /* XXX find a better place for definition */ struct l2_update_frame { struct ether_header eh; uint8_t dsap; uint8_t ssap; uint8_t control; uint8_t xid[3]; } __packed; /* * Deliver a TGf L2UF frame on behalf of a station. * This primes any bridge when the station is roaming * between ap's on the same wired network. */ static void ieee80211_deliver_l2uf(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct mbuf *m; struct l2_update_frame *l2uf; struct ether_header *eh; m = m_gethdr(IEEE80211_M_NOWAIT, MT_DATA); if (m == NULL) { IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, "%s", "no mbuf for l2uf frame"); vap->iv_stats.is_rx_nobuf++; /* XXX not right */ return; } l2uf = mtod(m, struct l2_update_frame *); eh = &l2uf->eh; /* dst: Broadcast address */ IEEE80211_ADDR_COPY(eh->ether_dhost, ieee80211_vap_get_broadcast_address(vap)); /* src: associated STA */ IEEE80211_ADDR_COPY(eh->ether_shost, ni->ni_macaddr); eh->ether_type = htons(sizeof(*l2uf) - sizeof(*eh)); l2uf->dsap = 0; l2uf->ssap = 0; l2uf->control = 0xf5; l2uf->xid[0] = 0x81; l2uf->xid[1] = 0x80; l2uf->xid[2] = 0x00; m->m_pkthdr.len = m->m_len = sizeof(*l2uf); hostap_deliver_data(vap, ni, m); } static void ratesetmismatch(struct ieee80211_node *ni, const struct ieee80211_frame *wh, int reassoc, int resp, const char *tag, int rate) { IEEE80211_NOTE_MAC(ni->ni_vap, IEEE80211_MSG_ANY, wh->i_addr2, "deny %s request, %s rate set mismatch, rate/MCS %d", reassoc ? "reassoc" : "assoc", tag, rate & IEEE80211_RATE_VAL); IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_BASIC_RATE); ieee80211_node_leave(ni); } static void capinfomismatch(struct ieee80211_node *ni, const struct ieee80211_frame *wh, int reassoc, int resp, const char *tag, int capinfo) { struct ieee80211vap *vap = ni->ni_vap; IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ANY, wh->i_addr2, "deny %s request, %s mismatch 0x%x", reassoc ? "reassoc" : "assoc", tag, capinfo); IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_CAPINFO); ieee80211_node_leave(ni); vap->iv_stats.is_rx_assoc_capmismatch++; } static void htcapmismatch(struct ieee80211_node *ni, const struct ieee80211_frame *wh, int reassoc, int resp) { IEEE80211_NOTE_MAC(ni->ni_vap, IEEE80211_MSG_ANY, wh->i_addr2, "deny %s request, %s missing HT ie", reassoc ? "reassoc" : "assoc"); /* XXX no better code */ IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_MISSING_HT_CAPS); ieee80211_node_leave(ni); } static void authalgreject(struct ieee80211_node *ni, const struct ieee80211_frame *wh, int algo, int seq, int status) { struct ieee80211vap *vap = ni->ni_vap; IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "unsupported alg %d", algo); vap->iv_stats.is_rx_auth_unsupported++; ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, seq | (status << 16)); } static __inline int ishtmixed(const uint8_t *ie) { const struct ieee80211_ie_htinfo *ht = (const struct ieee80211_ie_htinfo *) ie; return (ht->hi_byte2 & IEEE80211_HTINFO_OPMODE) == IEEE80211_HTINFO_OPMODE_MIXED; } static int is11bclient(const uint8_t *rates, const uint8_t *xrates) { static const uint32_t brates = (1<<2*1)|(1<<2*2)|(1<<11)|(1<<2*11); int i; /* NB: the 11b clients we care about will not have xrates */ if (xrates != NULL || rates == NULL) return 0; for (i = 0; i < rates[1]; i++) { int r = rates[2+i] & IEEE80211_RATE_VAL; if (r > 2*11 || ((1<ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_frame *wh; uint8_t *frm, *efrm, *sfrm; uint8_t *ssid, *rates, *xrates, *wpa, *rsn, *wme, *ath, *htcap; uint8_t *vhtcap, *vhtinfo; int reassoc, resp; uint8_t rate; wh = mtod(m0, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; efrm = mtod(m0, uint8_t *) + m0->m_len; switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: /* * We process beacon/probe response frames when scanning; * otherwise we check beacon frames for overlapping non-ERP * BSS in 11g and/or overlapping legacy BSS when in HT. */ if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* FALLTHROUGH */ case IEEE80211_FC0_SUBTYPE_BEACON: { struct ieee80211_scanparams scan; /* NB: accept off-channel frames */ /* XXX TODO: use rxstatus to determine off-channel details */ if (ieee80211_parse_beacon(ni, m0, ic->ic_curchan, &scan) &~ IEEE80211_BPARSE_OFFCHAN) return; /* * Count frame now that we know it's to be processed. */ if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) { vap->iv_stats.is_rx_beacon++; /* XXX remove */ IEEE80211_NODE_STAT(ni, rx_beacons); } else IEEE80211_NODE_STAT(ni, rx_proberesp); /* * If scanning, just pass information to the scan module. */ if (ic->ic_flags & IEEE80211_F_SCAN) { if (scan.status == 0 && /* NB: on channel */ (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN)) { /* * Actively scanning a channel marked passive; * send a probe request now that we know there * is 802.11 traffic present. * * XXX check if the beacon we recv'd gives * us what we need and suppress the probe req */ ieee80211_probe_curchan(vap, true); ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN; } ieee80211_add_scan(vap, ic->ic_curchan, &scan, wh, subtype, rssi, nf); return; } /* * Check beacon for overlapping bss w/ non ERP stations. * If we detect one and protection is configured but not * enabled, enable it and start a timer that'll bring us * out if we stop seeing the bss. */ if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && scan.status == 0 && /* NB: on-channel */ ((scan.erp & 0x100) == 0 || /* NB: no ERP, 11b sta*/ (scan.erp & IEEE80211_ERP_NON_ERP_PRESENT))) { vap->iv_lastnonerp = ticks; vap->iv_flags_ext |= IEEE80211_FEXT_NONERP_PR; /* * XXX TODO: this may need to check all VAPs? */ if (vap->iv_protmode != IEEE80211_PROT_NONE && (vap->iv_flags & IEEE80211_F_USEPROT) == 0) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_ASSOC, wh, "non-ERP present on channel %d " "(saw erp 0x%x from channel %d), " "enable use of protection", ic->ic_curchan->ic_ieee, scan.erp, scan.chan); vap->iv_flags |= IEEE80211_F_USEPROT; ieee80211_vap_update_erp_protmode(vap); } } /* * Check beacon for non-HT station on HT channel * and update HT BSS occupancy as appropriate. */ if (IEEE80211_IS_CHAN_HT(ic->ic_curchan)) { if (scan.status & IEEE80211_BPARSE_OFFCHAN) { /* * Off control channel; only check frames * that come in the extension channel when * operating w/ HT40. */ if (!IEEE80211_IS_CHAN_HT40(ic->ic_curchan)) break; if (scan.chan != ic->ic_curchan->ic_extieee) break; } if (scan.htinfo == NULL) { ieee80211_htprot_update(vap, IEEE80211_HTINFO_OPMODE_PROTOPT | IEEE80211_HTINFO_NONHT_PRESENT); } else if (ishtmixed(scan.htinfo)) { /* XXX? take NONHT_PRESENT from beacon? */ ieee80211_htprot_update(vap, IEEE80211_HTINFO_OPMODE_MIXED | IEEE80211_HTINFO_NONHT_PRESENT); } } break; } case IEEE80211_FC0_SUBTYPE_PROBE_REQ: if (vap->iv_state != IEEE80211_S_RUN) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* * Consult the ACL policy module if setup. */ if (vap->iv_acl != NULL && !vap->iv_acl->iac_check(vap, wh)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ACL, wh, NULL, "%s", "disallowed by ACL"); vap->iv_stats.is_rx_acl++; return; } /* * prreq frame format * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates */ ssid = rates = xrates = NULL; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); switch (*frm) { case IEEE80211_ELEMID_SSID: ssid = frm; break; case IEEE80211_ELEMID_RATES: rates = frm; break; case IEEE80211_ELEMID_XRATES: xrates = frm; break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE, return); if (xrates != NULL) IEEE80211_VERIFY_ELEMENT(xrates, IEEE80211_RATE_MAXSIZE - rates[1], return); IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN, return); IEEE80211_VERIFY_SSID(vap->iv_bss, ssid, return); if ((vap->iv_flags & IEEE80211_F_HIDESSID) && ssid[1] == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "no ssid with ssid suppression enabled"); vap->iv_stats.is_rx_ssidmismatch++; /*XXX*/ return; } /* XXX find a better class or define it's own */ IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, wh->i_addr2, "%s", "recv probe req"); /* * Some legacy 11b clients cannot hack a complete * probe response frame. When the request includes * only a bare-bones rate set, communicate this to * the transmit side. */ ieee80211_send_proberesp(vap, wh->i_addr2, is11bclient(rates, xrates) ? IEEE80211_SEND_LEGACY_11B : 0); break; case IEEE80211_FC0_SUBTYPE_AUTH: { uint16_t algo, seq, status; if (vap->iv_state != IEEE80211_S_RUN) { vap->iv_stats.is_rx_mgtdiscard++; return; } if (!IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_bss->ni_bssid)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "%s", "wrong bssid"); vap->iv_stats.is_rx_wrongbss++; /*XXX unique stat?*/ return; } /* * auth frame format * [2] algorithm * [2] sequence * [2] status * [tlv*] challenge */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6, return); algo = le16toh(*(uint16_t *)frm); seq = le16toh(*(uint16_t *)(frm + 2)); status = le16toh(*(uint16_t *)(frm + 4)); 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)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ACL, wh, NULL, "%s", "disallowed by ACL"); vap->iv_stats.is_rx_acl++; ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, (seq+1) | (IEEE80211_STATUS_UNSPECIFIED<<16)); return; } if (vap->iv_flags & IEEE80211_F_COUNTERM) { IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH | IEEE80211_MSG_CRYPTO, wh, NULL, "%s", "TKIP countermeasures enabled"); vap->iv_stats.is_rx_auth_countermeasures++; ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, IEEE80211_REASON_MIC_FAILURE); return; } if (algo == IEEE80211_AUTH_ALG_SHARED) hostap_auth_shared(ni, wh, frm + 6, efrm, rssi, nf, seq, status); else if (algo == IEEE80211_AUTH_ALG_OPEN) hostap_auth_open(ni, wh, rssi, nf, seq, status); else if (algo == IEEE80211_AUTH_ALG_LEAP) { authalgreject(ni, wh, algo, seq+1, IEEE80211_STATUS_ALG); return; } else { /* * We assume that an unknown algorithm is the result * of a decryption failure on a shared key auth frame; * return a status code appropriate for that instead * of IEEE80211_STATUS_ALG. * * NB: a seq# of 4 is intentional; the decrypted * frame likely has a bogus seq value. */ authalgreject(ni, wh, algo, 4, IEEE80211_STATUS_CHALLENGE); return; } break; } case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: { uint16_t capinfo, lintval; struct ieee80211_rsnparms rsnparms; if (vap->iv_state != IEEE80211_S_RUN) { vap->iv_stats.is_rx_mgtdiscard++; return; } if (!IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_bss->ni_bssid)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "%s", "wrong bssid"); vap->iv_stats.is_rx_assoc_bss++; return; } if (subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) { reassoc = 1; resp = IEEE80211_FC0_SUBTYPE_REASSOC_RESP; } else { reassoc = 0; resp = IEEE80211_FC0_SUBTYPE_ASSOC_RESP; } if (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; } /* * 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] WPA or RSN * [tlv] HT capabilities * [tlv] Atheros capabilities */ IEEE80211_VERIFY_LENGTH(efrm - frm, (reassoc ? 10 : 4), return); capinfo = le16toh(*(uint16_t *)frm); frm += 2; lintval = le16toh(*(uint16_t *)frm); frm += 2; if (reassoc) frm += 6; /* ignore current AP info */ ssid = rates = xrates = wpa = rsn = wme = ath = htcap = NULL; vhtcap = vhtinfo = NULL; sfrm = frm; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); 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_RSN: rsn = frm; break; case IEEE80211_ELEMID_HTCAP: htcap = frm; break; case IEEE80211_ELEMID_VHT_CAP: vhtcap = frm; break; case IEEE80211_ELEMID_VHT_OPMODE: vhtinfo = frm; break; case IEEE80211_ELEMID_VENDOR: if (iswpaoui(frm)) wpa = frm; else if (iswmeinfo(frm)) wme = frm; #ifdef IEEE80211_SUPPORT_SUPERG else if (isatherosoui(frm)) ath = frm; #endif else if (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) { if (ishtcapoui(frm) && htcap == NULL) htcap = frm; } break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE, return); if (xrates != NULL) IEEE80211_VERIFY_ELEMENT(xrates, IEEE80211_RATE_MAXSIZE - rates[1], return); IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN, return); IEEE80211_VERIFY_SSID(vap->iv_bss, ssid, return); if (htcap != NULL) { IEEE80211_VERIFY_LENGTH(htcap[1], htcap[0] == IEEE80211_ELEMID_VENDOR ? 4 + sizeof(struct ieee80211_ie_htcap)-2 : sizeof(struct ieee80211_ie_htcap)-2, return); /* XXX just NULL out? */ } /* Validate VHT IEs */ if (vhtcap != NULL) { IEEE80211_VERIFY_LENGTH(vhtcap[1], sizeof(struct ieee80211_vht_cap), return); } if (vhtinfo != NULL) { IEEE80211_VERIFY_LENGTH(vhtinfo[1], sizeof(struct ieee80211_vht_operation), return); } if ((vap->iv_flags & IEEE80211_F_WPA) && !wpa_assocreq(ni, &rsnparms, wh, wpa, rsn, capinfo)) return; /* discard challenge after association */ if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } /* NB: 802.11 spec says to ignore station's privacy bit */ if ((capinfo & IEEE80211_CAPINFO_ESS) == 0) { capinfomismatch(ni, wh, reassoc, resp, "capability", capinfo); return; } /* * Disallow re-associate w/ invalid slot time setting. */ if (ni->ni_associd != 0 && IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && ((ni->ni_capinfo ^ capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME)) { capinfomismatch(ni, wh, reassoc, resp, "slot time", capinfo); return; } rate = ieee80211_setup_rates(ni, rates, xrates, IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); if (rate & IEEE80211_RATE_BASIC) { ratesetmismatch(ni, wh, reassoc, resp, "legacy", rate); vap->iv_stats.is_rx_assoc_norate++; return; } /* * 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 ((vap->iv_flags & IEEE80211_F_PUREG) && rate < 48) { ratesetmismatch(ni, wh, reassoc, resp, "11g", rate); vap->iv_stats.is_rx_assoc_norate++; return; } /* * Do HT rate set handling and setup HT node state. */ ni->ni_chan = vap->iv_bss->ni_chan; /* VHT */ if (IEEE80211_IS_CHAN_VHT(ni->ni_chan) && vhtcap != NULL && vhtinfo != NULL) { /* XXX TODO; see below */ - printf("%s: VHT TODO!\n", __func__); + net80211_vap_printf(vap, "%s: VHT TODO!\n", __func__); ieee80211_vht_node_init(ni); ieee80211_vht_update_cap(ni, vhtcap, vhtinfo); } else if (ni->ni_flags & IEEE80211_NODE_VHT) ieee80211_vht_node_cleanup(ni); /* HT */ if (IEEE80211_IS_CHAN_HT(ni->ni_chan) && htcap != NULL) { rate = ieee80211_setup_htrates(ni, htcap, IEEE80211_F_DOFMCS | IEEE80211_F_DONEGO | IEEE80211_F_DOBRS); if (rate & IEEE80211_RATE_BASIC) { ratesetmismatch(ni, wh, reassoc, resp, "HT", rate); vap->iv_stats.is_ht_assoc_norate++; return; } ieee80211_ht_node_init(ni); ieee80211_ht_updatehtcap(ni, htcap); } else if (ni->ni_flags & IEEE80211_NODE_HT) ieee80211_ht_node_cleanup(ni); /* Finally - this will use HT/VHT info to change node channel */ if (IEEE80211_IS_CHAN_HT(ni->ni_chan) && htcap != NULL) { ieee80211_ht_updatehtcap_final(ni); } #ifdef IEEE80211_SUPPORT_SUPERG /* Always do ff node cleanup; for A-MSDU */ ieee80211_ff_node_cleanup(ni); #endif /* * Allow AMPDU operation only with unencrypted traffic * or AES-CCM / AES-GCM; the 802.11n spec only specifies these * ciphers so permitting any others is undefined and can lead * to interoperability problems. * * TODO: before landing, find exactly where in 802.11-2020 this * is called out! */ if ((ni->ni_flags & IEEE80211_NODE_HT) && (((vap->iv_flags & IEEE80211_F_WPA) && !hostapd_validate_cipher_for_ht_ampdu(rsnparms.rsn_ucastcipher)) || (vap->iv_flags & (IEEE80211_F_WPA|IEEE80211_F_PRIVACY)) == IEEE80211_F_PRIVACY)) { IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, "disallow HT use because WEP or TKIP requested, " "capinfo 0x%x ucastcipher %d", capinfo, rsnparms.rsn_ucastcipher); ieee80211_ht_node_cleanup(ni); #ifdef IEEE80211_SUPPORT_SUPERG /* Always do ff node cleanup; for A-MSDU */ ieee80211_ff_node_cleanup(ni); #endif vap->iv_stats.is_ht_assoc_downgrade++; } /* * If constrained to 11n-only stations reject legacy stations. */ if ((vap->iv_flags_ht & IEEE80211_FHT_PUREN) && (ni->ni_flags & IEEE80211_NODE_HT) == 0) { htcapmismatch(ni, wh, reassoc, resp); vap->iv_stats.is_ht_assoc_nohtcap++; return; } IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; ni->ni_intval = lintval; ni->ni_capinfo = capinfo; ni->ni_fhdwell = vap->iv_bss->ni_fhdwell; ni->ni_fhindex = vap->iv_bss->ni_fhindex; /* * Store the IEs. * XXX maybe better to just expand */ if (ieee80211_ies_init(&ni->ni_ies, sfrm, efrm - sfrm)) { #define setie(_ie, _off) ieee80211_ies_setie(ni->ni_ies, _ie, _off) if (wpa != NULL) setie(wpa_ie, wpa - sfrm); if (rsn != NULL) setie(rsn_ie, rsn - sfrm); if (htcap != NULL) setie(htcap_ie, htcap - sfrm); if (wme != NULL) { setie(wme_ie, wme - sfrm); /* * Mark node as capable of QoS. */ ni->ni_flags |= IEEE80211_NODE_QOS; if (ieee80211_parse_wmeie(wme, wh, ni) > 0) { if (ni->ni_uapsd != 0) ni->ni_flags |= IEEE80211_NODE_UAPSD; else ni->ni_flags &= ~IEEE80211_NODE_UAPSD; } } else ni->ni_flags &= ~(IEEE80211_NODE_QOS | IEEE80211_NODE_UAPSD); #ifdef IEEE80211_SUPPORT_SUPERG if (ath != NULL) { setie(ath_ie, ath - sfrm); /* * Parse ATH station parameters. */ ieee80211_parse_ath(ni, ni->ni_ies.ath_ie); } else #endif ni->ni_ath_flags = 0; #undef setie } else { ni->ni_flags &= ~IEEE80211_NODE_QOS; ni->ni_flags &= ~IEEE80211_NODE_UAPSD; ni->ni_ath_flags = 0; } ieee80211_node_join(ni, resp); ieee80211_deliver_l2uf(ni); break; } case IEEE80211_FC0_SUBTYPE_DEAUTH: case IEEE80211_FC0_SUBTYPE_DISASSOC: { #ifdef IEEE80211_DEBUG uint16_t reason; #endif if (vap->iv_state != IEEE80211_S_RUN || /* NB: can happen when in promiscuous mode */ !IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)) { vap->iv_stats.is_rx_mgtdiscard++; break; } /* * deauth/disassoc frame format * [2] reason */ IEEE80211_VERIFY_LENGTH(efrm - frm, 2, return); #ifdef IEEE80211_DEBUG reason = le16toh(*(uint16_t *)frm); #endif if (subtype == IEEE80211_FC0_SUBTYPE_DEAUTH) { vap->iv_stats.is_rx_deauth++; IEEE80211_NODE_STAT(ni, rx_deauth); } else { vap->iv_stats.is_rx_disassoc++; IEEE80211_NODE_STAT(ni, rx_disassoc); } IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, "recv %s (reason: %d (%s))", ieee80211_mgt_subtype_name(subtype), reason, ieee80211_reason_to_string(reason)); if (ni != vap->iv_bss) ieee80211_node_leave(ni); break; } case IEEE80211_FC0_SUBTYPE_ACTION: case IEEE80211_FC0_SUBTYPE_ACTION_NOACK: if (ni == vap->iv_bss) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "unknown node"); vap->iv_stats.is_rx_mgtdiscard++; } else if (!IEEE80211_ADDR_EQ(vap->iv_myaddr, wh->i_addr1) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not for us"); vap->iv_stats.is_rx_mgtdiscard++; } else if (vap->iv_state != IEEE80211_S_RUN) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "wrong state %s", ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_rx_mgtdiscard++; } else { if (ieee80211_parse_action(ni, m0) == 0) (void)ic->ic_recv_action(ni, wh, frm, efrm); } break; case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: case IEEE80211_FC0_SUBTYPE_TIMING_ADV: case IEEE80211_FC0_SUBTYPE_ATIM: IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not handled"); vap->iv_stats.is_rx_mgtdiscard++; break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "mgt", "subtype 0x%x not handled", subtype); vap->iv_stats.is_rx_badsubtype++; break; } } static void hostap_recv_ctl(struct ieee80211_node *ni, struct mbuf *m, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_PS_POLL: ni->ni_vap->iv_recv_pspoll(ni, m); break; case IEEE80211_FC0_SUBTYPE_BAR: ieee80211_recv_bar(ni, m); break; } } /* * Process a received ps-poll frame. */ void ieee80211_recv_pspoll(struct ieee80211_node *ni, struct mbuf *m0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_frame_min *wh; struct mbuf *m; uint16_t aid; int qlen; wh = mtod(m0, struct ieee80211_frame_min *); if (ni->ni_associd == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG, (struct ieee80211_frame *) wh, NULL, "%s", "unassociated station"); vap->iv_stats.is_ps_unassoc++; IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_NOT_ASSOCED); return; } aid = le16toh(*(uint16_t *)wh->i_dur); if (aid != ni->ni_associd) { IEEE80211_DISCARD(vap, IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG, (struct ieee80211_frame *) wh, NULL, "aid mismatch: sta aid 0x%x poll aid 0x%x", ni->ni_associd, aid); vap->iv_stats.is_ps_badaid++; /* * NB: We used to deauth the station but it turns out * the Blackberry Curve 8230 (and perhaps other devices) * sometimes send the wrong AID when WME is negotiated. * Being more lenient here seems ok as we already check * the station is associated and we only return frames * queued for the station (i.e. we don't use the AID). */ return; } /* Okay, take the first queued packet and put it out... */ m = ieee80211_node_psq_dequeue(ni, &qlen); if (m == 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); m->m_flags |= M_MORE_DATA; } 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->m_flags |= M_PWR_SAV; /* bypass PS handling */ /* * Do the right thing; if it's an encap'ed frame then * call ieee80211_parent_xmitpkt() else * call ieee80211_vap_xmitpkt(). */ if (m->m_flags & M_ENCAP) { (void) ieee80211_parent_xmitpkt(ic, m); } else { (void) ieee80211_vap_xmitpkt(vap, m); } } diff --git a/sys/net80211/ieee80211_hwmp.c b/sys/net80211/ieee80211_hwmp.c index c47e845bb8a8..b69210768c54 100644 --- a/sys/net80211/ieee80211_hwmp.c +++ b/sys/net80211/ieee80211_hwmp.c @@ -1,2085 +1,2086 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2009 The FreeBSD Foundation * * This software was developed by Rui Paulo under sponsorship from the * FreeBSD Foundation. * * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ /* * IEEE 802.11s Hybrid Wireless Mesh Protocol, HWMP. * * Based on March 2009, D3.0 802.11s draft spec. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void hwmp_vattach(struct ieee80211vap *); static void hwmp_vdetach(struct ieee80211vap *); static int hwmp_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int hwmp_send_action(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN], uint8_t *, size_t); static uint8_t * hwmp_add_meshpreq(uint8_t *, const struct ieee80211_meshpreq_ie *); static uint8_t * hwmp_add_meshprep(uint8_t *, const struct ieee80211_meshprep_ie *); static uint8_t * hwmp_add_meshperr(uint8_t *, const struct ieee80211_meshperr_ie *); static uint8_t * hwmp_add_meshrann(uint8_t *, const struct ieee80211_meshrann_ie *); static void hwmp_rootmode_setup(struct ieee80211vap *); static void hwmp_rootmode_cb(void *); static void hwmp_rootmode_rann_cb(void *); static void hwmp_recv_preq(struct ieee80211vap *, struct ieee80211_node *, const struct ieee80211_frame *, const struct ieee80211_meshpreq_ie *); static int hwmp_send_preq(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN], struct ieee80211_meshpreq_ie *, struct timeval *, struct timeval *); static void hwmp_recv_prep(struct ieee80211vap *, struct ieee80211_node *, const struct ieee80211_frame *, const struct ieee80211_meshprep_ie *); static int hwmp_send_prep(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN], struct ieee80211_meshprep_ie *); static void hwmp_recv_perr(struct ieee80211vap *, struct ieee80211_node *, const struct ieee80211_frame *, const struct ieee80211_meshperr_ie *); static int hwmp_send_perr(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN], struct ieee80211_meshperr_ie *); static void hwmp_senderror(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN], struct ieee80211_mesh_route *, int); static void hwmp_recv_rann(struct ieee80211vap *, struct ieee80211_node *, const struct ieee80211_frame *, const struct ieee80211_meshrann_ie *); static int hwmp_send_rann(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN], struct ieee80211_meshrann_ie *); static struct ieee80211_node * hwmp_discover(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN], struct mbuf *); static void hwmp_peerdown(struct ieee80211_node *); static struct timeval ieee80211_hwmp_preqminint = { 0, 100000 }; static struct timeval ieee80211_hwmp_perrminint = { 0, 100000 }; /* NB: the Target Address set in a Proactive PREQ is the broadcast address. */ static const uint8_t broadcastaddr[IEEE80211_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; typedef uint32_t ieee80211_hwmp_seq; #define HWMP_SEQ_LT(a, b) ((int32_t)((a)-(b)) < 0) #define HWMP_SEQ_LEQ(a, b) ((int32_t)((a)-(b)) <= 0) #define HWMP_SEQ_EQ(a, b) ((int32_t)((a)-(b)) == 0) #define HWMP_SEQ_GT(a, b) ((int32_t)((a)-(b)) > 0) #define HWMP_SEQ_MAX(a, b) (a > b ? a : b) /* * Private extension of ieee80211_mesh_route. */ struct ieee80211_hwmp_route { ieee80211_hwmp_seq hr_seq; /* last HWMP seq seen from dst*/ ieee80211_hwmp_seq hr_preqid; /* last PREQ ID seen from dst */ ieee80211_hwmp_seq hr_origseq; /* seq. no. on our latest PREQ*/ struct timeval hr_lastpreq; /* last time we sent a PREQ */ struct timeval hr_lastrootconf; /* last sent PREQ root conf */ int hr_preqretries; /* number of discoveries */ int hr_lastdiscovery; /* last discovery in ticks */ }; struct ieee80211_hwmp_state { ieee80211_hwmp_seq hs_seq; /* next seq to be used */ ieee80211_hwmp_seq hs_preqid; /* next PREQ ID to be used */ int hs_rootmode; /* proactive HWMP */ struct timeval hs_lastperr; /* last time we sent a PERR */ struct callout hs_roottimer; uint8_t hs_maxhops; /* max hop count */ }; static SYSCTL_NODE(_net_wlan, OID_AUTO, hwmp, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "IEEE 802.11s HWMP parameters"); static int ieee80211_hwmp_targetonly = 0; SYSCTL_INT(_net_wlan_hwmp, OID_AUTO, targetonly, CTLFLAG_RW, &ieee80211_hwmp_targetonly, 0, "Set TO bit on generated PREQs"); static int ieee80211_hwmp_pathtimeout = -1; SYSCTL_PROC(_net_wlan_hwmp, OID_AUTO, pathlifetime, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, &ieee80211_hwmp_pathtimeout, 0, ieee80211_sysctl_msecs_ticks, "I", "path entry lifetime (ms)"); static int ieee80211_hwmp_maxpreq_retries = -1; SYSCTL_PROC(_net_wlan_hwmp, OID_AUTO, maxpreq_retries, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, &ieee80211_hwmp_maxpreq_retries, 0, ieee80211_sysctl_msecs_ticks, "I", "maximum number of preq retries"); static int ieee80211_hwmp_net_diameter_traversaltime = -1; SYSCTL_PROC(_net_wlan_hwmp, OID_AUTO, net_diameter_traversal_time, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, &ieee80211_hwmp_net_diameter_traversaltime, 0, ieee80211_sysctl_msecs_ticks, "I", "estimate traversal time across the MBSS (ms)"); static int ieee80211_hwmp_roottimeout = -1; SYSCTL_PROC(_net_wlan_hwmp, OID_AUTO, roottimeout, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, &ieee80211_hwmp_roottimeout, 0, ieee80211_sysctl_msecs_ticks, "I", "root PREQ timeout (ms)"); static int ieee80211_hwmp_rootint = -1; SYSCTL_PROC(_net_wlan_hwmp, OID_AUTO, rootint, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, &ieee80211_hwmp_rootint, 0, ieee80211_sysctl_msecs_ticks, "I", "root interval (ms)"); static int ieee80211_hwmp_rannint = -1; SYSCTL_PROC(_net_wlan_hwmp, OID_AUTO, rannint, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, &ieee80211_hwmp_rannint, 0, ieee80211_sysctl_msecs_ticks, "I", "root announcement interval (ms)"); static struct timeval ieee80211_hwmp_rootconfint = { 0, 0 }; static int ieee80211_hwmp_rootconfint_internal = -1; SYSCTL_PROC(_net_wlan_hwmp, OID_AUTO, rootconfint, CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, &ieee80211_hwmp_rootconfint_internal, 0, ieee80211_sysctl_msecs_ticks, "I", "root confirmation interval (ms) (read-only)"); #define IEEE80211_HWMP_DEFAULT_MAXHOPS 31 static ieee80211_recv_action_func hwmp_recv_action_meshpath; static struct ieee80211_mesh_proto_path mesh_proto_hwmp = { .mpp_descr = "HWMP", .mpp_ie = IEEE80211_MESHCONF_PATH_HWMP, .mpp_discover = hwmp_discover, .mpp_peerdown = hwmp_peerdown, .mpp_senderror = hwmp_senderror, .mpp_vattach = hwmp_vattach, .mpp_vdetach = hwmp_vdetach, .mpp_newstate = hwmp_newstate, .mpp_privlen = sizeof(struct ieee80211_hwmp_route), }; SYSCTL_PROC(_net_wlan_hwmp, OID_AUTO, inact, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &mesh_proto_hwmp.mpp_inact, 0, ieee80211_sysctl_msecs_ticks, "I", "mesh route inactivity timeout (ms)"); static void ieee80211_hwmp_init(void) { /* Default values as per amendment */ ieee80211_hwmp_pathtimeout = msecs_to_ticks(5*1000); ieee80211_hwmp_roottimeout = msecs_to_ticks(5*1000); ieee80211_hwmp_rootint = msecs_to_ticks(2*1000); ieee80211_hwmp_rannint = msecs_to_ticks(1*1000); ieee80211_hwmp_rootconfint_internal = msecs_to_ticks(2*1000); ieee80211_hwmp_maxpreq_retries = 3; /* * (TU): A measurement of time equal to 1024 μs, * 500 TU is 512 ms. */ ieee80211_hwmp_net_diameter_traversaltime = msecs_to_ticks(512); /* * NB: I dont know how to make SYSCTL_PROC that calls ms to ticks * and return a struct timeval... */ ieee80211_hwmp_rootconfint.tv_usec = ieee80211_hwmp_rootconfint_internal * 1000; /* * Register action frame handler. */ ieee80211_recv_action_register(IEEE80211_ACTION_CAT_MESH, IEEE80211_ACTION_MESH_HWMP, hwmp_recv_action_meshpath); /* NB: default is 5 secs per spec */ mesh_proto_hwmp.mpp_inact = msecs_to_ticks(5*1000); /* * Register HWMP. */ ieee80211_mesh_register_proto_path(&mesh_proto_hwmp); } SYSINIT(wlan_hwmp, SI_SUB_DRIVERS, SI_ORDER_SECOND, ieee80211_hwmp_init, NULL); static void hwmp_vattach(struct ieee80211vap *vap) { struct ieee80211_hwmp_state *hs; KASSERT(vap->iv_opmode == IEEE80211_M_MBSS, ("not a mesh vap, opmode %d", vap->iv_opmode)); hs = IEEE80211_MALLOC(sizeof(struct ieee80211_hwmp_state), M_80211_VAP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (hs == NULL) { - printf("%s: couldn't alloc HWMP state\n", __func__); + net80211_vap_printf(vap, "%s: couldn't alloc HWMP state\n", + __func__); return; } hs->hs_maxhops = IEEE80211_HWMP_DEFAULT_MAXHOPS; callout_init(&hs->hs_roottimer, 1); vap->iv_hwmp = hs; } static void hwmp_vdetach(struct ieee80211vap *vap) { struct ieee80211_hwmp_state *hs = vap->iv_hwmp; callout_drain(&hs->hs_roottimer); IEEE80211_FREE(vap->iv_hwmp, M_80211_VAP); vap->iv_hwmp = NULL; } static int hwmp_newstate(struct ieee80211vap *vap, enum ieee80211_state ostate, int arg) { enum ieee80211_state nstate = vap->iv_state; struct ieee80211_hwmp_state *hs = vap->iv_hwmp; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (%d)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg); if (nstate != IEEE80211_S_RUN && ostate == IEEE80211_S_RUN) callout_drain(&hs->hs_roottimer); if (nstate == IEEE80211_S_RUN) hwmp_rootmode_setup(vap); return 0; } /* * Verify the length of an HWMP PREQ and return the number * of destinations >= 1, if verification fails -1 is returned. */ static int verify_mesh_preq_len(struct ieee80211vap *vap, const struct ieee80211_frame *wh, const uint8_t *iefrm) { int alloc_sz = -1; int ndest = -1; if (iefrm[2] & IEEE80211_MESHPREQ_FLAGS_AE) { /* Originator External Address present */ alloc_sz = IEEE80211_MESHPREQ_BASE_SZ_AE; ndest = iefrm[IEEE80211_MESHPREQ_TCNT_OFFSET_AE]; } else { /* w/o Originator External Address */ alloc_sz = IEEE80211_MESHPREQ_BASE_SZ; ndest = iefrm[IEEE80211_MESHPREQ_TCNT_OFFSET]; } alloc_sz += ndest * IEEE80211_MESHPREQ_TRGT_SZ; if(iefrm[1] != (alloc_sz)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_HWMP, wh, NULL, "PREQ (AE=%s) with wrong len", iefrm[2] & IEEE80211_MESHPREQ_FLAGS_AE ? "1" : "0"); return (-1); } return ndest; } /* * Verify the length of an HWMP PREP and returns 1 on success, * otherwise -1. */ static int verify_mesh_prep_len(struct ieee80211vap *vap, const struct ieee80211_frame *wh, const uint8_t *iefrm) { int alloc_sz = -1; if (iefrm[2] & IEEE80211_MESHPREP_FLAGS_AE) { if (iefrm[1] == IEEE80211_MESHPREP_BASE_SZ_AE) alloc_sz = IEEE80211_MESHPREP_BASE_SZ_AE; } else if (iefrm[1] == IEEE80211_MESHPREP_BASE_SZ) alloc_sz = IEEE80211_MESHPREP_BASE_SZ; if(alloc_sz < 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_HWMP, wh, NULL, "PREP (AE=%s) with wrong len", iefrm[2] & IEEE80211_MESHPREP_FLAGS_AE ? "1" : "0"); return (-1); } return (1); } /* * Verify the length of an HWMP PERR and return the number * of destinations >= 1, if verification fails -1 is returned. */ static int verify_mesh_perr_len(struct ieee80211vap *vap, const struct ieee80211_frame *wh, const uint8_t *iefrm) { int alloc_sz = -1; const uint8_t *iefrm_t = iefrm; uint8_t ndest = iefrm_t[IEEE80211_MESHPERR_NDEST_OFFSET]; int i; if(ndest > IEEE80211_MESHPERR_MAXDEST) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_HWMP, wh, NULL, "PERR with wrong number of destionat (>19), %u", ndest); return (-1); } iefrm_t += IEEE80211_MESHPERR_NDEST_OFFSET + 1; /* flag is next field */ /* We need to check each destination flag to know size */ for(i = 0; ini_vap; struct ieee80211_meshpreq_ie *preq; struct ieee80211_meshprep_ie *prep; struct ieee80211_meshperr_ie *perr; struct ieee80211_meshrann_ie rann; const uint8_t *iefrm = frm + 2; /* action + code */ const uint8_t *iefrm_t = iefrm; /* temporary pointer */ int ndest = -1; int found = 0; while (efrm - iefrm > 1) { IEEE80211_VERIFY_LENGTH(efrm - iefrm, iefrm[1] + 2, return 0); switch (*iefrm) { case IEEE80211_ELEMID_MESHPREQ: { int i = 0; iefrm_t = iefrm; ndest = verify_mesh_preq_len(vap, wh, iefrm_t); if (ndest < 0) { vap->iv_stats.is_rx_mgtdiscard++; break; } preq = IEEE80211_MALLOC(sizeof(*preq) + (ndest - 1) * sizeof(*preq->preq_targets), M_80211_MESH_PREQ, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); KASSERT(preq != NULL, ("preq == NULL")); preq->preq_ie = *iefrm_t++; preq->preq_len = *iefrm_t++; preq->preq_flags = *iefrm_t++; preq->preq_hopcount = *iefrm_t++; preq->preq_ttl = *iefrm_t++; preq->preq_id = le32dec(iefrm_t); iefrm_t += 4; IEEE80211_ADDR_COPY(preq->preq_origaddr, iefrm_t); iefrm_t += 6; preq->preq_origseq = le32dec(iefrm_t); iefrm_t += 4; /* NB: may have Originator Proxied Address */ if (preq->preq_flags & IEEE80211_MESHPREQ_FLAGS_AE) { IEEE80211_ADDR_COPY( preq->preq_orig_ext_addr, iefrm_t); iefrm_t += 6; } preq->preq_lifetime = le32dec(iefrm_t); iefrm_t += 4; preq->preq_metric = le32dec(iefrm_t); iefrm_t += 4; preq->preq_tcount = *iefrm_t++; for (i = 0; i < preq->preq_tcount; i++) { preq->preq_targets[i].target_flags = *iefrm_t++; IEEE80211_ADDR_COPY( preq->preq_targets[i].target_addr, iefrm_t); iefrm_t += 6; preq->preq_targets[i].target_seq = le32dec(iefrm_t); iefrm_t += 4; } hwmp_recv_preq(vap, ni, wh, preq); IEEE80211_FREE(preq, M_80211_MESH_PREQ); found++; break; } case IEEE80211_ELEMID_MESHPREP: { iefrm_t = iefrm; ndest = verify_mesh_prep_len(vap, wh, iefrm_t); if (ndest < 0) { vap->iv_stats.is_rx_mgtdiscard++; break; } prep = IEEE80211_MALLOC(sizeof(*prep), M_80211_MESH_PREP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); KASSERT(prep != NULL, ("prep == NULL")); prep->prep_ie = *iefrm_t++; prep->prep_len = *iefrm_t++; prep->prep_flags = *iefrm_t++; prep->prep_hopcount = *iefrm_t++; prep->prep_ttl = *iefrm_t++; IEEE80211_ADDR_COPY(prep->prep_targetaddr, iefrm_t); iefrm_t += 6; prep->prep_targetseq = le32dec(iefrm_t); iefrm_t += 4; /* NB: May have Target Proxied Address */ if (prep->prep_flags & IEEE80211_MESHPREP_FLAGS_AE) { IEEE80211_ADDR_COPY( prep->prep_target_ext_addr, iefrm_t); iefrm_t += 6; } prep->prep_lifetime = le32dec(iefrm_t); iefrm_t += 4; prep->prep_metric = le32dec(iefrm_t); iefrm_t += 4; IEEE80211_ADDR_COPY(prep->prep_origaddr, iefrm_t); iefrm_t += 6; prep->prep_origseq = le32dec(iefrm_t); iefrm_t += 4; hwmp_recv_prep(vap, ni, wh, prep); IEEE80211_FREE(prep, M_80211_MESH_PREP); found++; break; } case IEEE80211_ELEMID_MESHPERR: { int i = 0; iefrm_t = iefrm; ndest = verify_mesh_perr_len(vap, wh, iefrm_t); if (ndest < 0) { vap->iv_stats.is_rx_mgtdiscard++; break; } perr = IEEE80211_MALLOC(sizeof(*perr) + (ndest - 1) * sizeof(*perr->perr_dests), M_80211_MESH_PERR, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); KASSERT(perr != NULL, ("perr == NULL")); perr->perr_ie = *iefrm_t++; perr->perr_len = *iefrm_t++; perr->perr_ttl = *iefrm_t++; perr->perr_ndests = *iefrm_t++; for (i = 0; iperr_ndests; i++) { perr->perr_dests[i].dest_flags = *iefrm_t++; IEEE80211_ADDR_COPY( perr->perr_dests[i].dest_addr, iefrm_t); iefrm_t += 6; perr->perr_dests[i].dest_seq = le32dec(iefrm_t); iefrm_t += 4; /* NB: May have Target Proxied Address */ if (perr->perr_dests[i].dest_flags & IEEE80211_MESHPERR_FLAGS_AE) { IEEE80211_ADDR_COPY( perr->perr_dests[i].dest_ext_addr, iefrm_t); iefrm_t += 6; } perr->perr_dests[i].dest_rcode = le16dec(iefrm_t); iefrm_t += 2; } hwmp_recv_perr(vap, ni, wh, perr); IEEE80211_FREE(perr, M_80211_MESH_PERR); found++; break; } case IEEE80211_ELEMID_MESHRANN: { const struct ieee80211_meshrann_ie *mrann = (const struct ieee80211_meshrann_ie *) iefrm; if (mrann->rann_len != sizeof(struct ieee80211_meshrann_ie) - 2) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_HWMP, wh, NULL, "%s", "RAN with wrong len"); vap->iv_stats.is_rx_mgtdiscard++; return 1; } memcpy(&rann, mrann, sizeof(rann)); rann.rann_seq = le32dec(&mrann->rann_seq); rann.rann_interval = le32dec(&mrann->rann_interval); rann.rann_metric = le32dec(&mrann->rann_metric); hwmp_recv_rann(vap, ni, wh, &rann); found++; break; } } iefrm += iefrm[1] + 2; } if (!found) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_HWMP, wh, NULL, "%s", "PATH SEL action without IE"); vap->iv_stats.is_rx_mgtdiscard++; } return 0; } static int hwmp_send_action(struct ieee80211vap *vap, const uint8_t da[IEEE80211_ADDR_LEN], uint8_t *ie, size_t len) { struct ieee80211_node *ni; struct ieee80211com *ic; struct ieee80211_bpf_params params; struct mbuf *m; uint8_t *frm; int ret; if (IEEE80211_IS_MULTICAST(da)) { ni = ieee80211_ref_node(vap->iv_bss); #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); #endif ieee80211_ref_node(ni); } else ni = ieee80211_mesh_find_txnode(vap, da); if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT, ni, "block %s frame in CAC state", "HWMP action"); vap->iv_stats.is_tx_badstate++; return EIO; /* XXX */ } KASSERT(ni != NULL, ("null node")); ic = ni->ni_ic; m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(struct ieee80211_action) + len ); if (m == NULL) { ieee80211_free_node(ni); vap->iv_stats.is_tx_nobuf++; return ENOMEM; } *frm++ = IEEE80211_ACTION_CAT_MESH; *frm++ = IEEE80211_ACTION_MESH_HWMP; switch (*ie) { case IEEE80211_ELEMID_MESHPREQ: frm = hwmp_add_meshpreq(frm, (struct ieee80211_meshpreq_ie *)ie); break; case IEEE80211_ELEMID_MESHPREP: frm = hwmp_add_meshprep(frm, (struct ieee80211_meshprep_ie *)ie); break; case IEEE80211_ELEMID_MESHPERR: frm = hwmp_add_meshperr(frm, (struct ieee80211_meshperr_ie *)ie); break; case IEEE80211_ELEMID_MESHRANN: frm = hwmp_add_meshrann(frm, (struct ieee80211_meshrann_ie *)ie); break; } m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); M_PREPEND(m, sizeof(struct ieee80211_frame), IEEE80211_M_NOWAIT); if (m == NULL) { ieee80211_free_node(ni); vap->iv_stats.is_tx_nobuf++; return ENOMEM; } IEEE80211_TX_LOCK(ic); ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_ACTION, IEEE80211_NONQOS_TID, vap->iv_myaddr, da, vap->iv_myaddr); m->m_flags |= M_ENCAP; /* mark encapsulated */ IEEE80211_NODE_STAT(ni, tx_mgmt); memset(¶ms, 0, sizeof(params)); params.ibp_pri = WME_AC_VO; params.ibp_rate0 = ni->ni_txparms->mgmtrate; if (IEEE80211_IS_MULTICAST(da)) params.ibp_try0 = 1; else params.ibp_try0 = ni->ni_txparms->maxretry; params.ibp_power = ni->ni_txpower; ret = ieee80211_raw_output(vap, ni, m, ¶ms); IEEE80211_TX_UNLOCK(ic); return (ret); } #define ADDSHORT(frm, v) do { \ le16enc(frm, v); \ frm += 2; \ } while (0) #define ADDWORD(frm, v) do { \ le32enc(frm, v); \ frm += 4; \ } while (0) /* * Add a Mesh Path Request IE to a frame. */ #define PREQ_TFLAGS(n) preq->preq_targets[n].target_flags #define PREQ_TADDR(n) preq->preq_targets[n].target_addr #define PREQ_TSEQ(n) preq->preq_targets[n].target_seq static uint8_t * hwmp_add_meshpreq(uint8_t *frm, const struct ieee80211_meshpreq_ie *preq) { int i; *frm++ = IEEE80211_ELEMID_MESHPREQ; *frm++ = preq->preq_len; /* len already calculated */ *frm++ = preq->preq_flags; *frm++ = preq->preq_hopcount; *frm++ = preq->preq_ttl; ADDWORD(frm, preq->preq_id); IEEE80211_ADDR_COPY(frm, preq->preq_origaddr); frm += 6; ADDWORD(frm, preq->preq_origseq); if (preq->preq_flags & IEEE80211_MESHPREQ_FLAGS_AE) { IEEE80211_ADDR_COPY(frm, preq->preq_orig_ext_addr); frm += 6; } ADDWORD(frm, preq->preq_lifetime); ADDWORD(frm, preq->preq_metric); *frm++ = preq->preq_tcount; for (i = 0; i < preq->preq_tcount; i++) { *frm++ = PREQ_TFLAGS(i); IEEE80211_ADDR_COPY(frm, PREQ_TADDR(i)); frm += 6; ADDWORD(frm, PREQ_TSEQ(i)); } return frm; } #undef PREQ_TFLAGS #undef PREQ_TADDR #undef PREQ_TSEQ /* * Add a Mesh Path Reply IE to a frame. */ static uint8_t * hwmp_add_meshprep(uint8_t *frm, const struct ieee80211_meshprep_ie *prep) { *frm++ = IEEE80211_ELEMID_MESHPREP; *frm++ = prep->prep_len; /* len already calculated */ *frm++ = prep->prep_flags; *frm++ = prep->prep_hopcount; *frm++ = prep->prep_ttl; IEEE80211_ADDR_COPY(frm, prep->prep_targetaddr); frm += 6; ADDWORD(frm, prep->prep_targetseq); if (prep->prep_flags & IEEE80211_MESHPREP_FLAGS_AE) { IEEE80211_ADDR_COPY(frm, prep->prep_target_ext_addr); frm += 6; } ADDWORD(frm, prep->prep_lifetime); ADDWORD(frm, prep->prep_metric); IEEE80211_ADDR_COPY(frm, prep->prep_origaddr); frm += 6; ADDWORD(frm, prep->prep_origseq); return frm; } /* * Add a Mesh Path Error IE to a frame. */ #define PERR_DFLAGS(n) perr->perr_dests[n].dest_flags #define PERR_DADDR(n) perr->perr_dests[n].dest_addr #define PERR_DSEQ(n) perr->perr_dests[n].dest_seq #define PERR_EXTADDR(n) perr->perr_dests[n].dest_ext_addr #define PERR_DRCODE(n) perr->perr_dests[n].dest_rcode static uint8_t * hwmp_add_meshperr(uint8_t *frm, const struct ieee80211_meshperr_ie *perr) { int i; *frm++ = IEEE80211_ELEMID_MESHPERR; *frm++ = perr->perr_len; /* len already calculated */ *frm++ = perr->perr_ttl; *frm++ = perr->perr_ndests; for (i = 0; i < perr->perr_ndests; i++) { *frm++ = PERR_DFLAGS(i); IEEE80211_ADDR_COPY(frm, PERR_DADDR(i)); frm += 6; ADDWORD(frm, PERR_DSEQ(i)); if (PERR_DFLAGS(i) & IEEE80211_MESHPERR_FLAGS_AE) { IEEE80211_ADDR_COPY(frm, PERR_EXTADDR(i)); frm += 6; } ADDSHORT(frm, PERR_DRCODE(i)); } return frm; } #undef PERR_DFLAGS #undef PERR_DADDR #undef PERR_DSEQ #undef PERR_EXTADDR #undef PERR_DRCODE /* * Add a Root Annoucement IE to a frame. */ static uint8_t * hwmp_add_meshrann(uint8_t *frm, const struct ieee80211_meshrann_ie *rann) { *frm++ = IEEE80211_ELEMID_MESHRANN; *frm++ = rann->rann_len; *frm++ = rann->rann_flags; *frm++ = rann->rann_hopcount; *frm++ = rann->rann_ttl; IEEE80211_ADDR_COPY(frm, rann->rann_addr); frm += 6; ADDWORD(frm, rann->rann_seq); ADDWORD(frm, rann->rann_interval); ADDWORD(frm, rann->rann_metric); return frm; } static void hwmp_rootmode_setup(struct ieee80211vap *vap) { struct ieee80211_hwmp_state *hs = vap->iv_hwmp; struct ieee80211_mesh_state *ms = vap->iv_mesh; switch (hs->hs_rootmode) { case IEEE80211_HWMP_ROOTMODE_DISABLED: callout_drain(&hs->hs_roottimer); ms->ms_flags &= ~IEEE80211_MESHFLAGS_ROOT; break; case IEEE80211_HWMP_ROOTMODE_NORMAL: case IEEE80211_HWMP_ROOTMODE_PROACTIVE: callout_reset(&hs->hs_roottimer, ieee80211_hwmp_rootint, hwmp_rootmode_cb, vap); ms->ms_flags |= IEEE80211_MESHFLAGS_ROOT; break; case IEEE80211_HWMP_ROOTMODE_RANN: callout_reset(&hs->hs_roottimer, ieee80211_hwmp_rannint, hwmp_rootmode_rann_cb, vap); ms->ms_flags |= IEEE80211_MESHFLAGS_ROOT; break; } } /* * Send a broadcast Path Request to find all nodes on the mesh. We are * called when the vap is configured as a HWMP root node. */ #define PREQ_TFLAGS(n) preq.preq_targets[n].target_flags #define PREQ_TADDR(n) preq.preq_targets[n].target_addr #define PREQ_TSEQ(n) preq.preq_targets[n].target_seq static void hwmp_rootmode_cb(void *arg) { struct ieee80211vap *vap = (struct ieee80211vap *)arg; struct ieee80211_hwmp_state *hs = vap->iv_hwmp; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_meshpreq_ie preq; IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, vap->iv_bss, "%s", "send broadcast PREQ"); preq.preq_flags = 0; if (ms->ms_flags & IEEE80211_MESHFLAGS_GATE) preq.preq_flags |= IEEE80211_MESHPREQ_FLAGS_GATE; if (hs->hs_rootmode == IEEE80211_HWMP_ROOTMODE_PROACTIVE) preq.preq_flags |= IEEE80211_MESHPREQ_FLAGS_PP; preq.preq_hopcount = 0; preq.preq_ttl = ms->ms_ttl; preq.preq_id = ++hs->hs_preqid; IEEE80211_ADDR_COPY(preq.preq_origaddr, vap->iv_myaddr); preq.preq_origseq = ++hs->hs_seq; preq.preq_lifetime = ticks_to_msecs(ieee80211_hwmp_roottimeout); preq.preq_metric = IEEE80211_MESHLMETRIC_INITIALVAL; preq.preq_tcount = 1; IEEE80211_ADDR_COPY(PREQ_TADDR(0), broadcastaddr); PREQ_TFLAGS(0) = IEEE80211_MESHPREQ_TFLAGS_TO | IEEE80211_MESHPREQ_TFLAGS_USN; PREQ_TSEQ(0) = 0; vap->iv_stats.is_hwmp_rootreqs++; /* NB: we enforce rate check ourself */ hwmp_send_preq(vap, broadcastaddr, &preq, NULL, NULL); hwmp_rootmode_setup(vap); } #undef PREQ_TFLAGS #undef PREQ_TADDR #undef PREQ_TSEQ /* * Send a Root Annoucement (RANN) to find all the nodes on the mesh. We are * called when the vap is configured as a HWMP RANN root node. */ static void hwmp_rootmode_rann_cb(void *arg) { struct ieee80211vap *vap = (struct ieee80211vap *)arg; struct ieee80211_hwmp_state *hs = vap->iv_hwmp; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_meshrann_ie rann; IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, vap->iv_bss, "%s", "send broadcast RANN"); rann.rann_flags = 0; if (ms->ms_flags & IEEE80211_MESHFLAGS_GATE) rann.rann_flags |= IEEE80211_MESHFLAGS_GATE; rann.rann_hopcount = 0; rann.rann_ttl = ms->ms_ttl; IEEE80211_ADDR_COPY(rann.rann_addr, vap->iv_myaddr); rann.rann_seq = ++hs->hs_seq; rann.rann_interval = ieee80211_hwmp_rannint; rann.rann_metric = IEEE80211_MESHLMETRIC_INITIALVAL; vap->iv_stats.is_hwmp_rootrann++; hwmp_send_rann(vap, broadcastaddr, &rann); hwmp_rootmode_setup(vap); } /* * Update forwarding information to TA if metric improves. */ static void hwmp_update_transmitter(struct ieee80211vap *vap, struct ieee80211_node *ni, const char *hwmp_frame) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rttran = NULL; /* Transmitter */ int metric = 0; rttran = ieee80211_mesh_rt_find(vap, ni->ni_macaddr); if (rttran == NULL) { rttran = ieee80211_mesh_rt_add(vap, ni->ni_macaddr); if (rttran == NULL) { IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "unable to add path to transmitter %6D of %s", ni->ni_macaddr, ":", hwmp_frame); vap->iv_stats.is_mesh_rtaddfailed++; return; } } metric = ms->ms_pmetric->mpm_metric(ni); if (!(rttran->rt_flags & IEEE80211_MESHRT_FLAGS_VALID) || rttran->rt_metric > metric) { IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "%s path to transmitter %6D of %s, metric %d:%d", rttran->rt_flags & IEEE80211_MESHRT_FLAGS_VALID ? "prefer" : "update", ni->ni_macaddr, ":", hwmp_frame, rttran->rt_metric, metric); IEEE80211_ADDR_COPY(rttran->rt_nexthop, ni->ni_macaddr); rttran->rt_metric = metric; rttran->rt_nhops = 1; ieee80211_mesh_rt_update(rttran, ms->ms_ppath->mpp_inact); rttran->rt_flags = IEEE80211_MESHRT_FLAGS_VALID; } } #define PREQ_TFLAGS(n) preq->preq_targets[n].target_flags #define PREQ_TADDR(n) preq->preq_targets[n].target_addr #define PREQ_TSEQ(n) preq->preq_targets[n].target_seq static void hwmp_recv_preq(struct ieee80211vap *vap, struct ieee80211_node *ni, const struct ieee80211_frame *wh, const struct ieee80211_meshpreq_ie *preq) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rtorig = NULL; struct ieee80211_mesh_route *rtorig_ext = NULL; struct ieee80211_mesh_route *rttarg = NULL; struct ieee80211_hwmp_route *hrorig = NULL; struct ieee80211_hwmp_route *hrtarg = NULL; struct ieee80211_hwmp_state *hs = vap->iv_hwmp; ieee80211_hwmp_seq preqid; /* last seen preqid for orig */ uint32_t metric = 0; /* * Ignore PREQs from us. Could happen because someone forward it * back to us. */ if (IEEE80211_ADDR_EQ(vap->iv_myaddr, preq->preq_origaddr)) return; IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "received PREQ, orig %6D, targ(0) %6D", preq->preq_origaddr, ":", PREQ_TADDR(0), ":"); /* * Acceptance criteria: (if the PREQ is not for us or not broadcast, * or an external mac address not proxied by us), * AND forwarding is disabled, discard this PREQ. */ rttarg = ieee80211_mesh_rt_find(vap, PREQ_TADDR(0)); if (!(ms->ms_flags & IEEE80211_MESHFLAGS_FWD) && (!IEEE80211_ADDR_EQ(vap->iv_myaddr, PREQ_TADDR(0)) || !IEEE80211_IS_MULTICAST(PREQ_TADDR(0)) || (rttarg != NULL && rttarg->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY && IEEE80211_ADDR_EQ(vap->iv_myaddr, rttarg->rt_mesh_gate)))) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_HWMP, preq->preq_origaddr, NULL, "%s", "not accepting PREQ"); return; } /* * Acceptance criteria: if unicast addressed * AND no valid forwarding for Target of PREQ, discard this PREQ. */ if(rttarg != NULL) hrtarg = IEEE80211_MESH_ROUTE_PRIV(rttarg, struct ieee80211_hwmp_route); /* Address mode: ucast */ if(preq->preq_flags & IEEE80211_MESHPREQ_FLAGS_AM && rttarg == NULL && !IEEE80211_ADDR_EQ(vap->iv_myaddr, PREQ_TADDR(0))) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_HWMP, preq->preq_origaddr, NULL, "unicast addressed PREQ of unknown target %6D", PREQ_TADDR(0), ":"); return; } /* PREQ ACCEPTED */ rtorig = ieee80211_mesh_rt_find(vap, preq->preq_origaddr); if (rtorig == NULL) { rtorig = ieee80211_mesh_rt_add(vap, preq->preq_origaddr); if (rtorig == NULL) { IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "unable to add orig path to %6D", preq->preq_origaddr, ":"); vap->iv_stats.is_mesh_rtaddfailed++; return; } IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "adding originator %6D", preq->preq_origaddr, ":"); } hrorig = IEEE80211_MESH_ROUTE_PRIV(rtorig, struct ieee80211_hwmp_route); /* record last seen preqid */ preqid = hrorig->hr_preqid; hrorig->hr_preqid = HWMP_SEQ_MAX(hrorig->hr_preqid, preq->preq_id); /* Data creation and update of forwarding information * according to Table 11C-8 for originator mesh STA. */ metric = preq->preq_metric + ms->ms_pmetric->mpm_metric(ni); if (HWMP_SEQ_GT(preq->preq_origseq, hrorig->hr_seq) || (HWMP_SEQ_EQ(preq->preq_origseq, hrorig->hr_seq) && metric < rtorig->rt_metric)) { hrorig->hr_seq = preq->preq_origseq; IEEE80211_ADDR_COPY(rtorig->rt_nexthop, wh->i_addr2); rtorig->rt_metric = metric; rtorig->rt_nhops = preq->preq_hopcount + 1; ieee80211_mesh_rt_update(rtorig, preq->preq_lifetime); /* Path to orig is valid now. * NB: we know it can't be Proxy, and if it is GATE * it will be marked below. */ rtorig->rt_flags = IEEE80211_MESHRT_FLAGS_VALID; } else if ((hrtarg != NULL && !HWMP_SEQ_EQ(hrtarg->hr_seq, PREQ_TSEQ(0))) || (rtorig->rt_flags & IEEE80211_MESHRT_FLAGS_VALID && preqid >= preq->preq_id)) { IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "discard PREQ from %6D, old seqno %u <= %u," " or old preqid %u < %u", preq->preq_origaddr, ":", preq->preq_origseq, hrorig->hr_seq, preq->preq_id, preqid); return; } /* Update forwarding information to TA if metric improves. */ hwmp_update_transmitter(vap, ni, "PREQ"); /* * Check if the PREQ is addressed to us. * or a Proxy currently gated by us. */ if (IEEE80211_ADDR_EQ(vap->iv_myaddr, PREQ_TADDR(0)) || (ms->ms_flags & IEEE80211_MESHFLAGS_GATE && rttarg != NULL && IEEE80211_ADDR_EQ(vap->iv_myaddr, rttarg->rt_mesh_gate) && rttarg->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY && rttarg->rt_flags & IEEE80211_MESHRT_FLAGS_VALID)) { struct ieee80211_meshprep_ie prep; /* * When we are the target we shall update our own HWMP seq * number with max of (current and preq->seq) + 1 */ hs->hs_seq = HWMP_SEQ_MAX(hs->hs_seq, PREQ_TSEQ(0)) + 1; prep.prep_flags = 0; prep.prep_hopcount = 0; prep.prep_metric = IEEE80211_MESHLMETRIC_INITIALVAL; IEEE80211_ADDR_COPY(prep.prep_targetaddr, vap->iv_myaddr); if (rttarg != NULL && /* if NULL it means we are the target */ rttarg->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY) { IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "reply for proxy %6D", rttarg->rt_dest, ":"); prep.prep_flags |= IEEE80211_MESHPREP_FLAGS_AE; IEEE80211_ADDR_COPY(prep.prep_target_ext_addr, rttarg->rt_dest); /* update proxy seqno to HWMP seqno */ rttarg->rt_ext_seq = hs->hs_seq; prep.prep_hopcount = rttarg->rt_nhops; prep.prep_metric = rttarg->rt_metric; IEEE80211_ADDR_COPY(prep.prep_targetaddr, rttarg->rt_mesh_gate); } /* * Build and send a PREP frame. */ prep.prep_ttl = ms->ms_ttl; prep.prep_targetseq = hs->hs_seq; prep.prep_lifetime = preq->preq_lifetime; IEEE80211_ADDR_COPY(prep.prep_origaddr, preq->preq_origaddr); prep.prep_origseq = preq->preq_origseq; IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "reply to %6D", preq->preq_origaddr, ":"); hwmp_send_prep(vap, wh->i_addr2, &prep); return; } /* we may update our proxy information for the orig external */ else if (preq->preq_flags & IEEE80211_MESHPREQ_FLAGS_AE) { rtorig_ext = ieee80211_mesh_rt_find(vap, preq->preq_orig_ext_addr); if (rtorig_ext == NULL) { rtorig_ext = ieee80211_mesh_rt_add(vap, preq->preq_orig_ext_addr); if (rtorig_ext == NULL) { IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "unable to add orig ext proxy to %6D", preq->preq_orig_ext_addr, ":"); vap->iv_stats.is_mesh_rtaddfailed++; return; } IEEE80211_ADDR_COPY(rtorig_ext->rt_mesh_gate, preq->preq_origaddr); } rtorig_ext->rt_ext_seq = preq->preq_origseq; ieee80211_mesh_rt_update(rtorig_ext, preq->preq_lifetime); } /* * Proactive PREQ: reply with a proactive PREP to the * root STA if requested. */ if (IEEE80211_ADDR_EQ(PREQ_TADDR(0), broadcastaddr) && (PREQ_TFLAGS(0) & IEEE80211_MESHPREQ_TFLAGS_TO)) { IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "root mesh station @ %6D", preq->preq_origaddr, ":"); /* Check if root is a mesh gate, mark it */ if (preq->preq_flags & IEEE80211_MESHPREQ_FLAGS_GATE) { struct ieee80211_mesh_gate_route *gr; rtorig->rt_flags |= IEEE80211_MESHRT_FLAGS_GATE; gr = ieee80211_mesh_mark_gate(vap, preq->preq_origaddr, rtorig); gr->gr_lastseq = 0; /* NOT GANN */ } /* * Reply with a PREP if we don't have a path to the root * or if the root sent us a proactive PREQ. */ if ((rtorig->rt_flags & IEEE80211_MESHRT_FLAGS_VALID) == 0 || (preq->preq_flags & IEEE80211_MESHPREQ_FLAGS_PP)) { struct ieee80211_meshprep_ie prep; prep.prep_flags = 0; prep.prep_hopcount = 0; prep.prep_ttl = ms->ms_ttl; IEEE80211_ADDR_COPY(prep.prep_origaddr, preq->preq_origaddr); prep.prep_origseq = preq->preq_origseq; prep.prep_lifetime = preq->preq_lifetime; prep.prep_metric = IEEE80211_MESHLMETRIC_INITIALVAL; IEEE80211_ADDR_COPY(prep.prep_targetaddr, vap->iv_myaddr); prep.prep_targetseq = ++hs->hs_seq; hwmp_send_prep(vap, rtorig->rt_nexthop, &prep); } } /* * Forwarding and Intermediate reply for PREQs with 1 target. */ if ((preq->preq_tcount == 1) && (preq->preq_ttl > 1) && (ms->ms_flags & IEEE80211_MESHFLAGS_FWD)) { struct ieee80211_meshpreq_ie ppreq; /* propagated PREQ */ memcpy(&ppreq, preq, sizeof(ppreq)); /* * We have a valid route to this node. * NB: if target is proxy dont reply. */ if (rttarg != NULL && rttarg->rt_flags & IEEE80211_MESHRT_FLAGS_VALID && !(rttarg->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY)) { /* * Check if we can send an intermediate Path Reply, * i.e., Target Only bit is not set and target is not * the MAC broadcast address. */ if (!(PREQ_TFLAGS(0) & IEEE80211_MESHPREQ_TFLAGS_TO) && !IEEE80211_ADDR_EQ(PREQ_TADDR(0), broadcastaddr)) { struct ieee80211_meshprep_ie prep; IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "intermediate reply for PREQ from %6D", preq->preq_origaddr, ":"); prep.prep_flags = 0; prep.prep_hopcount = rttarg->rt_nhops; prep.prep_ttl = ms->ms_ttl; IEEE80211_ADDR_COPY(&prep.prep_targetaddr, PREQ_TADDR(0)); prep.prep_targetseq = hrtarg->hr_seq; prep.prep_lifetime = preq->preq_lifetime; prep.prep_metric =rttarg->rt_metric; IEEE80211_ADDR_COPY(&prep.prep_origaddr, preq->preq_origaddr); prep.prep_origseq = hrorig->hr_seq; hwmp_send_prep(vap, rtorig->rt_nexthop, &prep); /* * Set TO and unset RF bits because we have * sent a PREP. */ ppreq.preq_targets[0].target_flags |= IEEE80211_MESHPREQ_TFLAGS_TO; } } IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "forward PREQ from %6D", preq->preq_origaddr, ":"); ppreq.preq_hopcount += 1; ppreq.preq_ttl -= 1; ppreq.preq_metric += ms->ms_pmetric->mpm_metric(ni); /* don't do PREQ ratecheck when we propagate */ hwmp_send_preq(vap, broadcastaddr, &ppreq, NULL, NULL); } } #undef PREQ_TFLAGS #undef PREQ_TADDR #undef PREQ_TSEQ static int hwmp_send_preq(struct ieee80211vap *vap, const uint8_t da[IEEE80211_ADDR_LEN], struct ieee80211_meshpreq_ie *preq, struct timeval *last, struct timeval *minint) { /* * Enforce PREQ interval. * NB: Proactive ROOT PREQs rate is handled by cb task. */ if (last != NULL && minint != NULL) { if (ratecheck(last, minint) == 0) return EALREADY; /* XXX: we should postpone */ getmicrouptime(last); } /* * mesh preq action frame format * [6] da * [6] sa * [6] addr3 = sa * [1] action * [1] category * [tlv] mesh path request */ preq->preq_ie = IEEE80211_ELEMID_MESHPREQ; preq->preq_len = (preq->preq_flags & IEEE80211_MESHPREQ_FLAGS_AE ? IEEE80211_MESHPREQ_BASE_SZ_AE : IEEE80211_MESHPREQ_BASE_SZ) + preq->preq_tcount * IEEE80211_MESHPREQ_TRGT_SZ; return hwmp_send_action(vap, da, (uint8_t *)preq, preq->preq_len+2); } static void hwmp_recv_prep(struct ieee80211vap *vap, struct ieee80211_node *ni, const struct ieee80211_frame *wh, const struct ieee80211_meshprep_ie *prep) { #define IS_PROXY(rt) (rt->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY) #define PROXIED_BY_US(rt) \ (IEEE80211_ADDR_EQ(vap->iv_myaddr, rt->rt_mesh_gate)) struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_hwmp_state *hs = vap->iv_hwmp; struct ieee80211_mesh_route *rt = NULL; struct ieee80211_mesh_route *rtorig = NULL; struct ieee80211_mesh_route *rtext = NULL; struct ieee80211_hwmp_route *hr; struct ieee80211com *ic = vap->iv_ic; struct mbuf *m, *next; uint32_t metric = 0; const uint8_t *addr; IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "received PREP, orig %6D, targ %6D", prep->prep_origaddr, ":", prep->prep_targetaddr, ":"); /* * Acceptance criteria: (If the corresponding PREP was not generated * by us OR not generated by an external mac that is not proxied by us) * AND forwarding is disabled, discard this PREP. */ rtorig = ieee80211_mesh_rt_find(vap, prep->prep_origaddr); if ((!IEEE80211_ADDR_EQ(vap->iv_myaddr, prep->prep_origaddr) || (rtorig != NULL && IS_PROXY(rtorig) && !PROXIED_BY_US(rtorig))) && !(ms->ms_flags & IEEE80211_MESHFLAGS_FWD)){ IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "discard PREP, orig(%6D) not proxied or generated by us", prep->prep_origaddr, ":"); return; } /* PREP ACCEPTED */ /* * If accepted shall create or update the active forwarding information * it maintains for the target mesh STA of the PREP (according to the * rules defined in 13.10.8.4). If the conditions for creating or * updating the forwarding information have not been met in those * rules, no further steps are applied to the PREP. */ rt = ieee80211_mesh_rt_find(vap, prep->prep_targetaddr); if (rt == NULL) { rt = ieee80211_mesh_rt_add(vap, prep->prep_targetaddr); if (rt == NULL) { IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "unable to add PREP path to %6D", prep->prep_targetaddr, ":"); vap->iv_stats.is_mesh_rtaddfailed++; return; } IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "adding target %6D", prep->prep_targetaddr, ":"); } hr = IEEE80211_MESH_ROUTE_PRIV(rt, struct ieee80211_hwmp_route); /* update path metric */ metric = prep->prep_metric + ms->ms_pmetric->mpm_metric(ni); if ((rt->rt_flags & IEEE80211_MESHRT_FLAGS_VALID)) { if (HWMP_SEQ_LT(prep->prep_targetseq, hr->hr_seq)) { IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "discard PREP from %6D, old seq no %u < %u", prep->prep_targetaddr, ":", prep->prep_targetseq, hr->hr_seq); return; } else if (HWMP_SEQ_LEQ(prep->prep_targetseq, hr->hr_seq) && metric > rt->rt_metric) { IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "discard PREP from %6D, new metric %u > %u", prep->prep_targetaddr, ":", metric, rt->rt_metric); return; } } IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "%s path to %6D, hopcount %d:%d metric %d:%d", rt->rt_flags & IEEE80211_MESHRT_FLAGS_VALID ? "prefer" : "update", prep->prep_targetaddr, ":", rt->rt_nhops, prep->prep_hopcount + 1, rt->rt_metric, metric); hr->hr_seq = prep->prep_targetseq; hr->hr_preqretries = 0; IEEE80211_ADDR_COPY(rt->rt_nexthop, ni->ni_macaddr); rt->rt_metric = metric; rt->rt_nhops = prep->prep_hopcount + 1; ieee80211_mesh_rt_update(rt, prep->prep_lifetime); if (rt->rt_flags & IEEE80211_MESHRT_FLAGS_DISCOVER) { /* discovery complete */ rt->rt_flags &= ~IEEE80211_MESHRT_FLAGS_DISCOVER; } rt->rt_flags |= IEEE80211_MESHRT_FLAGS_VALID; /* mark valid */ /* Update forwarding information to TA if metric improves */ hwmp_update_transmitter(vap, ni, "PREP"); /* * If it's NOT for us, propagate the PREP */ if (!IEEE80211_ADDR_EQ(vap->iv_myaddr, prep->prep_origaddr) && prep->prep_ttl > 1 && prep->prep_hopcount < hs->hs_maxhops) { struct ieee80211_meshprep_ie pprep; /* propagated PREP */ /* * NB: We should already have setup the path to orig * mesh STA when we propagated PREQ to target mesh STA, * no PREP is generated without a corresponding PREQ. * XXX: for now just ignore. */ if (rtorig == NULL) { IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "received PREP for an unknown orig(%6D)", prep->prep_origaddr, ":"); return; } IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "propagate PREP from %6D", prep->prep_targetaddr, ":"); memcpy(&pprep, prep, sizeof(pprep)); pprep.prep_hopcount += 1; pprep.prep_ttl -= 1; pprep.prep_metric += ms->ms_pmetric->mpm_metric(ni); hwmp_send_prep(vap, rtorig->rt_nexthop, &pprep); /* precursor list for the Target Mesh STA Address is updated */ } /* * Check if we received a PREP w/ AE and store target external address. * We may store target external address if received PREP w/ AE * and we are not final destination */ if (prep->prep_flags & IEEE80211_MESHPREP_FLAGS_AE) { rtext = ieee80211_mesh_rt_find(vap, prep->prep_target_ext_addr); if (rtext == NULL) { rtext = ieee80211_mesh_rt_add(vap, prep->prep_target_ext_addr); if (rtext == NULL) { IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "unable to add PREP path to proxy %6D", prep->prep_targetaddr, ":"); vap->iv_stats.is_mesh_rtaddfailed++; return; } } IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "%s path to %6D, hopcount %d:%d metric %d:%d", rtext->rt_flags & IEEE80211_MESHRT_FLAGS_VALID ? "prefer" : "update", prep->prep_target_ext_addr, ":", rtext->rt_nhops, prep->prep_hopcount + 1, rtext->rt_metric, metric); rtext->rt_flags = IEEE80211_MESHRT_FLAGS_PROXY | IEEE80211_MESHRT_FLAGS_VALID; IEEE80211_ADDR_COPY(rtext->rt_dest, prep->prep_target_ext_addr); IEEE80211_ADDR_COPY(rtext->rt_mesh_gate, prep->prep_targetaddr); IEEE80211_ADDR_COPY(rtext->rt_nexthop, wh->i_addr2); rtext->rt_metric = metric; rtext->rt_lifetime = prep->prep_lifetime; rtext->rt_nhops = prep->prep_hopcount + 1; rtext->rt_ext_seq = prep->prep_origseq; /* new proxy seq */ /* * XXX: proxy entries have no HWMP priv data, * nullify them to be sure? */ } /* * Check for frames queued awaiting path discovery. * XXX probably can tell exactly and avoid remove call * NB: hash may have false matches, if so they will get * stuck back on the stageq because there won't be * a path. */ addr = prep->prep_flags & IEEE80211_MESHPREP_FLAGS_AE ? prep->prep_target_ext_addr : prep->prep_targetaddr; m = ieee80211_ageq_remove(&ic->ic_stageq, (struct ieee80211_node *)(uintptr_t) ieee80211_mac_hash(ic, addr)); /* either dest or ext_dest */ /* * All frames in the stageq here should be non-M_ENCAP; or things * will get very unhappy. */ for (; m != NULL; m = next) { next = m->m_nextpkt; m->m_nextpkt = NULL; IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "flush queued frame %p len %d", m, m->m_pkthdr.len); /* * If the mbuf has M_ENCAP set, ensure we free it. * Note that after if_transmit() is called, m is invalid. */ (void) ieee80211_vap_xmitpkt(vap, m); } #undef IS_PROXY #undef PROXIED_BY_US } static int hwmp_send_prep(struct ieee80211vap *vap, const uint8_t da[IEEE80211_ADDR_LEN], struct ieee80211_meshprep_ie *prep) { /* NB: there's no PREP minimum interval. */ /* * mesh prep action frame format * [6] da * [6] sa * [6] addr3 = sa * [1] action * [1] category * [tlv] mesh path reply */ prep->prep_ie = IEEE80211_ELEMID_MESHPREP; prep->prep_len = prep->prep_flags & IEEE80211_MESHPREP_FLAGS_AE ? IEEE80211_MESHPREP_BASE_SZ_AE : IEEE80211_MESHPREP_BASE_SZ; return hwmp_send_action(vap, da, (uint8_t *)prep, prep->prep_len + 2); } #define PERR_DFLAGS(n) perr.perr_dests[n].dest_flags #define PERR_DADDR(n) perr.perr_dests[n].dest_addr #define PERR_DSEQ(n) perr.perr_dests[n].dest_seq #define PERR_DRCODE(n) perr.perr_dests[n].dest_rcode static void hwmp_peerdown(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_meshperr_ie perr; struct ieee80211_mesh_route *rt; struct ieee80211_hwmp_route *hr; rt = ieee80211_mesh_rt_find(vap, ni->ni_macaddr); if (rt == NULL) return; hr = IEEE80211_MESH_ROUTE_PRIV(rt, struct ieee80211_hwmp_route); IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "%s", "delete route entry"); perr.perr_ttl = ms->ms_ttl; perr.perr_ndests = 1; PERR_DFLAGS(0) = 0; if (hr->hr_seq == 0) PERR_DFLAGS(0) |= IEEE80211_MESHPERR_DFLAGS_USN; PERR_DFLAGS(0) |= IEEE80211_MESHPERR_DFLAGS_RC; IEEE80211_ADDR_COPY(PERR_DADDR(0), rt->rt_dest); PERR_DSEQ(0) = ++hr->hr_seq; PERR_DRCODE(0) = IEEE80211_REASON_MESH_PERR_DEST_UNREACH; /* NB: flush everything passing through peer */ ieee80211_mesh_rt_flush_peer(vap, ni->ni_macaddr); hwmp_send_perr(vap, broadcastaddr, &perr); } #undef PERR_DFLAGS #undef PERR_DADDR #undef PERR_DSEQ #undef PERR_DRCODE #define PERR_DFLAGS(n) perr->perr_dests[n].dest_flags #define PERR_DADDR(n) perr->perr_dests[n].dest_addr #define PERR_DSEQ(n) perr->perr_dests[n].dest_seq #define PERR_DEXTADDR(n) perr->perr_dests[n].dest_ext_addr static void hwmp_recv_perr(struct ieee80211vap *vap, struct ieee80211_node *ni, const struct ieee80211_frame *wh, const struct ieee80211_meshperr_ie *perr) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rt = NULL; struct ieee80211_mesh_route *rt_ext = NULL; struct ieee80211_hwmp_route *hr; struct ieee80211_meshperr_ie *pperr = NULL; int i, j = 0, forward = 0; IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "received PERR from %6D", wh->i_addr2, ":"); /* * if forwarding is true, prepare pperr */ if (ms->ms_flags & IEEE80211_MESHFLAGS_FWD) { forward = 1; pperr = IEEE80211_MALLOC(sizeof(*perr) + 31*sizeof(*perr->perr_dests), M_80211_MESH_PERR, IEEE80211_M_NOWAIT); /* XXX: magic number, 32 err dests */ } /* * Acceptance criteria: check if we have forwarding information * stored about destination, and that nexthop == TA of this PERR. * NB: we also build a new PERR to propagate in case we should forward. */ for (i = 0; i < perr->perr_ndests; i++) { rt = ieee80211_mesh_rt_find(vap, PERR_DADDR(i)); if (rt == NULL) continue; if (!IEEE80211_ADDR_EQ(rt->rt_nexthop, wh->i_addr2)) continue; /* found and accepted a PERR ndest element, process it... */ if (forward) memcpy(&pperr->perr_dests[j], &perr->perr_dests[i], sizeof(*perr->perr_dests)); hr = IEEE80211_MESH_ROUTE_PRIV(rt, struct ieee80211_hwmp_route); switch(PERR_DFLAGS(i)) { case (IEEE80211_REASON_MESH_PERR_NO_FI): if (PERR_DSEQ(i) == 0) { hr->hr_seq++; if (forward) { pperr->perr_dests[j].dest_seq = hr->hr_seq; } } else { hr->hr_seq = PERR_DSEQ(i); } rt->rt_flags &= ~IEEE80211_MESHRT_FLAGS_VALID; j++; break; case (IEEE80211_REASON_MESH_PERR_DEST_UNREACH): if(HWMP_SEQ_GT(PERR_DSEQ(i), hr->hr_seq)) { hr->hr_seq = PERR_DSEQ(i); rt->rt_flags &= ~IEEE80211_MESHRT_FLAGS_VALID; j++; } break; case (IEEE80211_REASON_MESH_PERR_NO_PROXY): rt_ext = ieee80211_mesh_rt_find(vap, PERR_DEXTADDR(i)); if (rt_ext != NULL) { rt_ext->rt_flags &= ~IEEE80211_MESHRT_FLAGS_VALID; j++; } break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_HWMP, wh, NULL, "PERR, unknown reason code %u\n", PERR_DFLAGS(i)); goto done; /* XXX: stats?? */ } ieee80211_mesh_rt_flush_peer(vap, PERR_DADDR(i)); KASSERT(j < 32, ("PERR, error ndest >= 32 (%u)", j)); } if (j == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_HWMP, wh, NULL, "%s", "PERR not accepted"); goto done; /* XXX: stats?? */ } /* * Propagate the PERR if we previously found it on our routing table. */ if (forward && perr->perr_ttl > 1) { IEEE80211_NOTE(vap, IEEE80211_MSG_HWMP, ni, "propagate PERR from %6D", wh->i_addr2, ":"); pperr->perr_ndests = j; pperr->perr_ttl--; hwmp_send_perr(vap, broadcastaddr, pperr); } done: if (pperr != NULL) IEEE80211_FREE(pperr, M_80211_MESH_PERR); } #undef PERR_DFLAGS #undef PERR_DADDR #undef PERR_DSEQ #undef PERR_DEXTADDR static int hwmp_send_perr(struct ieee80211vap *vap, const uint8_t da[IEEE80211_ADDR_LEN], struct ieee80211_meshperr_ie *perr) { struct ieee80211_hwmp_state *hs = vap->iv_hwmp; int i; uint8_t length = 0; /* * Enforce PERR interval. */ if (ratecheck(&hs->hs_lastperr, &ieee80211_hwmp_perrminint) == 0) return EALREADY; getmicrouptime(&hs->hs_lastperr); /* * mesh perr action frame format * [6] da * [6] sa * [6] addr3 = sa * [1] action * [1] category * [tlv] mesh path error */ perr->perr_ie = IEEE80211_ELEMID_MESHPERR; length = IEEE80211_MESHPERR_BASE_SZ; for (i = 0; iperr_ndests; i++) { if (perr->perr_dests[i].dest_flags & IEEE80211_MESHPERR_FLAGS_AE) { length += IEEE80211_MESHPERR_DEST_SZ_AE; continue ; } length += IEEE80211_MESHPERR_DEST_SZ; } perr->perr_len =length; return hwmp_send_action(vap, da, (uint8_t *)perr, perr->perr_len+2); } /* * Called from the rest of the net80211 code (mesh code for example). * NB: IEEE80211_REASON_MESH_PERR_DEST_UNREACH can be trigger by the fact that * a mesh STA is unable to forward an MSDU/MMPDU to a next-hop mesh STA. */ #define PERR_DFLAGS(n) perr.perr_dests[n].dest_flags #define PERR_DADDR(n) perr.perr_dests[n].dest_addr #define PERR_DSEQ(n) perr.perr_dests[n].dest_seq #define PERR_DEXTADDR(n) perr.perr_dests[n].dest_ext_addr #define PERR_DRCODE(n) perr.perr_dests[n].dest_rcode static void hwmp_senderror(struct ieee80211vap *vap, const uint8_t addr[IEEE80211_ADDR_LEN], struct ieee80211_mesh_route *rt, int rcode) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_hwmp_route *hr = NULL; struct ieee80211_meshperr_ie perr; if (rt != NULL) hr = IEEE80211_MESH_ROUTE_PRIV(rt, struct ieee80211_hwmp_route); perr.perr_ndests = 1; perr.perr_ttl = ms->ms_ttl; PERR_DFLAGS(0) = 0; PERR_DRCODE(0) = rcode; switch (rcode) { case IEEE80211_REASON_MESH_PERR_NO_FI: IEEE80211_ADDR_COPY(PERR_DADDR(0), addr); PERR_DSEQ(0) = 0; /* reserved */ break; case IEEE80211_REASON_MESH_PERR_NO_PROXY: KASSERT(rt != NULL, ("no proxy info for sending PERR")); KASSERT(rt->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY, ("route is not marked proxy")); PERR_DFLAGS(0) |= IEEE80211_MESHPERR_FLAGS_AE; IEEE80211_ADDR_COPY(PERR_DADDR(0), vap->iv_myaddr); PERR_DSEQ(0) = rt->rt_ext_seq; IEEE80211_ADDR_COPY(PERR_DEXTADDR(0), addr); break; case IEEE80211_REASON_MESH_PERR_DEST_UNREACH: KASSERT(rt != NULL, ("no route info for sending PERR")); IEEE80211_ADDR_COPY(PERR_DADDR(0), addr); PERR_DSEQ(0) = hr->hr_seq; break; default: KASSERT(0, ("unknown reason code for HWMP PERR (%u)", rcode)); } hwmp_send_perr(vap, broadcastaddr, &perr); } #undef PERR_DFLAGS #undef PEER_DADDR #undef PERR_DSEQ #undef PERR_DEXTADDR #undef PERR_DRCODE static void hwmp_recv_rann(struct ieee80211vap *vap, struct ieee80211_node *ni, const struct ieee80211_frame *wh, const struct ieee80211_meshrann_ie *rann) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_hwmp_state *hs = vap->iv_hwmp; struct ieee80211_mesh_route *rt = NULL; struct ieee80211_hwmp_route *hr; struct ieee80211_meshpreq_ie preq; struct ieee80211_meshrann_ie prann; if (IEEE80211_ADDR_EQ(rann->rann_addr, vap->iv_myaddr)) return; rt = ieee80211_mesh_rt_find(vap, rann->rann_addr); if (rt != NULL && rt->rt_flags & IEEE80211_MESHRT_FLAGS_VALID) { hr = IEEE80211_MESH_ROUTE_PRIV(rt, struct ieee80211_hwmp_route); /* Acceptance criteria: if RANN.seq < stored seq, discard RANN */ if (HWMP_SEQ_LT(rann->rann_seq, hr->hr_seq)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_HWMP, wh, NULL, "RANN seq %u < %u", rann->rann_seq, hr->hr_seq); return; } /* Acceptance criteria: if RANN.seq == stored seq AND * RANN.metric > stored metric, discard RANN */ if (HWMP_SEQ_EQ(rann->rann_seq, hr->hr_seq) && rann->rann_metric > rt->rt_metric) { IEEE80211_DISCARD(vap, IEEE80211_MSG_HWMP, wh, NULL, "RANN metric %u > %u", rann->rann_metric, rt->rt_metric); return; } } /* RANN ACCEPTED */ ieee80211_hwmp_rannint = rann->rann_interval; /* XXX: mtx lock? */ if (rt == NULL) { rt = ieee80211_mesh_rt_add(vap, rann->rann_addr); if (rt == NULL) { IEEE80211_DISCARD(vap, IEEE80211_MSG_HWMP, wh, NULL, "unable to add mac for RANN root %6D", rann->rann_addr, ":"); vap->iv_stats.is_mesh_rtaddfailed++; return; } } hr = IEEE80211_MESH_ROUTE_PRIV(rt, struct ieee80211_hwmp_route); /* Check if root is a mesh gate, mark it */ if (rann->rann_flags & IEEE80211_MESHRANN_FLAGS_GATE) { struct ieee80211_mesh_gate_route *gr; rt->rt_flags |= IEEE80211_MESHRT_FLAGS_GATE; gr = ieee80211_mesh_mark_gate(vap, rann->rann_addr, rt); gr->gr_lastseq = 0; /* NOT GANN */ } /* discovery timeout */ ieee80211_mesh_rt_update(rt, ticks_to_msecs(ieee80211_hwmp_roottimeout)); preq.preq_flags = IEEE80211_MESHPREQ_FLAGS_AM; preq.preq_hopcount = 0; preq.preq_ttl = ms->ms_ttl; preq.preq_id = 0; /* reserved */ IEEE80211_ADDR_COPY(preq.preq_origaddr, vap->iv_myaddr); preq.preq_origseq = ++hs->hs_seq; preq.preq_lifetime = ieee80211_hwmp_roottimeout; preq.preq_metric = IEEE80211_MESHLMETRIC_INITIALVAL; preq.preq_tcount = 1; preq.preq_targets[0].target_flags = IEEE80211_MESHPREQ_TFLAGS_TO; /* NB: IEEE80211_MESHPREQ_TFLAGS_USN = 0 implicitly implied */ IEEE80211_ADDR_COPY(preq.preq_targets[0].target_addr, rann->rann_addr); preq.preq_targets[0].target_seq = rann->rann_seq; /* XXX: if rootconfint have not passed, we built this preq in vain */ hwmp_send_preq(vap, wh->i_addr2, &preq, &hr->hr_lastrootconf, &ieee80211_hwmp_rootconfint); /* propagate a RANN */ if (rt->rt_flags & IEEE80211_MESHRT_FLAGS_VALID && rann->rann_ttl > 1 && ms->ms_flags & IEEE80211_MESHFLAGS_FWD) { hr->hr_seq = rann->rann_seq; memcpy(&prann, rann, sizeof(prann)); prann.rann_hopcount += 1; prann.rann_ttl -= 1; prann.rann_metric += ms->ms_pmetric->mpm_metric(ni); hwmp_send_rann(vap, broadcastaddr, &prann); } } static int hwmp_send_rann(struct ieee80211vap *vap, const uint8_t da[IEEE80211_ADDR_LEN], struct ieee80211_meshrann_ie *rann) { /* * mesh rann action frame format * [6] da * [6] sa * [6] addr3 = sa * [1] action * [1] category * [tlv] root announcement */ rann->rann_ie = IEEE80211_ELEMID_MESHRANN; rann->rann_len = IEEE80211_MESHRANN_BASE_SZ; return hwmp_send_action(vap, da, (uint8_t *)rann, rann->rann_len + 2); } #define PREQ_TFLAGS(n) preq.preq_targets[n].target_flags #define PREQ_TADDR(n) preq.preq_targets[n].target_addr #define PREQ_TSEQ(n) preq.preq_targets[n].target_seq static void hwmp_rediscover_cb(void *arg) { struct ieee80211_mesh_route *rt = arg; struct ieee80211vap *vap = rt->rt_vap; struct ieee80211_hwmp_state *hs = vap->iv_hwmp; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_hwmp_route *hr; struct ieee80211_meshpreq_ie preq; /* Optimize: storing first preq? */ if ((rt->rt_flags & IEEE80211_MESHRT_FLAGS_VALID)) return ; /* nothing to do */ hr = IEEE80211_MESH_ROUTE_PRIV(rt, struct ieee80211_hwmp_route); if (hr->hr_preqretries >= ieee80211_hwmp_maxpreq_retries) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ANY, rt->rt_dest, "%s", "max number of discovery, send queued frames to GATE"); ieee80211_mesh_forward_to_gates(vap, rt); vap->iv_stats.is_mesh_fwd_nopath++; return ; /* XXX: flush queue? */ } hr->hr_preqretries++; IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_HWMP, rt->rt_dest, "start path rediscovery , target seq %u", hr->hr_seq); /* * Try to discover the path for this node. * Group addressed PREQ Case A */ preq.preq_flags = 0; preq.preq_hopcount = 0; preq.preq_ttl = ms->ms_ttl; preq.preq_id = ++hs->hs_preqid; IEEE80211_ADDR_COPY(preq.preq_origaddr, vap->iv_myaddr); preq.preq_origseq = hr->hr_origseq; preq.preq_lifetime = ticks_to_msecs(ieee80211_hwmp_pathtimeout); preq.preq_metric = IEEE80211_MESHLMETRIC_INITIALVAL; preq.preq_tcount = 1; IEEE80211_ADDR_COPY(PREQ_TADDR(0), rt->rt_dest); PREQ_TFLAGS(0) = 0; if (ieee80211_hwmp_targetonly) PREQ_TFLAGS(0) |= IEEE80211_MESHPREQ_TFLAGS_TO; PREQ_TFLAGS(0) |= IEEE80211_MESHPREQ_TFLAGS_USN; PREQ_TSEQ(0) = 0; /* RESERVED when USN flag is set */ /* XXX check return value */ hwmp_send_preq(vap, broadcastaddr, &preq, &hr->hr_lastpreq, &ieee80211_hwmp_preqminint); callout_reset(&rt->rt_discovery, ieee80211_hwmp_net_diameter_traversaltime * 2, hwmp_rediscover_cb, rt); } static struct ieee80211_node * hwmp_discover(struct ieee80211vap *vap, const uint8_t dest[IEEE80211_ADDR_LEN], struct mbuf *m) { struct ieee80211_hwmp_state *hs = vap->iv_hwmp; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rt = NULL; struct ieee80211_hwmp_route *hr; struct ieee80211_meshpreq_ie preq; struct ieee80211_node *ni; int sendpreq = 0; KASSERT(vap->iv_opmode == IEEE80211_M_MBSS, ("not a mesh vap, opmode %d", vap->iv_opmode)); KASSERT(!IEEE80211_ADDR_EQ(vap->iv_myaddr, dest), ("%s: discovering self!", __func__)); ni = NULL; if (!IEEE80211_IS_MULTICAST(dest)) { rt = ieee80211_mesh_rt_find(vap, dest); if (rt == NULL) { rt = ieee80211_mesh_rt_add(vap, dest); if (rt == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_HWMP, "unable to add discovery path to %6D", dest, ":"); vap->iv_stats.is_mesh_rtaddfailed++; goto done; } } hr = IEEE80211_MESH_ROUTE_PRIV(rt, struct ieee80211_hwmp_route); if (rt->rt_flags & IEEE80211_MESHRT_FLAGS_DISCOVER) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_HWMP, dest, "%s", "already discovering queue frame until path found"); sendpreq = 1; goto done; } if ((rt->rt_flags & IEEE80211_MESHRT_FLAGS_VALID) == 0) { if (hr->hr_lastdiscovery != 0 && (ticks - hr->hr_lastdiscovery < (ieee80211_hwmp_net_diameter_traversaltime * 2))) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, dest, NULL, "%s", "too frequent discovery requeust"); sendpreq = 1; goto done; } hr->hr_lastdiscovery = ticks; if (hr->hr_preqretries >= ieee80211_hwmp_maxpreq_retries) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, dest, NULL, "%s", "no valid path , max number of discovery"); vap->iv_stats.is_mesh_fwd_nopath++; goto done; } rt->rt_flags = IEEE80211_MESHRT_FLAGS_DISCOVER; hr->hr_preqretries++; if (hr->hr_origseq == 0) hr->hr_origseq = ++hs->hs_seq; rt->rt_metric = IEEE80211_MESHLMETRIC_INITIALVAL; sendpreq = 1; IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_HWMP, dest, "start path discovery (src %s), target seq %u", m == NULL ? "" : ether_sprintf( mtod(m, struct ether_header *)->ether_shost), hr->hr_seq); /* * Try to discover the path for this node. * Group addressed PREQ Case A */ preq.preq_flags = 0; preq.preq_hopcount = 0; preq.preq_ttl = ms->ms_ttl; preq.preq_id = ++hs->hs_preqid; IEEE80211_ADDR_COPY(preq.preq_origaddr, vap->iv_myaddr); preq.preq_origseq = hr->hr_origseq; preq.preq_lifetime = ticks_to_msecs(ieee80211_hwmp_pathtimeout); preq.preq_metric = IEEE80211_MESHLMETRIC_INITIALVAL; preq.preq_tcount = 1; IEEE80211_ADDR_COPY(PREQ_TADDR(0), dest); PREQ_TFLAGS(0) = 0; if (ieee80211_hwmp_targetonly) PREQ_TFLAGS(0) |= IEEE80211_MESHPREQ_TFLAGS_TO; PREQ_TFLAGS(0) |= IEEE80211_MESHPREQ_TFLAGS_USN; PREQ_TSEQ(0) = 0; /* RESERVED when USN flag is set */ /* XXX check return value */ hwmp_send_preq(vap, broadcastaddr, &preq, &hr->hr_lastpreq, &ieee80211_hwmp_preqminint); callout_reset(&rt->rt_discovery, ieee80211_hwmp_net_diameter_traversaltime * 2, hwmp_rediscover_cb, rt); } if (rt->rt_flags & IEEE80211_MESHRT_FLAGS_VALID) ni = ieee80211_find_txnode(vap, rt->rt_nexthop); } else { ni = ieee80211_find_txnode(vap, dest); /* NB: if null then we leak mbuf */ KASSERT(ni != NULL, ("leak mcast frame")); return ni; } done: if (ni == NULL && m != NULL) { if (sendpreq) { struct ieee80211com *ic = vap->iv_ic; /* * Queue packet for transmit when path discovery * completes. If discovery never completes the * frame will be flushed by way of the aging timer. */ IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_HWMP, dest, "%s", "queue frame until path found"); MPASS((m->m_pkthdr.csum_flags & CSUM_SND_TAG) == 0); m->m_pkthdr.rcvif = (void *)(uintptr_t) ieee80211_mac_hash(ic, dest); /* XXX age chosen randomly */ ieee80211_ageq_append(&ic->ic_stageq, m, IEEE80211_INACT_WAIT); } else { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_HWMP, dest, NULL, "%s", "no valid path to this node"); m_freem(m); } } return ni; } #undef PREQ_TFLAGS #undef PREQ_TADDR #undef PREQ_TSEQ static int hwmp_ioctl_get80211(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211_hwmp_state *hs = vap->iv_hwmp; int error; if (vap->iv_opmode != IEEE80211_M_MBSS) return ENOSYS; error = 0; switch (ireq->i_type) { case IEEE80211_IOC_HWMP_ROOTMODE: ireq->i_val = hs->hs_rootmode; break; case IEEE80211_IOC_HWMP_MAXHOPS: ireq->i_val = hs->hs_maxhops; break; default: return ENOSYS; } return error; } IEEE80211_IOCTL_GET(hwmp, hwmp_ioctl_get80211); static int hwmp_ioctl_set80211(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211_hwmp_state *hs = vap->iv_hwmp; int error; if (vap->iv_opmode != IEEE80211_M_MBSS) return ENOSYS; error = 0; switch (ireq->i_type) { case IEEE80211_IOC_HWMP_ROOTMODE: if (ireq->i_val < 0 || ireq->i_val > 3) return EINVAL; hs->hs_rootmode = ireq->i_val; hwmp_rootmode_setup(vap); break; case IEEE80211_IOC_HWMP_MAXHOPS: if (ireq->i_val <= 0 || ireq->i_val > 255) return EINVAL; hs->hs_maxhops = ireq->i_val; break; default: return ENOSYS; } return error; } IEEE80211_IOCTL_SET(hwmp, hwmp_ioctl_set80211); diff --git a/sys/net80211/ieee80211_input.c b/sys/net80211/ieee80211_input.c index f8e9b4665b56..69ec286305b4 100644 --- a/sys/net80211/ieee80211_input.c +++ b/sys/net80211/ieee80211_input.c @@ -1,1060 +1,1065 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 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 #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_MESH #include #endif #include #ifdef INET #include #include #endif static void ieee80211_process_mimo(struct ieee80211_node *ni, struct ieee80211_rx_stats *rx) { int i; /* Verify the required MIMO bits are set */ if ((rx->r_flags & (IEEE80211_R_C_CHAIN | IEEE80211_R_C_NF | IEEE80211_R_C_RSSI)) != (IEEE80211_R_C_CHAIN | IEEE80211_R_C_NF | IEEE80211_R_C_RSSI)) return; /* XXX This assumes the MIMO radios have both ctl and ext chains */ for (i = 0; i < MIN(rx->c_chain, IEEE80211_MAX_CHAINS); i++) { IEEE80211_RSSI_LPF(ni->ni_mimo_rssi_ctl[i], rx->c_rssi_ctl[i]); IEEE80211_RSSI_LPF(ni->ni_mimo_rssi_ext[i], rx->c_rssi_ext[i]); } /* XXX This also assumes the MIMO radios have both ctl and ext chains */ for(i = 0; i < MIN(rx->c_chain, IEEE80211_MAX_CHAINS); i++) { ni->ni_mimo_noise_ctl[i] = rx->c_nf_ctl[i]; ni->ni_mimo_noise_ext[i] = rx->c_nf_ext[i]; } ni->ni_mimo_chains = rx->c_chain; } int ieee80211_input_mimo(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211_rx_stats rxs; /* try to read stats from mbuf */ bzero(&rxs, sizeof(rxs)); if (ieee80211_get_rx_params(m, &rxs) != 0) return (-1); /* XXX should assert IEEE80211_R_NF and IEEE80211_R_RSSI are set */ ieee80211_process_mimo(ni, &rxs); //return ieee80211_input(ni, m, rx->rssi, rx->nf); return ni->ni_vap->iv_input(ni, m, &rxs, rxs.c_rssi, rxs.c_nf); } int ieee80211_input_all(struct ieee80211com *ic, struct mbuf *m, int rssi, int nf) { struct ieee80211_rx_stats rx; rx.r_flags = IEEE80211_R_NF | IEEE80211_R_RSSI; rx.c_nf = nf; rx.c_rssi = rssi; if (!ieee80211_add_rx_params(m, &rx)) return (-1); return ieee80211_input_mimo_all(ic, m); } int ieee80211_input_mimo_all(struct ieee80211com *ic, struct mbuf *m) { struct ieee80211vap *vap; int type = -1; m->m_flags |= M_BCAST; /* NB: mark for bpf tap'ing */ /* XXX locking */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { struct ieee80211_node *ni; struct mbuf *mcopy; /* NB: could check for IFF_UP but this is cheaper */ if (vap->iv_state == IEEE80211_S_INIT) continue; /* * WDS vap's only receive directed traffic from the * station at the ``far end''. That traffic should * be passed through the AP vap the station is associated * to--so don't spam them with mcast frames. */ if (vap->iv_opmode == IEEE80211_M_WDS) continue; if (TAILQ_NEXT(vap, iv_next) != NULL) { /* * Packet contents are changed by ieee80211_decap * so do a deep copy of the packet. * NB: tags are copied too. */ mcopy = m_dup(m, IEEE80211_M_NOWAIT); if (mcopy == NULL) { /* XXX stat+msg */ continue; } } else { mcopy = m; m = NULL; } ni = ieee80211_ref_node(vap->iv_bss); type = ieee80211_input_mimo(ni, mcopy); ieee80211_free_node(ni); } if (m != NULL) /* no vaps, reclaim mbuf */ m_freem(m); return type; } /* * This function reassembles fragments. * * XXX should handle 3 concurrent reassemblies per-spec. */ struct mbuf * ieee80211_defrag(struct ieee80211_node *ni, struct mbuf *m, int hdrspace, int has_decrypted) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); struct ieee80211_frame *lwh; uint16_t rxseq; uint8_t fragno; uint8_t more_frag = wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG; struct mbuf *mfrag; KASSERT(!IEEE80211_IS_MULTICAST(wh->i_addr1), ("multicast fragm?")); rxseq = le16toh(*(uint16_t *)wh->i_seq); fragno = rxseq & IEEE80211_SEQ_FRAG_MASK; /* Quick way out, if there's nothing to defragment */ if (!more_frag && fragno == 0 && ni->ni_rxfrag[0] == NULL) return m; /* Temporarily set flag to remember if fragment was encrypted. */ /* XXX use a non-packet altering storage for this in the future. */ if (has_decrypted) wh->i_fc[1] |= IEEE80211_FC1_PROTECTED; /* * Remove frag to insure 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 */ m_freem(m); return NULL; } IEEE80211_NODE_LOCK(ni->ni_table); mfrag = ni->ni_rxfrag[0]; ni->ni_rxfrag[0] = NULL; IEEE80211_NODE_UNLOCK(ni->ni_table); /* * Validate new fragment is in order and * related to the previous ones. */ if (mfrag != NULL) { uint16_t last_rxseq; lwh = mtod(mfrag, struct ieee80211_frame *); last_rxseq = le16toh(*(uint16_t *)lwh->i_seq); /* * NB: check seq # and frag together. Also check that both * fragments are plaintext or that both are encrypted. */ if (rxseq == last_rxseq+1 && IEEE80211_ADDR_EQ(wh->i_addr1, lwh->i_addr1) && IEEE80211_ADDR_EQ(wh->i_addr2, lwh->i_addr2) && !((wh->i_fc[1] ^ lwh->i_fc[1]) & IEEE80211_FC1_PROTECTED)) { /* XXX clear MORE_FRAG bit? */ /* track last seqnum and fragno */ *(uint16_t *) lwh->i_seq = *(uint16_t *) wh->i_seq; m_adj(m, hdrspace); /* strip header */ m_catpkt(mfrag, m); /* concatenate */ } else { /* * Unrelated fragment or no space for it, * clear current fragments. */ m_freem(mfrag); mfrag = NULL; } } if (mfrag == NULL) { if (fragno != 0) { /* !first fragment, discard */ vap->iv_stats.is_rx_defrag++; IEEE80211_NODE_STAT(ni, rx_defrag); m_freem(m); return NULL; } mfrag = m; } if (more_frag) { /* more to come, save */ ni->ni_rxfragstamp = ticks; ni->ni_rxfrag[0] = mfrag; mfrag = NULL; } /* Remember to clear protected flag that was temporarily set. */ if (mfrag != NULL) { wh = mtod(mfrag, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; } return mfrag; } void ieee80211_deliver_data(struct ieee80211vap *vap, struct ieee80211_node *ni, struct mbuf *m) { struct ether_header *eh = mtod(m, struct ether_header *); struct ifnet *ifp = vap->iv_ifp; /* clear driver/net80211 flags before passing up */ m->m_flags &= ~(M_MCAST | M_BCAST); m_clrprotoflags(m); /* NB: see hostap_deliver_data, this path doesn't handle hostap */ KASSERT(vap->iv_opmode != IEEE80211_M_HOSTAP, ("gack, hostap")); /* * Do accounting. */ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); IEEE80211_NODE_STAT(ni, rx_data); IEEE80211_NODE_STAT_ADD(ni, rx_bytes, m->m_pkthdr.len); if (ETHER_IS_MULTICAST(eh->ether_dhost)) { if (ETHER_IS_BROADCAST(eh->ether_dhost)) m->m_flags |= M_BCAST; else m->m_flags |= M_MCAST; IEEE80211_NODE_STAT(ni, rx_mcast); } else IEEE80211_NODE_STAT(ni, rx_ucast); m->m_pkthdr.rcvif = ifp; if (ni->ni_vlan != 0) { /* attach vlan tag */ m->m_pkthdr.ether_vtag = ni->ni_vlan; m->m_flags |= M_VLANTAG; } ieee80211_vap_deliver_data(vap, m); } struct mbuf * ieee80211_decap(struct ieee80211vap *vap, struct mbuf *m, int hdrlen, uint8_t qos) { struct ieee80211_qosframe_addr4 wh; struct ether_header *eh; struct llc *llc; KASSERT(hdrlen <= sizeof(wh), ("hdrlen %d > max %zd", hdrlen, sizeof(wh))); if (m->m_len < hdrlen + sizeof(*llc) && (m = m_pullup(m, hdrlen + sizeof(*llc))) == NULL) { vap->iv_stats.is_rx_tooshort++; /* XXX msg */ return NULL; } memcpy(&wh, mtod(m, caddr_t), hdrlen); llc = (struct llc *)(mtod(m, caddr_t) + hdrlen); if (llc->llc_dsap == LLC_SNAP_LSAP && llc->llc_ssap == LLC_SNAP_LSAP && llc->llc_control == LLC_UI && llc->llc_snap.org_code[0] == 0 && llc->llc_snap.org_code[1] == 0 && llc->llc_snap.org_code[2] == 0 && /* NB: preserve AppleTalk frames that have a native SNAP hdr */ !(llc->llc_snap.ether_type == htons(ETHERTYPE_AARP) || llc->llc_snap.ether_type == htons(ETHERTYPE_IPX)) && /* Do not want to touch A-MSDU frames. */ !(qos & IEEE80211_QOS_AMSDU)) { m_adj(m, hdrlen + sizeof(struct llc) - sizeof(*eh)); llc = NULL; } else { m_adj(m, hdrlen - sizeof(*eh)); } eh = mtod(m, 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; } #ifndef __NO_STRICT_ALIGNMENT if (!ALIGNED_POINTER(mtod(m, caddr_t) + sizeof(*eh), uint32_t)) { m = ieee80211_realign(vap, m, sizeof(*eh)); if (m == NULL) return NULL; } #endif /* !__NO_STRICT_ALIGNMENT */ if (llc != NULL) { eh = mtod(m, struct ether_header *); eh->ether_type = htons(m->m_pkthdr.len - sizeof(*eh)); } return m; } /* * Decap a frame encapsulated in a fast-frame/A-MSDU. */ struct mbuf * ieee80211_decap1(struct mbuf *m, int *framelen) { #define FF_LLC_SIZE (sizeof(struct ether_header) + sizeof(struct llc)) struct ether_header *eh; struct llc *llc; const uint8_t llc_hdr_mac[ETHER_ADDR_LEN] = { /* MAC address matching the 802.2 LLC header */ LLC_SNAP_LSAP, LLC_SNAP_LSAP, LLC_UI, 0, 0, 0 }; /* * The frame has an 802.3 header followed by an 802.2 * LLC header. The encapsulated frame length is in the * first header type field; save that and overwrite it * with the true type field found in the second. Then * copy the 802.3 header up to where it belongs and * adjust the mbuf contents to remove the void. */ if (m->m_len < FF_LLC_SIZE && (m = m_pullup(m, FF_LLC_SIZE)) == NULL) return NULL; eh = mtod(m, struct ether_header *); /* 802.3 header is first */ /* * Detect possible attack where a single 802.11 frame is processed * as an A-MSDU frame due to an adversary setting the A-MSDU present * bit in the 802.11 QoS header. [FragAttacks] */ if (memcmp(eh->ether_dhost, llc_hdr_mac, ETHER_ADDR_LEN) == 0) return NULL; llc = (struct llc *)&eh[1]; /* 802.2 header follows */ *framelen = ntohs(eh->ether_type) /* encap'd frame size */ + sizeof(struct ether_header) - sizeof(struct llc); eh->ether_type = llc->llc_un.type_snap.ether_type; ovbcopy(eh, mtod(m, uint8_t *) + sizeof(struct llc), sizeof(struct ether_header)); m_adj(m, sizeof(struct llc)); return m; #undef FF_LLC_SIZE } /* * Install received rate set information in the node's state block. */ int ieee80211_setup_rates(struct ieee80211_node *ni, const uint8_t *rates, const uint8_t *xrates, int flags) { struct ieee80211vap *vap = ni->ni_vap; 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) { uint8_t nxrates; /* * Tack on 11g extended supported rate element. */ nxrates = xrates[1]; if (rs->rs_nrates + nxrates > IEEE80211_RATE_MAXSIZE) { 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, rs, flags); } /* * 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. */ void ieee80211_send_error(struct ieee80211_node *ni, const uint8_t mac[IEEE80211_ADDR_LEN], int subtype, int arg) { struct ieee80211vap *vap = ni->ni_vap; int istmp; if (ni == vap->iv_bss) { if (vap->iv_state != IEEE80211_S_RUN) { /* * XXX hack until we get rid of this routine. * We can be called prior to the vap reaching * run state under certain conditions in which * case iv_bss->ni_chan will not be setup. * Check for this explicitly and and just ignore * the request. */ return; } ni = ieee80211_tmp_node(vap, mac); if (ni == NULL) { /* XXX msg */ return; } istmp = 1; } else istmp = 0; IEEE80211_SEND_MGMT(ni, subtype, arg); if (istmp) ieee80211_free_node(ni); } int ieee80211_alloc_challenge(struct ieee80211_node *ni) { if (ni->ni_challenge == NULL) ni->ni_challenge = (uint32_t *) IEEE80211_MALLOC(IEEE80211_CHALLENGE_LEN, M_80211_NODE, IEEE80211_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); } /* * Parse a Beacon or ProbeResponse frame and return the * useful information in an ieee80211_scanparams structure. * Status is set to 0 if no problems were found; otherwise * a bitmask of IEEE80211_BPARSE_* items is returned that * describes the problems detected. */ int ieee80211_parse_beacon(struct ieee80211_node *ni, struct mbuf *m, struct ieee80211_channel *rxchan, struct ieee80211_scanparams *scan) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_frame *wh; uint8_t *frm, *efrm; wh = mtod(m, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; efrm = mtod(m, uint8_t *) + m->m_len; scan->status = 0; /* * beacon/probe response frame format * * XXX Update from 802.11-2012 - eg where HT is * [8] time stamp * [2] beacon interval * [2] capability information * [tlv] ssid * [tlv] supported rates * [tlv] country information * [tlv] channel switch announcement (CSA) * [tlv] parameter set (FH/DS) * [tlv] erp information * [tlv] extended supported rates * [tlv] WME * [tlv] WPA or RSN * [tlv] HT capabilities * [tlv] HT information * [tlv] VHT capabilities * [tlv] VHT information * [tlv] Atheros capabilities * [tlv] Mesh ID * [tlv] Mesh Configuration */ IEEE80211_VERIFY_LENGTH(efrm - frm, 12, return (scan->status = IEEE80211_BPARSE_BADIELEN)); memset(scan, 0, sizeof(*scan)); scan->tstamp = frm; frm += 8; scan->bintval = le16toh(*(uint16_t *)frm); frm += 2; scan->capinfo = le16toh(*(uint16_t *)frm); frm += 2; scan->bchan = ieee80211_chan2ieee(ic, rxchan); scan->chan = scan->bchan; scan->ies = frm; scan->ies_len = efrm - frm; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return (scan->status = IEEE80211_BPARSE_BADIELEN)); 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_CSA: scan->csa = frm; break; case IEEE80211_ELEMID_QUIET: scan->quiet = frm; break; case IEEE80211_ELEMID_FHPARMS: if (ic->ic_phytype == IEEE80211_T_FH) { scan->fhdwell = le16dec(&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 - mtod(m, uint8_t *); break; case IEEE80211_ELEMID_IBSSPARMS: case IEEE80211_ELEMID_CFPARMS: case IEEE80211_ELEMID_PWRCNSTR: case IEEE80211_ELEMID_BSSLOAD: case IEEE80211_ELEMID_APCHANREP: /* NB: avoid debugging complaints */ 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] | 0x100; break; case IEEE80211_ELEMID_HTCAP: scan->htcap = frm; break; case IEEE80211_ELEMID_VHT_CAP: scan->vhtcap = frm; break; case IEEE80211_ELEMID_VHT_OPMODE: scan->vhtopmode = frm; break; case IEEE80211_ELEMID_RSN: scan->rsn = frm; break; case IEEE80211_ELEMID_HTINFO: scan->htinfo = frm; break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_ELEMID_MESHID: scan->meshid = frm; break; case IEEE80211_ELEMID_MESHCONF: scan->meshconf = frm; break; #endif /* Extended capabilities; nothing handles it for now */ case IEEE80211_ELEMID_EXTCAP: break; case IEEE80211_ELEMID_VENDOR: if (iswpaoui(frm)) scan->wpa = frm; else if (iswmeparam(frm) || iswmeinfo(frm)) scan->wme = frm; #ifdef IEEE80211_SUPPORT_SUPERG else if (isatherosoui(frm)) scan->ath = frm; #endif #ifdef IEEE80211_SUPPORT_TDMA else if (istdmaoui(frm)) scan->tdma = frm; #endif else if (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) { /* * Accept pre-draft HT ie's if the * standard ones have not been seen. */ if (ishtcapoui(frm)) { if (scan->htcap == NULL) scan->htcap = frm; } else if (ishtinfooui(frm)) { if (scan->htinfo == NULL) scan->htcap = 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; } IEEE80211_VERIFY_ELEMENT(scan->rates, IEEE80211_RATE_MAXSIZE, scan->status |= IEEE80211_BPARSE_RATES_INVALID); if (scan->rates != NULL && scan->xrates != NULL) { /* * NB: don't process XRATES if RATES is missing. This * avoids a potential null ptr deref and should be ok * as the return code will already note RATES is missing * (so callers shouldn't otherwise process the frame). */ IEEE80211_VERIFY_ELEMENT(scan->xrates, IEEE80211_RATE_MAXSIZE - scan->rates[1], scan->status |= IEEE80211_BPARSE_XRATES_INVALID); } IEEE80211_VERIFY_ELEMENT(scan->ssid, IEEE80211_NWID_LEN, scan->status |= IEEE80211_BPARSE_SSID_INVALID); 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 | IEEE80211_MSG_INPUT, wh, NULL, "for off-channel %u (bchan=%u)", scan->chan, scan->bchan); vap->iv_stats.is_rx_chanmismatch++; scan->status |= IEEE80211_BPARSE_OFFCHAN; } if (!(IEEE80211_BINTVAL_MIN <= scan->bintval && scan->bintval <= IEEE80211_BINTVAL_MAX)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, wh, NULL, "bogus beacon interval (%d TU)", (int) scan->bintval); vap->iv_stats.is_rx_badbintval++; scan->status |= IEEE80211_BPARSE_BINTVAL_INVALID; } if (scan->country != NULL) { /* * Validate we have at least enough data to extract * the country code. Not sure if we should return an * error instead of discarding the IE; consider this * being lenient as we don't depend on the data for * correct operation. */ IEEE80211_VERIFY_LENGTH(scan->country[1], 3 * sizeof(uint8_t), scan->country = NULL); } if (scan->csa != NULL) { /* * Validate Channel Switch Announcement; this must * be the correct length or we toss the frame. */ IEEE80211_VERIFY_LENGTH(scan->csa[1], 3 * sizeof(uint8_t), scan->status |= IEEE80211_BPARSE_CSA_INVALID); } #ifdef IEEE80211_SUPPORT_MESH if (scan->meshid != NULL) { IEEE80211_VERIFY_ELEMENT(scan->meshid, IEEE80211_MESHID_LEN, scan->status |= IEEE80211_BPARSE_MESHID_INVALID); } #endif /* * Process HT ie's. This is complicated by our * accepting both the standard ie's and the pre-draft * vendor OUI ie's that some vendors still use/require. */ if (scan->htcap != NULL) { IEEE80211_VERIFY_LENGTH(scan->htcap[1], scan->htcap[0] == IEEE80211_ELEMID_VENDOR ? 4 + sizeof(struct ieee80211_ie_htcap)-2 : sizeof(struct ieee80211_ie_htcap)-2, scan->htcap = NULL); } if (scan->htinfo != NULL) { IEEE80211_VERIFY_LENGTH(scan->htinfo[1], scan->htinfo[0] == IEEE80211_ELEMID_VENDOR ? 4 + sizeof(struct ieee80211_ie_htinfo)-2 : sizeof(struct ieee80211_ie_htinfo)-2, scan->htinfo = NULL); } /* Process VHT IEs */ if (scan->vhtcap != NULL) { IEEE80211_VERIFY_LENGTH(scan->vhtcap[1], sizeof(struct ieee80211_vht_cap), scan->vhtcap = NULL); } if (scan->vhtopmode != NULL) { IEEE80211_VERIFY_LENGTH(scan->vhtopmode[1], sizeof(struct ieee80211_vht_operation), scan->vhtopmode = NULL); } return scan->status; } /* * Parse an Action frame. Return 0 on success, non-zero on failure. */ int ieee80211_parse_action(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_action *ia; struct ieee80211_frame *wh; uint8_t *frm, *efrm; /* * action frame format: * [1] category * [1] action * [tlv] parameters */ wh = mtod(m, struct ieee80211_frame *); frm = (u_int8_t *)&wh[1]; efrm = mtod(m, u_int8_t *) + m->m_len; IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action), return EINVAL); ia = (const struct ieee80211_action *) frm; vap->iv_stats.is_rx_action++; IEEE80211_NODE_STAT(ni, rx_action); /* verify frame payloads but defer processing */ switch (ia->ia_category) { case IEEE80211_ACTION_CAT_BA: switch (ia->ia_action) { case IEEE80211_ACTION_BA_ADDBA_REQUEST: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ba_addbarequest), return EINVAL); break; case IEEE80211_ACTION_BA_ADDBA_RESPONSE: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ba_addbaresponse), return EINVAL); break; case IEEE80211_ACTION_BA_DELBA: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ba_delba), return EINVAL); break; } break; case IEEE80211_ACTION_CAT_HT: switch (ia->ia_action) { case IEEE80211_ACTION_HT_TXCHWIDTH: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ht_txchwidth), return EINVAL); break; case IEEE80211_ACTION_HT_MIMOPWRSAVE: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ht_mimopowersave), return EINVAL); break; } break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_ACTION_CAT_MESH: switch (ia->ia_action) { case IEEE80211_ACTION_MESH_LMETRIC: /* * XXX: verification is true only if we are using * Airtime link metric (default) */ IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_meshlmetric_ie), return EINVAL); break; case IEEE80211_ACTION_MESH_HWMP: /* verify something */ break; case IEEE80211_ACTION_MESH_GANN: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_meshgann_ie), return EINVAL); break; case IEEE80211_ACTION_MESH_CC: case IEEE80211_ACTION_MESH_MCCA_SREQ: case IEEE80211_ACTION_MESH_MCCA_SREP: case IEEE80211_ACTION_MESH_MCCA_AREQ: case IEEE80211_ACTION_MESH_MCCA_ADVER: case IEEE80211_ACTION_MESH_MCCA_TRDOWN: case IEEE80211_ACTION_MESH_TBTT_REQ: case IEEE80211_ACTION_MESH_TBTT_RES: /* reject these early on, not implemented */ IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, wh, NULL, "not implemented yet, act=0x%02X", ia->ia_action); return EINVAL; } break; case IEEE80211_ACTION_CAT_SELF_PROT: /* If TA or RA group address discard silently */ if (IEEE80211_IS_MULTICAST(wh->i_addr1) || IEEE80211_IS_MULTICAST(wh->i_addr2)) return EINVAL; /* * XXX: Should we verify complete length now or it is * to varying in sizes? */ switch (ia->ia_action) { case IEEE80211_ACTION_MESHPEERING_CONFIRM: case IEEE80211_ACTION_MESHPEERING_CLOSE: /* is not a peering candidate (yet) */ if (ni == vap->iv_bss) return EINVAL; break; } break; #endif case IEEE80211_ACTION_CAT_VHT: - printf("%s: TODO: VHT handling!\n", __func__); + net80211_printf("%s: TODO: VHT handling!\n", __func__); break; } return 0; } #ifdef IEEE80211_DEBUG /* * Debugging support. */ void ieee80211_ssid_mismatch(struct ieee80211vap *vap, const char *tag, uint8_t mac[IEEE80211_ADDR_LEN], uint8_t *ssid) { - printf("[%s] discard %s frame, ssid mismatch: ", + net80211_printf("[%s] discard %s frame, ssid mismatch: ", ether_sprintf(mac), tag); ieee80211_print_essid(ssid + 2, ssid[1]); - printf("\n"); + net80211_printf("\n"); } /* * Return the bssid of a frame. */ static const uint8_t * ieee80211_getbssid(const 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; } #include void ieee80211_note(const struct ieee80211vap *vap, const char *fmt, ...) { char buf[256]; /* XXX */ va_list ap; int len; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); net80211_vap_printf(vap, "%s", buf); /* NB: no \n */ if (len >= sizeof(buf)) net80211_vap_printf(vap, "%s: XXX buffer too small: len = %d\n", __func__, len); } void ieee80211_note_frame(const struct ieee80211vap *vap, const struct ieee80211_frame *wh, const char *fmt, ...) { char buf[256]; /* XXX */ va_list ap; int len; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); net80211_vap_printf(vap, "[%s] %s\n", ether_sprintf(ieee80211_getbssid(vap, wh)), buf); if (len >= sizeof(buf)) - printf("%s: XXX buffer too small: len = %d\n", __func__, len); + net80211_printf("%s: XXX buffer too small: len = %d\n", + __func__, len); } void ieee80211_note_mac(const struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN], const char *fmt, ...) { char buf[256]; /* XXX */ va_list ap; int len; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); net80211_vap_printf(vap, "[%s] %s\n", ether_sprintf(mac), buf); if (len >= sizeof(buf)) - printf("%s: XXX buffer too small: len = %d\n", __func__, len); + net80211_printf("%s: XXX buffer too small: len = %d\n", + __func__, len); } void ieee80211_discard_frame(const struct ieee80211vap *vap, const struct ieee80211_frame *wh, const char *type, const char *fmt, ...) { char buf[256]; /* XXX */ va_list ap; int len; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); net80211_vap_printf(vap, "[%s] discard %s frame, %s\n", ether_sprintf(ieee80211_getbssid(vap, wh)), type != NULL ? type : ieee80211_mgt_subtype_name(wh->i_fc[0]), buf); if (len >= sizeof(buf)) - printf("%s: XXX buffer too small: len = %d\n", __func__, len); + net80211_printf("%s: XXX buffer too small: len = %d\n", + __func__, len); } void ieee80211_discard_ie(const struct ieee80211vap *vap, const struct ieee80211_frame *wh, const char *type, const char *fmt, ...) { char buf[256]; /* XXX */ va_list ap; int len; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); net80211_vap_printf(vap, "[%s] discard%s%s information element, %s\n", ether_sprintf(ieee80211_getbssid(vap, wh)), type != NULL ? " " : "", type != NULL ? type : "", buf); if (len >= sizeof(buf)) - printf("%s: XXX buffer too small: len = %d\n", __func__, len); + net80211_printf("%s: XXX buffer too small: len = %d\n", + __func__, len); } void ieee80211_discard_mac(const struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN], const char *type, const char *fmt, ...) { char buf[256]; /* XXX */ va_list ap; int len; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); net80211_vap_printf(vap, "[%s] discard%s%s frame, %s\n", ether_sprintf(mac), type != NULL ? " " : "", type != NULL ? type : "", buf); if (len >= sizeof(buf)) - printf("%s: XXX buffer too small: len = %d\n", __func__, len); + net80211_printf("%s: XXX buffer too small: len = %d\n", + __func__, len); } #endif /* IEEE80211_DEBUG */ diff --git a/sys/net80211/ieee80211_ioctl.c b/sys/net80211/ieee80211_ioctl.c index 85d46c018d36..4c3e5fa94243 100644 --- a/sys/net80211/ieee80211_ioctl.c +++ b/sys/net80211/ieee80211_ioctl.c @@ -1,3732 +1,3732 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 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 /* * IEEE 802.11 ioctl support (FreeBSD-specific) */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #endif #include #include #include #include #define IS_UP_AUTO(_vap) \ (IFNET_IS_UP_RUNNING((_vap)->iv_ifp) && \ (_vap)->iv_roaming == IEEE80211_ROAMING_AUTO) static const uint8_t zerobssid[IEEE80211_ADDR_LEN]; static struct ieee80211_channel *findchannel(struct ieee80211com *, int ieee, int mode); static int ieee80211_scanreq(struct ieee80211vap *, struct ieee80211_scan_req *); static int ieee80211_ioctl_getkey(u_long cmd, struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; struct ieee80211req_key ik; struct ieee80211_key *wk; const struct ieee80211_cipher *cip; u_int kid; int error; if (ireq->i_len != sizeof(ik)) return EINVAL; error = copyin(ireq->i_data, &ik, sizeof(ik)); if (error) return error; kid = ik.ik_keyix; if (kid == IEEE80211_KEYIX_NONE) { ni = ieee80211_find_vap_node(&ic->ic_sta, vap, ik.ik_macaddr); if (ni == NULL) return ENOENT; wk = &ni->ni_ucastkey; } else { if (kid >= IEEE80211_WEP_NKID) return EINVAL; wk = &vap->iv_nw_keys[kid]; IEEE80211_ADDR_COPY(&ik.ik_macaddr, vap->iv_bss->ni_macaddr); ni = NULL; } cip = wk->wk_cipher; ik.ik_type = cip->ic_cipher; ik.ik_keylen = wk->wk_keylen; ik.ik_flags = wk->wk_flags & (IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV); if (wk->wk_keyix == vap->iv_def_txkey) ik.ik_flags |= IEEE80211_KEY_DEFAULT; if (ieee80211_priv_check_vap_getkey(cmd, vap, NULL) == 0) { /* NB: only root can read key data */ ik.ik_keyrsc = wk->wk_keyrsc[IEEE80211_NONQOS_TID]; ik.ik_keytsc = wk->wk_keytsc; memcpy(ik.ik_keydata, wk->wk_key, wk->wk_keylen); if (cip->ic_cipher == IEEE80211_CIPHER_TKIP) { memcpy(ik.ik_keydata+wk->wk_keylen, wk->wk_key + IEEE80211_KEYBUF_SIZE, IEEE80211_MICBUF_SIZE); ik.ik_keylen += IEEE80211_MICBUF_SIZE; } } else { ik.ik_keyrsc = 0; ik.ik_keytsc = 0; memset(ik.ik_keydata, 0, sizeof(ik.ik_keydata)); } if (ni != NULL) ieee80211_free_node(ni); return copyout(&ik, ireq->i_data, sizeof(ik)); } static int ieee80211_ioctl_getchanlist(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; if (sizeof(ic->ic_chan_active) < ireq->i_len) ireq->i_len = sizeof(ic->ic_chan_active); return copyout(&ic->ic_chan_active, ireq->i_data, ireq->i_len); } static int ieee80211_ioctl_getchaninfo(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; uint32_t space; space = __offsetof(struct ieee80211req_chaninfo, ic_chans[ic->ic_nchans]); if (space > ireq->i_len) space = ireq->i_len; /* XXX assumes compatible layout */ return copyout(&ic->ic_nchans, ireq->i_data, space); } static int ieee80211_ioctl_getwpaie(struct ieee80211vap *vap, struct ieee80211req *ireq, int req) { struct ieee80211_node *ni; struct ieee80211req_wpaie2 *wpaie; int error; if (ireq->i_len < IEEE80211_ADDR_LEN) return EINVAL; wpaie = IEEE80211_MALLOC(sizeof(*wpaie), M_TEMP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (wpaie == NULL) return ENOMEM; error = copyin(ireq->i_data, wpaie->wpa_macaddr, IEEE80211_ADDR_LEN); if (error != 0) goto bad; ni = ieee80211_find_vap_node(&vap->iv_ic->ic_sta, vap, wpaie->wpa_macaddr); if (ni == NULL) { error = ENOENT; goto bad; } if (ni->ni_ies.wpa_ie != NULL) { int ielen = ni->ni_ies.wpa_ie[1] + 2; if (ielen > sizeof(wpaie->wpa_ie)) ielen = sizeof(wpaie->wpa_ie); memcpy(wpaie->wpa_ie, ni->ni_ies.wpa_ie, ielen); } if (req == IEEE80211_IOC_WPAIE2) { if (ni->ni_ies.rsn_ie != NULL) { int ielen = ni->ni_ies.rsn_ie[1] + 2; if (ielen > sizeof(wpaie->rsn_ie)) ielen = sizeof(wpaie->rsn_ie); memcpy(wpaie->rsn_ie, ni->ni_ies.rsn_ie, ielen); } if (ireq->i_len > sizeof(struct ieee80211req_wpaie2)) ireq->i_len = sizeof(struct ieee80211req_wpaie2); } else { /* compatibility op, may overwrite wpa ie */ /* XXX check ic_flags? */ if (ni->ni_ies.rsn_ie != NULL) { int ielen = ni->ni_ies.rsn_ie[1] + 2; if (ielen > sizeof(wpaie->wpa_ie)) ielen = sizeof(wpaie->wpa_ie); memcpy(wpaie->wpa_ie, ni->ni_ies.rsn_ie, ielen); } if (ireq->i_len > sizeof(struct ieee80211req_wpaie)) ireq->i_len = sizeof(struct ieee80211req_wpaie); } ieee80211_free_node(ni); error = copyout(wpaie, ireq->i_data, ireq->i_len); bad: IEEE80211_FREE(wpaie, M_TEMP); return error; } static int ieee80211_ioctl_getstastats(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211_node *ni; uint8_t macaddr[IEEE80211_ADDR_LEN]; const size_t off = __offsetof(struct ieee80211req_sta_stats, is_stats); int error; if (ireq->i_len < off) return EINVAL; error = copyin(ireq->i_data, macaddr, IEEE80211_ADDR_LEN); if (error != 0) return error; ni = ieee80211_find_vap_node(&vap->iv_ic->ic_sta, vap, macaddr); if (ni == NULL) return ENOENT; if (ireq->i_len > sizeof(struct ieee80211req_sta_stats)) ireq->i_len = sizeof(struct ieee80211req_sta_stats); /* NB: copy out only the statistics */ error = copyout(&ni->ni_stats, (uint8_t *) ireq->i_data + off, ireq->i_len - off); ieee80211_free_node(ni); return error; } struct scanreq { struct ieee80211req_scan_result *sr; size_t space; }; static size_t scan_space(const struct ieee80211_scan_entry *se, int *ielen) { size_t len; *ielen = se->se_ies.len; /* * NB: ie's can be no more than 255 bytes and the max 802.11 * packet is <3Kbytes so we are sure this doesn't overflow * 16-bits; if this is a concern we can drop the ie's. */ len = sizeof(struct ieee80211req_scan_result) + se->se_ssid[1] + se->se_meshid[1] + *ielen; return roundup(len, sizeof(uint32_t)); } static void get_scan_space(void *arg, const struct ieee80211_scan_entry *se) { struct scanreq *req = arg; int ielen; req->space += scan_space(se, &ielen); } static void get_scan_result(void *arg, const struct ieee80211_scan_entry *se) { struct scanreq *req = arg; struct ieee80211req_scan_result *sr; int ielen, len, nr, nxr; uint8_t *cp; len = scan_space(se, &ielen); if (len > req->space) return; sr = req->sr; KASSERT(len <= 65535 && ielen <= 65535, ("len %u ssid %u ie %u", len, se->se_ssid[1], ielen)); sr->isr_len = len; sr->isr_ie_off = sizeof(struct ieee80211req_scan_result); sr->isr_ie_len = ielen; sr->isr_freq = se->se_chan->ic_freq; sr->isr_flags = se->se_chan->ic_flags; sr->isr_rssi = se->se_rssi; sr->isr_noise = se->se_noise; sr->isr_intval = se->se_intval; sr->isr_capinfo = se->se_capinfo; sr->isr_erp = se->se_erp; IEEE80211_ADDR_COPY(sr->isr_bssid, se->se_bssid); nr = min(se->se_rates[1], IEEE80211_RATE_MAXSIZE); memcpy(sr->isr_rates, se->se_rates+2, nr); nxr = min(se->se_xrates[1], IEEE80211_RATE_MAXSIZE - nr); memcpy(sr->isr_rates+nr, se->se_xrates+2, nxr); sr->isr_nrates = nr + nxr; /* copy SSID */ sr->isr_ssid_len = se->se_ssid[1]; cp = ((uint8_t *)sr) + sr->isr_ie_off; memcpy(cp, se->se_ssid+2, sr->isr_ssid_len); /* copy mesh id */ cp += sr->isr_ssid_len; sr->isr_meshid_len = se->se_meshid[1]; memcpy(cp, se->se_meshid+2, sr->isr_meshid_len); cp += sr->isr_meshid_len; if (ielen) memcpy(cp, se->se_ies.data, ielen); req->space -= len; req->sr = (struct ieee80211req_scan_result *)(((uint8_t *)sr) + len); } static int ieee80211_ioctl_getscanresults(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct scanreq req; int error; if (ireq->i_len < sizeof(struct scanreq)) return EFAULT; error = 0; req.space = 0; ieee80211_scan_iterate(vap, get_scan_space, &req); if (req.space > ireq->i_len) req.space = ireq->i_len; if (req.space > 0) { uint32_t space; void *p; space = req.space; /* XXX IEEE80211_M_WAITOK after driver lock released */ p = IEEE80211_MALLOC(space, M_TEMP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (p == NULL) return ENOMEM; req.sr = p; ieee80211_scan_iterate(vap, get_scan_result, &req); ireq->i_len = space - req.space; error = copyout(p, ireq->i_data, ireq->i_len); IEEE80211_FREE(p, M_TEMP); } else ireq->i_len = 0; return error; } struct stainforeq { struct ieee80211req_sta_info *si; size_t space; }; static size_t sta_space(const struct ieee80211_node *ni, size_t *ielen) { *ielen = ni->ni_ies.len; return roundup(sizeof(struct ieee80211req_sta_info) + *ielen, sizeof(uint32_t)); } static void get_sta_space(void *arg, struct ieee80211_node *ni) { struct stainforeq *req = arg; size_t ielen; if (ni->ni_vap->iv_opmode == IEEE80211_M_HOSTAP && ni->ni_associd == 0) /* only associated stations */ return; req->space += sta_space(ni, &ielen); } static void get_sta_info(void *arg, struct ieee80211_node *ni) { struct stainforeq *req = arg; struct ieee80211_node_txrate tr; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211req_sta_info *si; size_t ielen, len; uint8_t *cp; if (vap->iv_opmode == IEEE80211_M_HOSTAP && ni->ni_associd == 0) /* only associated stations */ return; if (ni->ni_chan == IEEE80211_CHAN_ANYC) /* XXX bogus entry */ return; len = sta_space(ni, &ielen); if (len > req->space) return; si = req->si; si->isi_len = len; si->isi_ie_off = sizeof(struct ieee80211req_sta_info); si->isi_ie_len = ielen; si->isi_freq = ni->ni_chan->ic_freq; si->isi_flags = ni->ni_chan->ic_flags; si->isi_state = ni->ni_flags; si->isi_authmode = ni->ni_authmode; vap->iv_ic->ic_node_getsignal(ni, &si->isi_rssi, &si->isi_noise); vap->iv_ic->ic_node_getmimoinfo(ni, &si->isi_mimo); si->isi_capinfo = ni->ni_capinfo; si->isi_erp = ni->ni_erp; IEEE80211_ADDR_COPY(si->isi_macaddr, ni->ni_macaddr); si->isi_nrates = ni->ni_rates.rs_nrates; if (si->isi_nrates > 15) si->isi_nrates = 15; memcpy(si->isi_rates, ni->ni_rates.rs_rates, si->isi_nrates); /* * isi_txrate can only represent the legacy/HT rates. * Only set it if the rate is a legacy/HT rate. * * TODO: For VHT and later rates the API will need changing. */ ieee80211_node_get_txrate(ni, &tr); if ((tr.type == IEEE80211_NODE_TXRATE_LEGACY) || (tr.type == IEEE80211_NODE_TXRATE_HT)) si->isi_txrate = ieee80211_node_get_txrate_dot11rate(ni); /* Note: txmbps is in 1/2Mbit/s units */ si->isi_txmbps = ieee80211_node_get_txrate_kbit(ni) / 500; si->isi_associd = ni->ni_associd; si->isi_txpower = ni->ni_txpower; si->isi_vlan = ni->ni_vlan; if (ni->ni_flags & IEEE80211_NODE_QOS) { memcpy(si->isi_txseqs, ni->ni_txseqs, sizeof(ni->ni_txseqs)); memcpy(si->isi_rxseqs, ni->ni_rxseqs, sizeof(ni->ni_rxseqs)); } else { si->isi_txseqs[0] = ni->ni_txseqs[IEEE80211_NONQOS_TID]; si->isi_rxseqs[0] = ni->ni_rxseqs[IEEE80211_NONQOS_TID]; } /* NB: leave all cases in case we relax ni_associd == 0 check */ if (ieee80211_node_is_authorized(ni)) si->isi_inact = vap->iv_inact_run; else if (ni->ni_associd != 0 || (vap->iv_opmode == IEEE80211_M_WDS && (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY))) si->isi_inact = vap->iv_inact_auth; else si->isi_inact = vap->iv_inact_init; si->isi_inact = (si->isi_inact - ni->ni_inact) * IEEE80211_INACT_WAIT; si->isi_localid = ni->ni_mllid; si->isi_peerid = ni->ni_mlpid; si->isi_peerstate = ni->ni_mlstate; if (ielen) { cp = ((uint8_t *)si) + si->isi_ie_off; memcpy(cp, ni->ni_ies.data, ielen); } req->si = (struct ieee80211req_sta_info *)(((uint8_t *)si) + len); req->space -= len; } static int getstainfo_common(struct ieee80211vap *vap, struct ieee80211req *ireq, struct ieee80211_node *ni, size_t off) { struct ieee80211com *ic = vap->iv_ic; struct stainforeq req; size_t space; void *p; int error; error = 0; req.space = 0; if (ni == NULL) { ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, get_sta_space, &req); } else get_sta_space(&req, ni); if (req.space > ireq->i_len) req.space = ireq->i_len; if (req.space > 0) { space = req.space; /* XXX IEEE80211_M_WAITOK after driver lock released */ p = IEEE80211_MALLOC(space, M_TEMP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (p == NULL) { error = ENOMEM; goto bad; } req.si = p; if (ni == NULL) { ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, get_sta_info, &req); } else get_sta_info(&req, ni); ireq->i_len = space - req.space; error = copyout(p, (uint8_t *) ireq->i_data+off, ireq->i_len); IEEE80211_FREE(p, M_TEMP); } else ireq->i_len = 0; bad: if (ni != NULL) ieee80211_free_node(ni); return error; } static int ieee80211_ioctl_getstainfo(struct ieee80211vap *vap, struct ieee80211req *ireq) { uint8_t macaddr[IEEE80211_ADDR_LEN]; const size_t off = __offsetof(struct ieee80211req_sta_req, info); struct ieee80211_node *ni; int error; if (ireq->i_len < sizeof(struct ieee80211req_sta_req)) return EFAULT; error = copyin(ireq->i_data, macaddr, IEEE80211_ADDR_LEN); if (error != 0) return error; if (IEEE80211_ADDR_EQ(macaddr, ieee80211_vap_get_broadcast_address(vap))) { ni = NULL; } else { ni = ieee80211_find_vap_node(&vap->iv_ic->ic_sta, vap, macaddr); if (ni == NULL) return ENOENT; } return getstainfo_common(vap, ireq, ni, off); } static int ieee80211_ioctl_getstatxpow(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211_node *ni; struct ieee80211req_sta_txpow txpow; int error; if (ireq->i_len != sizeof(txpow)) return EINVAL; error = copyin(ireq->i_data, &txpow, sizeof(txpow)); if (error != 0) return error; ni = ieee80211_find_vap_node(&vap->iv_ic->ic_sta, vap, txpow.it_macaddr); if (ni == NULL) return ENOENT; txpow.it_txpow = ni->ni_txpower; error = copyout(&txpow, ireq->i_data, sizeof(txpow)); ieee80211_free_node(ni); return error; } static int ieee80211_ioctl_getwmeparam(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_wme_state *wme = &ic->ic_wme; struct wmeParams *wmep; int ac; if ((ic->ic_caps & IEEE80211_C_WME) == 0) return EINVAL; ac = (ireq->i_len & IEEE80211_WMEPARAM_VAL); if (ac >= WME_NUM_AC) ac = WME_AC_BE; if (ireq->i_len & IEEE80211_WMEPARAM_BSS) wmep = &wme->wme_wmeBssChanParams.cap_wmeParams[ac]; else wmep = &wme->wme_wmeChanParams.cap_wmeParams[ac]; switch (ireq->i_type) { case IEEE80211_IOC_WME_CWMIN: /* WME: CWmin */ ireq->i_val = wmep->wmep_logcwmin; break; case IEEE80211_IOC_WME_CWMAX: /* WME: CWmax */ ireq->i_val = wmep->wmep_logcwmax; break; case IEEE80211_IOC_WME_AIFS: /* WME: AIFS */ ireq->i_val = wmep->wmep_aifsn; break; case IEEE80211_IOC_WME_TXOPLIMIT: /* WME: txops limit */ ireq->i_val = wmep->wmep_txopLimit; break; case IEEE80211_IOC_WME_ACM: /* WME: ACM (bss only) */ wmep = &wme->wme_wmeBssChanParams.cap_wmeParams[ac]; ireq->i_val = wmep->wmep_acm; break; case IEEE80211_IOC_WME_ACKPOLICY: /* WME: ACK policy (!bss only)*/ wmep = &wme->wme_wmeChanParams.cap_wmeParams[ac]; ireq->i_val = !wmep->wmep_noackPolicy; break; } return 0; } static int ieee80211_ioctl_getmaccmd(struct ieee80211vap *vap, struct ieee80211req *ireq) { const struct ieee80211_aclator *acl = vap->iv_acl; return (acl == NULL ? EINVAL : acl->iac_getioctl(vap, ireq)); } static int ieee80211_ioctl_getcurchan(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel *c; if (ireq->i_len != sizeof(struct ieee80211_channel)) return EINVAL; /* * vap's may have different operating channels when HT is * in use. When in RUN state report the vap-specific channel. * Otherwise return curchan. */ if (vap->iv_state == IEEE80211_S_RUN || vap->iv_state == IEEE80211_S_SLEEP) c = vap->iv_bss->ni_chan; else c = ic->ic_curchan; return copyout(c, ireq->i_data, sizeof(*c)); } static int getappie(const struct ieee80211_appie *aie, struct ieee80211req *ireq) { if (aie == NULL) return EINVAL; /* NB: truncate, caller can check length */ if (ireq->i_len > aie->ie_len) ireq->i_len = aie->ie_len; return copyout(aie->ie_data, ireq->i_data, ireq->i_len); } static int ieee80211_ioctl_getappie(struct ieee80211vap *vap, struct ieee80211req *ireq) { uint8_t fc0; fc0 = ireq->i_val & 0xff; if ((fc0 & IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_MGT) return EINVAL; /* NB: could check iv_opmode and reject but hardly worth the effort */ switch (fc0 & IEEE80211_FC0_SUBTYPE_MASK) { case IEEE80211_FC0_SUBTYPE_BEACON: return getappie(vap->iv_appie_beacon, ireq); case IEEE80211_FC0_SUBTYPE_PROBE_RESP: return getappie(vap->iv_appie_proberesp, ireq); case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: return getappie(vap->iv_appie_assocresp, ireq); case IEEE80211_FC0_SUBTYPE_PROBE_REQ: return getappie(vap->iv_appie_probereq, ireq); case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: return getappie(vap->iv_appie_assocreq, ireq); case IEEE80211_FC0_SUBTYPE_BEACON|IEEE80211_FC0_SUBTYPE_PROBE_RESP: return getappie(vap->iv_appie_wpa, ireq); } return EINVAL; } static int ieee80211_ioctl_getregdomain(struct ieee80211vap *vap, const struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; if (ireq->i_len != sizeof(ic->ic_regdomain)) return EINVAL; return copyout(&ic->ic_regdomain, ireq->i_data, sizeof(ic->ic_regdomain)); } static int ieee80211_ioctl_getroam(struct ieee80211vap *vap, const struct ieee80211req *ireq) { size_t len = ireq->i_len; /* NB: accept short requests for backwards compat */ if (len > sizeof(vap->iv_roamparms)) len = sizeof(vap->iv_roamparms); return copyout(vap->iv_roamparms, ireq->i_data, len); } static int ieee80211_ioctl_gettxparams(struct ieee80211vap *vap, const struct ieee80211req *ireq) { size_t len = ireq->i_len; /* NB: accept short requests for backwards compat */ if (len > sizeof(vap->iv_txparms)) len = sizeof(vap->iv_txparms); return copyout(vap->iv_txparms, ireq->i_data, len); } static int ieee80211_ioctl_getdevcaps(struct ieee80211com *ic, const struct ieee80211req *ireq) { struct ieee80211_devcaps_req *dc; struct ieee80211req_chaninfo *ci; int maxchans, error; maxchans = 1 + ((ireq->i_len - sizeof(struct ieee80211_devcaps_req)) / sizeof(struct ieee80211_channel)); /* NB: require 1 so we know ic_nchans is accessible */ if (maxchans < 1) return EINVAL; /* constrain max request size, 2K channels is ~24Kbytes */ if (maxchans > 2048) maxchans = 2048; dc = (struct ieee80211_devcaps_req *) IEEE80211_MALLOC(IEEE80211_DEVCAPS_SIZE(maxchans), M_TEMP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (dc == NULL) return ENOMEM; dc->dc_drivercaps = ic->ic_caps; /* * Announce the set of both hardware and software supported * ciphers. */ dc->dc_cryptocaps = ic->ic_cryptocaps | ic->ic_sw_cryptocaps; dc->dc_htcaps = ic->ic_htcaps; dc->dc_vhtcaps = ic->ic_vht_cap.vht_cap_info; ci = &dc->dc_chaninfo; ic->ic_getradiocaps(ic, maxchans, &ci->ic_nchans, ci->ic_chans); KASSERT(ci->ic_nchans <= maxchans, ("nchans %d maxchans %d", ci->ic_nchans, maxchans)); ieee80211_sort_channels(ci->ic_chans, ci->ic_nchans); error = copyout(dc, ireq->i_data, IEEE80211_DEVCAPS_SPACE(dc)); IEEE80211_FREE(dc, M_TEMP); return error; } static int ieee80211_ioctl_getstavlan(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211_node *ni; struct ieee80211req_sta_vlan vlan; int error; if (ireq->i_len != sizeof(vlan)) return EINVAL; error = copyin(ireq->i_data, &vlan, sizeof(vlan)); if (error != 0) return error; if (!IEEE80211_ADDR_EQ(vlan.sv_macaddr, zerobssid)) { ni = ieee80211_find_vap_node(&vap->iv_ic->ic_sta, vap, vlan.sv_macaddr); if (ni == NULL) return ENOENT; } else ni = ieee80211_ref_node(vap->iv_bss); vlan.sv_vlan = ni->ni_vlan; error = copyout(&vlan, ireq->i_data, sizeof(vlan)); ieee80211_free_node(ni); return error; } /* * Dummy ioctl get handler so the linker set is defined. */ static int dummy_ioctl_get(struct ieee80211vap *vap, struct ieee80211req *ireq) { return ENOSYS; } IEEE80211_IOCTL_GET(dummy, dummy_ioctl_get); static int ieee80211_ioctl_getdefault(struct ieee80211vap *vap, struct ieee80211req *ireq) { ieee80211_ioctl_getfunc * const *get; int error; SET_FOREACH(get, ieee80211_ioctl_getset) { error = (*get)(vap, ireq); if (error != ENOSYS) return error; } return EINVAL; } static int ieee80211_ioctl_get80211(struct ieee80211vap *vap, u_long cmd, struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; u_int kid, len; uint8_t tmpkey[IEEE80211_KEYBUF_SIZE]; char tmpssid[IEEE80211_NWID_LEN]; int error = 0; switch (ireq->i_type) { case IEEE80211_IOC_IC_NAME: len = strlen(ic->ic_name) + 1; if (len > ireq->i_len) return (EINVAL); ireq->i_len = len; error = copyout(ic->ic_name, ireq->i_data, ireq->i_len); break; case IEEE80211_IOC_SSID: switch (vap->iv_state) { case IEEE80211_S_INIT: case IEEE80211_S_SCAN: ireq->i_len = vap->iv_des_ssid[0].len; memcpy(tmpssid, vap->iv_des_ssid[0].ssid, ireq->i_len); break; default: ireq->i_len = vap->iv_bss->ni_esslen; memcpy(tmpssid, vap->iv_bss->ni_essid, ireq->i_len); break; } error = copyout(tmpssid, ireq->i_data, ireq->i_len); break; case IEEE80211_IOC_NUMSSIDS: ireq->i_val = 1; break; case IEEE80211_IOC_WEP: if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) ireq->i_val = IEEE80211_WEP_OFF; else if (vap->iv_flags & IEEE80211_F_DROPUNENC) ireq->i_val = IEEE80211_WEP_ON; else ireq->i_val = IEEE80211_WEP_MIXED; break; case IEEE80211_IOC_WEPKEY: kid = (u_int) ireq->i_val; if (kid >= IEEE80211_WEP_NKID) return EINVAL; len = (u_int) vap->iv_nw_keys[kid].wk_keylen; /* NB: only root can read WEP keys */ if (ieee80211_priv_check_vap_getkey(cmd, vap, NULL) == 0) { bcopy(vap->iv_nw_keys[kid].wk_key, tmpkey, len); } else { bzero(tmpkey, len); } ireq->i_len = len; error = copyout(tmpkey, ireq->i_data, len); break; case IEEE80211_IOC_NUMWEPKEYS: ireq->i_val = IEEE80211_WEP_NKID; break; case IEEE80211_IOC_WEPTXKEY: ireq->i_val = vap->iv_def_txkey; break; case IEEE80211_IOC_AUTHMODE: if (vap->iv_flags & IEEE80211_F_WPA) ireq->i_val = IEEE80211_AUTH_WPA; else ireq->i_val = vap->iv_bss->ni_authmode; break; case IEEE80211_IOC_CHANNEL: ireq->i_val = ieee80211_chan2ieee(ic, ic->ic_curchan); break; case IEEE80211_IOC_POWERSAVE: if (vap->iv_flags & IEEE80211_F_PMGTON) ireq->i_val = IEEE80211_POWERSAVE_ON; else ireq->i_val = IEEE80211_POWERSAVE_OFF; break; case IEEE80211_IOC_POWERSAVESLEEP: ireq->i_val = ic->ic_lintval; break; case IEEE80211_IOC_RTSTHRESHOLD: ireq->i_val = vap->iv_rtsthreshold; break; case IEEE80211_IOC_PROTMODE: ireq->i_val = vap->iv_protmode; break; case IEEE80211_IOC_TXPOWER: /* * Tx power limit is the min of max regulatory * power, any user-set limit, and the max the * radio can do. * * TODO: methodize this */ ireq->i_val = 2*ic->ic_curchan->ic_maxregpower; if (ireq->i_val > ic->ic_txpowlimit) ireq->i_val = ic->ic_txpowlimit; if (ireq->i_val > ic->ic_curchan->ic_maxpower) ireq->i_val = ic->ic_curchan->ic_maxpower; break; case IEEE80211_IOC_WPA: switch (vap->iv_flags & IEEE80211_F_WPA) { case IEEE80211_F_WPA1: ireq->i_val = 1; break; case IEEE80211_F_WPA2: ireq->i_val = 2; break; case IEEE80211_F_WPA1 | IEEE80211_F_WPA2: ireq->i_val = 3; break; default: ireq->i_val = 0; break; } break; case IEEE80211_IOC_CHANLIST: error = ieee80211_ioctl_getchanlist(vap, ireq); break; case IEEE80211_IOC_ROAMING: ireq->i_val = vap->iv_roaming; break; case IEEE80211_IOC_PRIVACY: ireq->i_val = (vap->iv_flags & IEEE80211_F_PRIVACY) != 0; break; case IEEE80211_IOC_DROPUNENCRYPTED: ireq->i_val = (vap->iv_flags & IEEE80211_F_DROPUNENC) != 0; break; case IEEE80211_IOC_COUNTERMEASURES: ireq->i_val = (vap->iv_flags & IEEE80211_F_COUNTERM) != 0; break; case IEEE80211_IOC_WME: ireq->i_val = (vap->iv_flags & IEEE80211_F_WME) != 0; break; case IEEE80211_IOC_HIDESSID: ireq->i_val = (vap->iv_flags & IEEE80211_F_HIDESSID) != 0; break; case IEEE80211_IOC_APBRIDGE: ireq->i_val = (vap->iv_flags & IEEE80211_F_NOBRIDGE) == 0; break; case IEEE80211_IOC_WPAKEY: error = ieee80211_ioctl_getkey(cmd, vap, ireq); break; case IEEE80211_IOC_CHANINFO: error = ieee80211_ioctl_getchaninfo(vap, ireq); break; case IEEE80211_IOC_BSSID: if (ireq->i_len != IEEE80211_ADDR_LEN) return EINVAL; if (vap->iv_state == IEEE80211_S_RUN || vap->iv_state == IEEE80211_S_SLEEP) { error = copyout(vap->iv_opmode == IEEE80211_M_WDS ? vap->iv_bss->ni_macaddr : vap->iv_bss->ni_bssid, ireq->i_data, ireq->i_len); } else error = copyout(vap->iv_des_bssid, ireq->i_data, ireq->i_len); break; case IEEE80211_IOC_WPAIE: case IEEE80211_IOC_WPAIE2: error = ieee80211_ioctl_getwpaie(vap, ireq, ireq->i_type); break; case IEEE80211_IOC_SCAN_RESULTS: error = ieee80211_ioctl_getscanresults(vap, ireq); break; case IEEE80211_IOC_STA_STATS: error = ieee80211_ioctl_getstastats(vap, ireq); break; case IEEE80211_IOC_TXPOWMAX: ireq->i_val = vap->iv_bss->ni_txpower; break; case IEEE80211_IOC_STA_TXPOW: error = ieee80211_ioctl_getstatxpow(vap, ireq); break; case IEEE80211_IOC_STA_INFO: error = ieee80211_ioctl_getstainfo(vap, ireq); break; case IEEE80211_IOC_WME_CWMIN: /* WME: CWmin */ case IEEE80211_IOC_WME_CWMAX: /* WME: CWmax */ case IEEE80211_IOC_WME_AIFS: /* WME: AIFS */ case IEEE80211_IOC_WME_TXOPLIMIT: /* WME: txops limit */ case IEEE80211_IOC_WME_ACM: /* WME: ACM (bss only) */ case IEEE80211_IOC_WME_ACKPOLICY: /* WME: ACK policy (!bss only) */ error = ieee80211_ioctl_getwmeparam(vap, ireq); break; case IEEE80211_IOC_DTIM_PERIOD: ireq->i_val = vap->iv_dtim_period; break; case IEEE80211_IOC_BEACON_INTERVAL: /* NB: get from ic_bss for station mode */ ireq->i_val = vap->iv_bss->ni_intval; break; case IEEE80211_IOC_PUREG: ireq->i_val = (vap->iv_flags & IEEE80211_F_PUREG) != 0; break; case IEEE80211_IOC_QUIET: ireq->i_val = vap->iv_quiet; break; case IEEE80211_IOC_QUIET_COUNT: ireq->i_val = vap->iv_quiet_count; break; case IEEE80211_IOC_QUIET_PERIOD: ireq->i_val = vap->iv_quiet_period; break; case IEEE80211_IOC_QUIET_DUR: ireq->i_val = vap->iv_quiet_duration; break; case IEEE80211_IOC_QUIET_OFFSET: ireq->i_val = vap->iv_quiet_offset; break; case IEEE80211_IOC_BGSCAN: ireq->i_val = (vap->iv_flags & IEEE80211_F_BGSCAN) != 0; break; case IEEE80211_IOC_BGSCAN_IDLE: ireq->i_val = vap->iv_bgscanidle*hz/1000; /* ms */ break; case IEEE80211_IOC_BGSCAN_INTERVAL: ireq->i_val = vap->iv_bgscanintvl/hz; /* seconds */ break; case IEEE80211_IOC_SCANVALID: ireq->i_val = vap->iv_scanvalid/hz; /* seconds */ break; case IEEE80211_IOC_FRAGTHRESHOLD: ireq->i_val = vap->iv_fragthreshold; break; case IEEE80211_IOC_MACCMD: error = ieee80211_ioctl_getmaccmd(vap, ireq); break; case IEEE80211_IOC_BURST: ireq->i_val = (vap->iv_flags & IEEE80211_F_BURST) != 0; break; case IEEE80211_IOC_BMISSTHRESHOLD: ireq->i_val = vap->iv_bmissthreshold; break; case IEEE80211_IOC_CURCHAN: error = ieee80211_ioctl_getcurchan(vap, ireq); break; case IEEE80211_IOC_SHORTGI: ireq->i_val = 0; if (vap->iv_flags_ht & IEEE80211_FHT_SHORTGI20) ireq->i_val |= IEEE80211_HTCAP_SHORTGI20; if (vap->iv_flags_ht & IEEE80211_FHT_SHORTGI40) ireq->i_val |= IEEE80211_HTCAP_SHORTGI40; break; case IEEE80211_IOC_AMPDU: ireq->i_val = 0; if (vap->iv_flags_ht & IEEE80211_FHT_AMPDU_TX) ireq->i_val |= 1; if (vap->iv_flags_ht & IEEE80211_FHT_AMPDU_RX) ireq->i_val |= 2; break; case IEEE80211_IOC_AMPDU_LIMIT: /* XXX TODO: make this a per-node thing; and leave this as global */ if (vap->iv_opmode == IEEE80211_M_HOSTAP) ireq->i_val = vap->iv_ampdu_rxmax; else if (vap->iv_state == IEEE80211_S_RUN || vap->iv_state == IEEE80211_S_SLEEP) /* * XXX TODO: this isn't completely correct, as we've * negotiated the higher of the two. */ ireq->i_val = _IEEE80211_MASKSHIFT( vap->iv_bss->ni_htparam, IEEE80211_HTCAP_MAXRXAMPDU); else ireq->i_val = vap->iv_ampdu_limit; break; case IEEE80211_IOC_AMPDU_DENSITY: /* XXX TODO: make this a per-node thing; and leave this as global */ if (vap->iv_opmode == IEEE80211_M_STA && (vap->iv_state == IEEE80211_S_RUN || vap->iv_state == IEEE80211_S_SLEEP)) /* * XXX TODO: this isn't completely correct, as we've * negotiated the higher of the two. */ ireq->i_val = _IEEE80211_MASKSHIFT(vap->iv_bss->ni_htparam, IEEE80211_HTCAP_MPDUDENSITY); else ireq->i_val = vap->iv_ampdu_density; break; case IEEE80211_IOC_AMSDU: ireq->i_val = 0; if (vap->iv_flags_ht & IEEE80211_FHT_AMSDU_TX) ireq->i_val |= 1; if (vap->iv_flags_ht & IEEE80211_FHT_AMSDU_RX) ireq->i_val |= 2; break; case IEEE80211_IOC_AMSDU_LIMIT: ireq->i_val = vap->iv_amsdu_limit; /* XXX truncation? */ break; case IEEE80211_IOC_PUREN: ireq->i_val = (vap->iv_flags_ht & IEEE80211_FHT_PUREN) != 0; break; case IEEE80211_IOC_DOTH: ireq->i_val = (vap->iv_flags & IEEE80211_F_DOTH) != 0; break; case IEEE80211_IOC_REGDOMAIN: error = ieee80211_ioctl_getregdomain(vap, ireq); break; case IEEE80211_IOC_ROAM: error = ieee80211_ioctl_getroam(vap, ireq); break; case IEEE80211_IOC_TXPARAMS: error = ieee80211_ioctl_gettxparams(vap, ireq); break; case IEEE80211_IOC_HTCOMPAT: ireq->i_val = (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) != 0; break; case IEEE80211_IOC_DWDS: ireq->i_val = (vap->iv_flags & IEEE80211_F_DWDS) != 0; break; case IEEE80211_IOC_INACTIVITY: ireq->i_val = (vap->iv_flags_ext & IEEE80211_FEXT_INACT) != 0; break; case IEEE80211_IOC_APPIE: error = ieee80211_ioctl_getappie(vap, ireq); break; case IEEE80211_IOC_WPS: ireq->i_val = (vap->iv_flags_ext & IEEE80211_FEXT_WPS) != 0; break; case IEEE80211_IOC_TSN: ireq->i_val = (vap->iv_flags_ext & IEEE80211_FEXT_TSN) != 0; break; case IEEE80211_IOC_DFS: ireq->i_val = (vap->iv_flags_ext & IEEE80211_FEXT_DFS) != 0; break; case IEEE80211_IOC_DOTD: ireq->i_val = (vap->iv_flags_ext & IEEE80211_FEXT_DOTD) != 0; break; case IEEE80211_IOC_DEVCAPS: error = ieee80211_ioctl_getdevcaps(ic, ireq); break; case IEEE80211_IOC_HTPROTMODE: ireq->i_val = vap->iv_htprotmode; break; case IEEE80211_IOC_HTCONF: if (vap->iv_flags_ht & IEEE80211_FHT_HT) { ireq->i_val = 1; if (vap->iv_flags_ht & IEEE80211_FHT_USEHT40) ireq->i_val |= 2; } else ireq->i_val = 0; break; case IEEE80211_IOC_STA_VLAN: error = ieee80211_ioctl_getstavlan(vap, ireq); break; case IEEE80211_IOC_SMPS: if (vap->iv_opmode == IEEE80211_M_STA && (vap->iv_state == IEEE80211_S_RUN || vap->iv_state == IEEE80211_S_SLEEP)) { if (vap->iv_bss->ni_flags & IEEE80211_NODE_MIMO_RTS) ireq->i_val = IEEE80211_HTCAP_SMPS_DYNAMIC; else if (vap->iv_bss->ni_flags & IEEE80211_NODE_MIMO_PS) ireq->i_val = IEEE80211_HTCAP_SMPS_ENA; else ireq->i_val = IEEE80211_HTCAP_SMPS_OFF; } else ireq->i_val = vap->iv_htcaps & IEEE80211_HTCAP_SMPS; break; case IEEE80211_IOC_RIFS: if (vap->iv_opmode == IEEE80211_M_STA && (vap->iv_state == IEEE80211_S_RUN || vap->iv_state == IEEE80211_S_SLEEP)) ireq->i_val = (vap->iv_bss->ni_flags & IEEE80211_NODE_RIFS) != 0; else ireq->i_val = (vap->iv_flags_ht & IEEE80211_FHT_RIFS) != 0; break; case IEEE80211_IOC_STBC: ireq->i_val = 0; if (vap->iv_flags_ht & IEEE80211_FHT_STBC_TX) ireq->i_val |= 1; if (vap->iv_flags_ht & IEEE80211_FHT_STBC_RX) ireq->i_val |= 2; break; case IEEE80211_IOC_LDPC: ireq->i_val = 0; if (vap->iv_flags_ht & IEEE80211_FHT_LDPC_TX) ireq->i_val |= 1; if (vap->iv_flags_ht & IEEE80211_FHT_LDPC_RX) ireq->i_val |= 2; break; case IEEE80211_IOC_UAPSD: ireq->i_val = 0; if (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD) ireq->i_val = 1; break; case IEEE80211_IOC_VHTCONF: ireq->i_val = vap->iv_vht_flags & IEEE80211_FVHT_MASK; break; default: error = ieee80211_ioctl_getdefault(vap, ireq); break; } return error; } static int ieee80211_ioctl_setkey(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211req_key ik; struct ieee80211_node *ni; struct ieee80211_key *wk; uint16_t kid; int error, i; if (ireq->i_len != sizeof(ik)) return EINVAL; error = copyin(ireq->i_data, &ik, sizeof(ik)); if (error) return error; /* NB: cipher support is verified by ieee80211_crypt_newkey */ /* NB: this also checks ik->ik_keylen > sizeof(wk->wk_key) */ if (ik.ik_keylen > sizeof(ik.ik_keydata)) return E2BIG; kid = ik.ik_keyix; if (kid == IEEE80211_KEYIX_NONE) { /* XXX unicast keys currently must be tx/rx */ if (ik.ik_flags != (IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV)) return EINVAL; if (vap->iv_opmode == IEEE80211_M_STA) { ni = ieee80211_ref_node(vap->iv_bss); if (!IEEE80211_ADDR_EQ(ik.ik_macaddr, ni->ni_bssid)) { ieee80211_free_node(ni); return EADDRNOTAVAIL; } } else { ni = ieee80211_find_vap_node(&vap->iv_ic->ic_sta, vap, ik.ik_macaddr); if (ni == NULL) return ENOENT; } wk = &ni->ni_ucastkey; } else { if (kid >= IEEE80211_WEP_NKID) return EINVAL; wk = &vap->iv_nw_keys[kid]; /* * Global slots start off w/o any assigned key index. * Force one here for consistency with IEEE80211_IOC_WEPKEY. */ if (wk->wk_keyix == IEEE80211_KEYIX_NONE) wk->wk_keyix = kid; ni = NULL; } error = 0; ieee80211_key_update_begin(vap); if (ieee80211_crypto_newkey(vap, ik.ik_type, ik.ik_flags, wk)) { wk->wk_keylen = ik.ik_keylen; /* NB: MIC presence is implied by cipher type */ if (wk->wk_keylen > IEEE80211_KEYBUF_SIZE) wk->wk_keylen = IEEE80211_KEYBUF_SIZE; for (i = 0; i < IEEE80211_TID_SIZE; i++) wk->wk_keyrsc[i] = ik.ik_keyrsc; wk->wk_keytsc = 0; /* new key, reset */ memset(wk->wk_key, 0, sizeof(wk->wk_key)); memcpy(wk->wk_key, ik.ik_keydata, ik.ik_keylen); IEEE80211_ADDR_COPY(wk->wk_macaddr, ni != NULL ? ni->ni_macaddr : ik.ik_macaddr); if (!ieee80211_crypto_setkey(vap, wk)) error = EIO; else if ((ik.ik_flags & IEEE80211_KEY_DEFAULT)) /* * Inform the driver that this is the default * transmit key. Now, ideally we'd just set * a flag in the key update that would * say "yes, we're the default key", but * that currently isn't the way the ioctl -> * key interface works. */ ieee80211_crypto_set_deftxkey(vap, kid); } else error = ENXIO; ieee80211_key_update_end(vap); if (ni != NULL) ieee80211_free_node(ni); return error; } static int ieee80211_ioctl_delkey(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211req_del_key dk; int kid, error; if (ireq->i_len != sizeof(dk)) return EINVAL; error = copyin(ireq->i_data, &dk, sizeof(dk)); if (error) return error; kid = dk.idk_keyix; /* XXX uint8_t -> uint16_t */ if (dk.idk_keyix == (uint8_t) IEEE80211_KEYIX_NONE) { struct ieee80211_node *ni; if (vap->iv_opmode == IEEE80211_M_STA) { ni = ieee80211_ref_node(vap->iv_bss); if (!IEEE80211_ADDR_EQ(dk.idk_macaddr, ni->ni_bssid)) { ieee80211_free_node(ni); return EADDRNOTAVAIL; } } else { ni = ieee80211_find_vap_node(&vap->iv_ic->ic_sta, vap, dk.idk_macaddr); if (ni == NULL) return ENOENT; } /* XXX error return */ ieee80211_node_delucastkey(ni); ieee80211_free_node(ni); } else { if (kid >= IEEE80211_WEP_NKID) return EINVAL; /* XXX error return */ ieee80211_crypto_delkey(vap, &vap->iv_nw_keys[kid]); } return 0; } struct mlmeop { struct ieee80211vap *vap; int op; int reason; }; static void mlmedebug(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN], int op, int reason) { #ifdef IEEE80211_DEBUG static const struct { int mask; const char *opstr; } ops[] = { { 0, "op#0" }, { IEEE80211_MSG_IOCTL | IEEE80211_MSG_STATE | IEEE80211_MSG_ASSOC, "assoc" }, { IEEE80211_MSG_IOCTL | IEEE80211_MSG_STATE | IEEE80211_MSG_ASSOC, "disassoc" }, { IEEE80211_MSG_IOCTL | IEEE80211_MSG_STATE | IEEE80211_MSG_AUTH, "deauth" }, { IEEE80211_MSG_IOCTL | IEEE80211_MSG_STATE | IEEE80211_MSG_AUTH, "authorize" }, { IEEE80211_MSG_IOCTL | IEEE80211_MSG_STATE | IEEE80211_MSG_AUTH, "unauthorize" }, }; if (op == IEEE80211_MLME_AUTH) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_IOCTL | IEEE80211_MSG_STATE | IEEE80211_MSG_AUTH, mac, "station authenticate %s via MLME (reason: %d (%s))", reason == IEEE80211_STATUS_SUCCESS ? "ACCEPT" : "REJECT", reason, ieee80211_reason_to_string(reason)); } else if (!(IEEE80211_MLME_ASSOC <= op && op <= IEEE80211_MLME_AUTH)) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ANY, mac, "unknown MLME request %d (reason: %d (%s))", op, reason, ieee80211_reason_to_string(reason)); } else if (reason == IEEE80211_STATUS_SUCCESS) { IEEE80211_NOTE_MAC(vap, ops[op].mask, mac, "station %s via MLME", ops[op].opstr); } else { IEEE80211_NOTE_MAC(vap, ops[op].mask, mac, "station %s via MLME (reason: %d (%s))", ops[op].opstr, reason, ieee80211_reason_to_string(reason)); } #endif /* IEEE80211_DEBUG */ } static void domlme(void *arg, struct ieee80211_node *ni) { struct mlmeop *mop = arg; struct ieee80211vap *vap = ni->ni_vap; if (vap != mop->vap) return; /* * NB: if ni_associd is zero then the node is already cleaned * up and we don't need to do this (we're safely holding a * reference but should otherwise not modify it's state). */ if (ni->ni_associd == 0) return; mlmedebug(vap, ni->ni_macaddr, mop->op, mop->reason); if (mop->op == IEEE80211_MLME_DEAUTH) { IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, mop->reason); } else { IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DISASSOC, mop->reason); } ieee80211_node_leave(ni); } static int setmlme_dropsta(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN], struct mlmeop *mlmeop) { struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta; struct ieee80211_node *ni; int error = 0; /* NB: the broadcast address means do 'em all */ if (!IEEE80211_ADDR_EQ(mac, ieee80211_vap_get_broadcast_address(vap))) { IEEE80211_NODE_LOCK(nt); ni = ieee80211_find_node_locked(nt, mac); IEEE80211_NODE_UNLOCK(nt); /* * Don't do the node update inside the node * table lock. This unfortunately causes LORs * with drivers and their TX paths. */ if (ni != NULL) { domlme(mlmeop, ni); ieee80211_free_node(ni); } else error = ENOENT; } else { ieee80211_iterate_nodes(nt, domlme, mlmeop); } return error; } static int setmlme_common(struct ieee80211vap *vap, int op, const uint8_t mac[IEEE80211_ADDR_LEN], int reason) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node_table *nt = &ic->ic_sta; struct ieee80211_node *ni; struct mlmeop mlmeop; int error; error = 0; switch (op) { case IEEE80211_MLME_DISASSOC: case IEEE80211_MLME_DEAUTH: switch (vap->iv_opmode) { case IEEE80211_M_STA: mlmedebug(vap, vap->iv_bss->ni_macaddr, op, reason); /* XXX not quite right */ ieee80211_new_state(vap, IEEE80211_S_INIT, reason); break; case IEEE80211_M_HOSTAP: mlmeop.vap = vap; mlmeop.op = op; mlmeop.reason = reason; error = setmlme_dropsta(vap, mac, &mlmeop); break; case IEEE80211_M_WDS: /* XXX user app should send raw frame? */ if (op != IEEE80211_MLME_DEAUTH) { error = EINVAL; break; } #if 0 /* XXX accept any address, simplifies user code */ if (!IEEE80211_ADDR_EQ(mac, vap->iv_bss->ni_macaddr)) { error = EINVAL; break; } #endif mlmedebug(vap, vap->iv_bss->ni_macaddr, op, reason); ni = ieee80211_ref_node(vap->iv_bss); IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, reason); ieee80211_free_node(ni); break; case IEEE80211_M_MBSS: IEEE80211_NODE_LOCK(nt); ni = ieee80211_find_node_locked(nt, mac); /* * Don't do the node update inside the node * table lock. This unfortunately causes LORs * with drivers and their TX paths. */ IEEE80211_NODE_UNLOCK(nt); if (ni != NULL) { ieee80211_node_leave(ni); ieee80211_free_node(ni); } else { error = ENOENT; } break; default: error = EINVAL; break; } break; case IEEE80211_MLME_AUTHORIZE: case IEEE80211_MLME_UNAUTHORIZE: if (vap->iv_opmode != IEEE80211_M_HOSTAP && vap->iv_opmode != IEEE80211_M_WDS) { error = EINVAL; break; } IEEE80211_NODE_LOCK(nt); ni = ieee80211_find_vap_node_locked(nt, vap, mac); /* * Don't do the node update inside the node * table lock. This unfortunately causes LORs * with drivers and their TX paths. */ IEEE80211_NODE_UNLOCK(nt); if (ni != NULL) { mlmedebug(vap, mac, op, reason); if (op == IEEE80211_MLME_AUTHORIZE) ieee80211_node_authorize(ni); else ieee80211_node_unauthorize(ni); ieee80211_free_node(ni); } else error = ENOENT; break; case IEEE80211_MLME_AUTH: if (vap->iv_opmode != IEEE80211_M_HOSTAP) { error = EINVAL; break; } IEEE80211_NODE_LOCK(nt); ni = ieee80211_find_vap_node_locked(nt, vap, mac); /* * Don't do the node update inside the node * table lock. This unfortunately causes LORs * with drivers and their TX paths. */ IEEE80211_NODE_UNLOCK(nt); if (ni != NULL) { mlmedebug(vap, mac, op, reason); if (reason == IEEE80211_STATUS_SUCCESS) { IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 2); /* * For shared key auth, just continue the * exchange. Otherwise 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 && ni->ni_challenge == NULL) ieee80211_node_authorize(ni); } else { vap->iv_stats.is_rx_acl++; ieee80211_send_error(ni, ni->ni_macaddr, IEEE80211_FC0_SUBTYPE_AUTH, 2|(reason<<16)); ieee80211_node_leave(ni); } ieee80211_free_node(ni); } else error = ENOENT; break; default: error = EINVAL; break; } return error; } struct scanlookup { const uint8_t *mac; int esslen; const uint8_t *essid; bool found; struct ieee80211_scan_entry se; }; /* * Match mac address and any ssid. */ static void mlmelookup(void *arg, const struct ieee80211_scan_entry *se) { struct scanlookup *look = arg; int rv; if (look->found) return; if (!IEEE80211_ADDR_EQ(look->mac, se->se_macaddr)) return; if (look->esslen != 0) { if (se->se_ssid[1] != look->esslen) return; if (memcmp(look->essid, se->se_ssid+2, look->esslen)) return; } /* * First copy everything and then ensure we get our own copy of se_ies. */ look->se = *se; look->se.se_ies.data = 0; look->se.se_ies.len = 0; rv = ieee80211_ies_init(&look->se.se_ies, se->se_ies.data, se->se_ies.len); if (rv != 0) /* No error */ look->found = true; } static int setmlme_assoc_sta(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN], int ssid_len, const uint8_t ssid[IEEE80211_NWID_LEN]) { struct scanlookup lookup; int rv; KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("expected opmode STA not %s", ieee80211_opmode_name[vap->iv_opmode])); /* NB: this is racey if roaming is !manual */ lookup.mac = mac; lookup.esslen = ssid_len; lookup.essid = ssid; memset(&lookup.se, 0, sizeof(lookup.se)); lookup.found = false; ieee80211_scan_iterate(vap, mlmelookup, &lookup); if (!lookup.found) return ENOENT; mlmedebug(vap, mac, IEEE80211_MLME_ASSOC, 0); rv = ieee80211_sta_join(vap, lookup.se.se_chan, &lookup.se); ieee80211_ies_cleanup(&lookup.se.se_ies); if (rv == 0) return EIO; /* XXX unique but could be better */ return 0; } static int setmlme_assoc_adhoc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN], int ssid_len, const uint8_t ssid[IEEE80211_NWID_LEN]) { struct ieee80211_scan_req *sr; int error; KASSERT(vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_AHDEMO, ("expected opmode IBSS or AHDEMO not %s", ieee80211_opmode_name[vap->iv_opmode])); if (ssid_len == 0 || ssid_len > IEEE80211_NWID_LEN) return EINVAL; sr = IEEE80211_MALLOC(sizeof(*sr), M_TEMP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (sr == NULL) return ENOMEM; /* NB: IEEE80211_IOC_SSID call missing for ap_scan=2. */ memset(vap->iv_des_ssid[0].ssid, 0, IEEE80211_NWID_LEN); vap->iv_des_ssid[0].len = ssid_len; memcpy(vap->iv_des_ssid[0].ssid, ssid, ssid_len); vap->iv_des_nssid = 1; sr->sr_flags = IEEE80211_IOC_SCAN_ACTIVE | IEEE80211_IOC_SCAN_ONCE; sr->sr_duration = IEEE80211_IOC_SCAN_FOREVER; memcpy(sr->sr_ssid[0].ssid, ssid, ssid_len); sr->sr_ssid[0].len = ssid_len; sr->sr_nssid = 1; error = ieee80211_scanreq(vap, sr); IEEE80211_FREE(sr, M_TEMP); return error; } static int ieee80211_ioctl_setmlme(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211req_mlme mlme; int error; if (ireq->i_len != sizeof(mlme)) return EINVAL; error = copyin(ireq->i_data, &mlme, sizeof(mlme)); if (error) return error; if (vap->iv_opmode == IEEE80211_M_STA && mlme.im_op == IEEE80211_MLME_ASSOC) return setmlme_assoc_sta(vap, mlme.im_macaddr, vap->iv_des_ssid[0].len, vap->iv_des_ssid[0].ssid); else if ((vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_AHDEMO) && mlme.im_op == IEEE80211_MLME_ASSOC) return setmlme_assoc_adhoc(vap, mlme.im_macaddr, mlme.im_ssid_len, mlme.im_ssid); else return setmlme_common(vap, mlme.im_op, mlme.im_macaddr, mlme.im_reason); } static int ieee80211_ioctl_macmac(struct ieee80211vap *vap, struct ieee80211req *ireq) { uint8_t mac[IEEE80211_ADDR_LEN]; const struct ieee80211_aclator *acl = vap->iv_acl; int error; if (ireq->i_len != sizeof(mac)) return EINVAL; error = copyin(ireq->i_data, mac, ireq->i_len); if (error) return error; if (acl == NULL) { acl = ieee80211_aclator_get("mac"); if (acl == NULL || !acl->iac_attach(vap)) return EINVAL; vap->iv_acl = acl; } if (ireq->i_type == IEEE80211_IOC_ADDMAC) acl->iac_add(vap, mac); else acl->iac_remove(vap, mac); return 0; } static int ieee80211_ioctl_setmaccmd(struct ieee80211vap *vap, struct ieee80211req *ireq) { const struct ieee80211_aclator *acl = vap->iv_acl; switch (ireq->i_val) { case IEEE80211_MACCMD_POLICY_OPEN: case IEEE80211_MACCMD_POLICY_ALLOW: case IEEE80211_MACCMD_POLICY_DENY: case IEEE80211_MACCMD_POLICY_RADIUS: if (acl == NULL) { acl = ieee80211_aclator_get("mac"); if (acl == NULL || !acl->iac_attach(vap)) return EINVAL; vap->iv_acl = acl; } acl->iac_setpolicy(vap, ireq->i_val); break; case IEEE80211_MACCMD_FLUSH: if (acl != NULL) acl->iac_flush(vap); /* NB: silently ignore when not in use */ break; case IEEE80211_MACCMD_DETACH: if (acl != NULL) { vap->iv_acl = NULL; acl->iac_detach(vap); } break; default: if (acl == NULL) return EINVAL; else return acl->iac_setioctl(vap, ireq); } return 0; } static int ieee80211_ioctl_setchanlist(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; uint8_t *chanlist, *list; int i, nchan, maxchan, error; if (ireq->i_len > sizeof(ic->ic_chan_active)) ireq->i_len = sizeof(ic->ic_chan_active); list = IEEE80211_MALLOC(ireq->i_len + IEEE80211_CHAN_BYTES, M_TEMP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (list == NULL) return ENOMEM; error = copyin(ireq->i_data, list, ireq->i_len); if (error) { IEEE80211_FREE(list, M_TEMP); return error; } nchan = 0; chanlist = list + ireq->i_len; /* NB: zero'd already */ maxchan = ireq->i_len * NBBY; for (i = 0; i < ic->ic_nchans; i++) { const struct ieee80211_channel *c = &ic->ic_channels[i]; /* * Calculate the intersection of the user list and the * available channels so users can do things like specify * 1-255 to get all available channels. */ if (c->ic_ieee < maxchan && isset(list, c->ic_ieee)) { setbit(chanlist, c->ic_ieee); nchan++; } } if (nchan == 0) { IEEE80211_FREE(list, M_TEMP); return EINVAL; } if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && /* XXX */ isclr(chanlist, ic->ic_bsschan->ic_ieee)) ic->ic_bsschan = IEEE80211_CHAN_ANYC; memcpy(ic->ic_chan_active, chanlist, IEEE80211_CHAN_BYTES); ieee80211_scan_flush(vap); IEEE80211_FREE(list, M_TEMP); return ENETRESET; } static int ieee80211_ioctl_setstastats(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211_node *ni; uint8_t macaddr[IEEE80211_ADDR_LEN]; int error; /* * NB: we could copyin ieee80211req_sta_stats so apps * could make selective changes but that's overkill; * just clear all stats for now. */ if (ireq->i_len < IEEE80211_ADDR_LEN) return EINVAL; error = copyin(ireq->i_data, macaddr, IEEE80211_ADDR_LEN); if (error != 0) return error; ni = ieee80211_find_vap_node(&vap->iv_ic->ic_sta, vap, macaddr); if (ni == NULL) return ENOENT; /* XXX require ni_vap == vap? */ memset(&ni->ni_stats, 0, sizeof(ni->ni_stats)); ieee80211_free_node(ni); return 0; } static int ieee80211_ioctl_setstatxpow(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211_node *ni; struct ieee80211req_sta_txpow txpow; int error; if (ireq->i_len != sizeof(txpow)) return EINVAL; error = copyin(ireq->i_data, &txpow, sizeof(txpow)); if (error != 0) return error; ni = ieee80211_find_vap_node(&vap->iv_ic->ic_sta, vap, txpow.it_macaddr); if (ni == NULL) return ENOENT; ni->ni_txpower = txpow.it_txpow; ieee80211_free_node(ni); return error; } static int ieee80211_ioctl_setwmeparam(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_wme_state *wme = &ic->ic_wme; struct wmeParams *wmep, *chanp; int isbss, ac, aggrmode; if ((ic->ic_caps & IEEE80211_C_WME) == 0) return EOPNOTSUPP; isbss = (ireq->i_len & IEEE80211_WMEPARAM_BSS); ac = (ireq->i_len & IEEE80211_WMEPARAM_VAL); aggrmode = (wme->wme_flags & WME_F_AGGRMODE); if (ac >= WME_NUM_AC) ac = WME_AC_BE; if (isbss) { chanp = &wme->wme_bssChanParams.cap_wmeParams[ac]; wmep = &wme->wme_wmeBssChanParams.cap_wmeParams[ac]; } else { chanp = &wme->wme_chanParams.cap_wmeParams[ac]; wmep = &wme->wme_wmeChanParams.cap_wmeParams[ac]; } switch (ireq->i_type) { case IEEE80211_IOC_WME_CWMIN: /* WME: CWmin */ wmep->wmep_logcwmin = ireq->i_val; if (!isbss || !aggrmode) chanp->wmep_logcwmin = ireq->i_val; break; case IEEE80211_IOC_WME_CWMAX: /* WME: CWmax */ wmep->wmep_logcwmax = ireq->i_val; if (!isbss || !aggrmode) chanp->wmep_logcwmax = ireq->i_val; break; case IEEE80211_IOC_WME_AIFS: /* WME: AIFS */ wmep->wmep_aifsn = ireq->i_val; if (!isbss || !aggrmode) chanp->wmep_aifsn = ireq->i_val; break; case IEEE80211_IOC_WME_TXOPLIMIT: /* WME: txops limit */ wmep->wmep_txopLimit = ireq->i_val; if (!isbss || !aggrmode) chanp->wmep_txopLimit = ireq->i_val; break; case IEEE80211_IOC_WME_ACM: /* WME: ACM (bss only) */ wmep->wmep_acm = ireq->i_val; if (!aggrmode) chanp->wmep_acm = ireq->i_val; break; case IEEE80211_IOC_WME_ACKPOLICY: /* WME: ACK policy (!bss only)*/ wmep->wmep_noackPolicy = chanp->wmep_noackPolicy = (ireq->i_val) == 0; break; } ieee80211_wme_updateparams(vap); return 0; } static int find11gchannel(struct ieee80211com *ic, int start, int freq) { const struct ieee80211_channel *c; int i; for (i = start+1; i < ic->ic_nchans; i++) { c = &ic->ic_channels[i]; if (c->ic_freq == freq && IEEE80211_IS_CHAN_ANYG(c)) return 1; } /* NB: should not be needed but in case things are mis-sorted */ for (i = 0; i < start; i++) { c = &ic->ic_channels[i]; if (c->ic_freq == freq && IEEE80211_IS_CHAN_ANYG(c)) return 1; } return 0; } static struct ieee80211_channel * findchannel(struct ieee80211com *ic, int ieee, int mode) { static const u_int chanflags[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = 0, [IEEE80211_MODE_11A] = IEEE80211_CHAN_A, [IEEE80211_MODE_11B] = IEEE80211_CHAN_B, [IEEE80211_MODE_11G] = IEEE80211_CHAN_G, [IEEE80211_MODE_FH] = IEEE80211_CHAN_FHSS, [IEEE80211_MODE_TURBO_A] = IEEE80211_CHAN_108A, [IEEE80211_MODE_TURBO_G] = IEEE80211_CHAN_108G, [IEEE80211_MODE_STURBO_A] = IEEE80211_CHAN_STURBO, [IEEE80211_MODE_HALF] = IEEE80211_CHAN_HALF, [IEEE80211_MODE_QUARTER] = IEEE80211_CHAN_QUARTER, /* NB: handled specially below */ [IEEE80211_MODE_11NA] = IEEE80211_CHAN_A, [IEEE80211_MODE_11NG] = IEEE80211_CHAN_G, [IEEE80211_MODE_VHT_5GHZ] = IEEE80211_CHAN_A, [IEEE80211_MODE_VHT_2GHZ] = IEEE80211_CHAN_G, }; u_int modeflags; int i; modeflags = chanflags[mode]; for (i = 0; i < ic->ic_nchans; i++) { struct ieee80211_channel *c = &ic->ic_channels[i]; if (c->ic_ieee != ieee) continue; if (mode == IEEE80211_MODE_AUTO) { /* ignore turbo channels for autoselect */ if (IEEE80211_IS_CHAN_TURBO(c)) continue; /* * XXX special-case 11b/g channels so we * always select the g channel if both * are present. * XXX prefer HT to non-HT? */ if (!IEEE80211_IS_CHAN_B(c) || !find11gchannel(ic, i, c->ic_freq)) return c; } else { /* must check VHT specifically */ if ((mode == IEEE80211_MODE_VHT_5GHZ || mode == IEEE80211_MODE_VHT_2GHZ) && !IEEE80211_IS_CHAN_VHT(c)) continue; /* * Must check HT specially - only match on HT, * not HT+VHT channels */ if ((mode == IEEE80211_MODE_11NA || mode == IEEE80211_MODE_11NG) && !IEEE80211_IS_CHAN_HT(c)) continue; if ((mode == IEEE80211_MODE_11NA || mode == IEEE80211_MODE_11NG) && IEEE80211_IS_CHAN_VHT(c)) continue; /* Check that the modeflags above match */ if ((c->ic_flags & modeflags) == modeflags) return c; } } return NULL; } /* * Check the specified against any desired mode (aka netband). * This is only used (presently) when operating in hostap mode * to enforce consistency. */ static int check_mode_consistency(const struct ieee80211_channel *c, int mode) { KASSERT(c != IEEE80211_CHAN_ANYC, ("oops, no channel")); switch (mode) { case IEEE80211_MODE_11B: return (IEEE80211_IS_CHAN_B(c)); case IEEE80211_MODE_11G: return (IEEE80211_IS_CHAN_ANYG(c) && !IEEE80211_IS_CHAN_HT(c)); case IEEE80211_MODE_11A: return (IEEE80211_IS_CHAN_A(c) && !IEEE80211_IS_CHAN_HT(c)); case IEEE80211_MODE_STURBO_A: return (IEEE80211_IS_CHAN_STURBO(c)); case IEEE80211_MODE_11NA: return (IEEE80211_IS_CHAN_HTA(c)); case IEEE80211_MODE_11NG: return (IEEE80211_IS_CHAN_HTG(c)); } return 1; } /* * Common code to set the current channel. If the device * is up and running this may result in an immediate channel * change or a kick of the state machine. */ static int setcurchan(struct ieee80211vap *vap, struct ieee80211_channel *c) { struct ieee80211com *ic = vap->iv_ic; int error; if (c != IEEE80211_CHAN_ANYC) { if (IEEE80211_IS_CHAN_RADAR(c)) return EBUSY; /* XXX better code? */ if (vap->iv_opmode == IEEE80211_M_HOSTAP) { if (IEEE80211_IS_CHAN_NOHOSTAP(c)) return EINVAL; if (!check_mode_consistency(c, vap->iv_des_mode)) return EINVAL; } else if (vap->iv_opmode == IEEE80211_M_IBSS) { if (IEEE80211_IS_CHAN_NOADHOC(c)) return EINVAL; } if ((vap->iv_state == IEEE80211_S_RUN || vap->iv_state == IEEE80211_S_SLEEP) && vap->iv_bss->ni_chan == c) return 0; /* NB: nothing to do */ } vap->iv_des_chan = c; error = 0; if (vap->iv_opmode == IEEE80211_M_MONITOR && vap->iv_des_chan != IEEE80211_CHAN_ANYC) { /* * Monitor mode can switch directly. */ if (IFNET_IS_UP_RUNNING(vap->iv_ifp)) { /* XXX need state machine for other vap's to follow */ ieee80211_setcurchan(ic, vap->iv_des_chan); vap->iv_bss->ni_chan = ic->ic_curchan; } else { ic->ic_curchan = vap->iv_des_chan; ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); } } else { /* * Need to go through the state machine in case we * need to reassociate or the like. The state machine * will pickup the desired channel and avoid scanning. */ if (IS_UP_AUTO(vap)) ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); else if (vap->iv_des_chan != IEEE80211_CHAN_ANYC) { /* * When not up+running and a real channel has * been specified fix the current channel so * there is immediate feedback; e.g. via ifconfig. */ ic->ic_curchan = vap->iv_des_chan; ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); } } return error; } /* * Old api for setting the current channel; this is * deprecated because channel numbers are ambiguous. */ static int ieee80211_ioctl_setchannel(struct ieee80211vap *vap, const struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel *c; /* XXX 0xffff overflows 16-bit signed */ if (ireq->i_val == 0 || ireq->i_val == (int16_t) IEEE80211_CHAN_ANY) { c = IEEE80211_CHAN_ANYC; } else { struct ieee80211_channel *c2; c = findchannel(ic, ireq->i_val, vap->iv_des_mode); if (c == NULL) { c = findchannel(ic, ireq->i_val, IEEE80211_MODE_AUTO); if (c == NULL) return EINVAL; } /* * Fine tune channel selection based on desired mode: * if 11b is requested, find the 11b version of any * 11g channel returned, * if static turbo, find the turbo version of any * 11a channel return, * if 11na is requested, find the ht version of any * 11a channel returned, * if 11ng is requested, find the ht version of any * 11g channel returned, * if 11ac is requested, find the 11ac version * of any 11a/11na channel returned, * (TBD) 11acg (2GHz VHT) * otherwise we should be ok with what we've got. */ switch (vap->iv_des_mode) { case IEEE80211_MODE_11B: if (IEEE80211_IS_CHAN_ANYG(c)) { c2 = findchannel(ic, ireq->i_val, IEEE80211_MODE_11B); /* NB: should not happen, =>'s 11g w/o 11b */ if (c2 != NULL) c = c2; } break; case IEEE80211_MODE_TURBO_A: if (IEEE80211_IS_CHAN_A(c)) { c2 = findchannel(ic, ireq->i_val, IEEE80211_MODE_TURBO_A); if (c2 != NULL) c = c2; } break; case IEEE80211_MODE_11NA: if (IEEE80211_IS_CHAN_A(c)) { c2 = findchannel(ic, ireq->i_val, IEEE80211_MODE_11NA); if (c2 != NULL) c = c2; } break; case IEEE80211_MODE_11NG: if (IEEE80211_IS_CHAN_ANYG(c)) { c2 = findchannel(ic, ireq->i_val, IEEE80211_MODE_11NG); if (c2 != NULL) c = c2; } break; case IEEE80211_MODE_VHT_2GHZ: - printf("%s: TBD\n", __func__); + net80211_printf("%s: TBD\n", __func__); break; case IEEE80211_MODE_VHT_5GHZ: if (IEEE80211_IS_CHAN_A(c)) { c2 = findchannel(ic, ireq->i_val, IEEE80211_MODE_VHT_5GHZ); if (c2 != NULL) c = c2; } break; default: /* NB: no static turboG */ break; } } return setcurchan(vap, c); } /* * New/current api for setting the current channel; a complete * channel description is provide so there is no ambiguity in * identifying the channel. */ static int ieee80211_ioctl_setcurchan(struct ieee80211vap *vap, const struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel chan, *c; int error; if (ireq->i_len != sizeof(chan)) return EINVAL; error = copyin(ireq->i_data, &chan, sizeof(chan)); if (error != 0) return error; /* XXX 0xffff overflows 16-bit signed */ if (chan.ic_freq == 0 || chan.ic_freq == IEEE80211_CHAN_ANY) { c = IEEE80211_CHAN_ANYC; } else { c = ieee80211_find_channel(ic, chan.ic_freq, chan.ic_flags); if (c == NULL) return EINVAL; } return setcurchan(vap, c); } static int ieee80211_ioctl_setregdomain(struct ieee80211vap *vap, const struct ieee80211req *ireq) { struct ieee80211_regdomain_req *reg; int nchans, error; nchans = 1 + ((ireq->i_len - sizeof(struct ieee80211_regdomain_req)) / sizeof(struct ieee80211_channel)); if (!(1 <= nchans && nchans <= IEEE80211_CHAN_MAX)) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_IOCTL, "%s: bad # chans, i_len %d nchans %d\n", __func__, ireq->i_len, nchans); return EINVAL; } reg = (struct ieee80211_regdomain_req *) IEEE80211_MALLOC(IEEE80211_REGDOMAIN_SIZE(nchans), M_TEMP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (reg == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_IOCTL, "%s: no memory, nchans %d\n", __func__, nchans); return ENOMEM; } error = copyin(ireq->i_data, reg, IEEE80211_REGDOMAIN_SIZE(nchans)); if (error == 0) { /* NB: validate inline channel count against storage size */ if (reg->chaninfo.ic_nchans != nchans) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_IOCTL, "%s: chan cnt mismatch, %d != %d\n", __func__, reg->chaninfo.ic_nchans, nchans); error = EINVAL; } else error = ieee80211_setregdomain(vap, reg); } IEEE80211_FREE(reg, M_TEMP); return (error == 0 ? ENETRESET : error); } static int checkrate(const struct ieee80211_rateset *rs, int rate) { int i; if (rate == IEEE80211_FIXED_RATE_NONE) return 1; for (i = 0; i < rs->rs_nrates; i++) if ((rs->rs_rates[i] & IEEE80211_RATE_VAL) == rate) return 1; return 0; } static int checkmcs(const struct ieee80211_htrateset *rs, int mcs) { int rate_val = IEEE80211_RV(mcs); int i; if (mcs == IEEE80211_FIXED_RATE_NONE) return 1; if ((mcs & IEEE80211_RATE_MCS) == 0) /* MCS always have 0x80 set */ return 0; for (i = 0; i < rs->rs_nrates; i++) if (IEEE80211_RV(rs->rs_rates[i]) == rate_val) return 1; return 0; } static int ieee80211_ioctl_setroam(struct ieee80211vap *vap, const struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_roamparams_req *parms; struct ieee80211_roamparam *src, *dst; const struct ieee80211_htrateset *rs_ht; const struct ieee80211_rateset *rs; int changed, error, mode, is11n, nmodes; if (ireq->i_len != sizeof(vap->iv_roamparms)) return EINVAL; parms = IEEE80211_MALLOC(sizeof(*parms), M_TEMP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (parms == NULL) return ENOMEM; error = copyin(ireq->i_data, parms, ireq->i_len); if (error != 0) goto fail; changed = 0; nmodes = IEEE80211_MODE_MAX; /* validate parameters and check if anything changed */ for (mode = IEEE80211_MODE_11A; mode < nmodes; mode++) { if (isclr(ic->ic_modecaps, mode)) continue; src = &parms->params[mode]; dst = &vap->iv_roamparms[mode]; rs = &ic->ic_sup_rates[mode]; /* NB: 11n maps to legacy */ rs_ht = &ic->ic_sup_htrates; is11n = (mode == IEEE80211_MODE_11NA || mode == IEEE80211_MODE_11NG); /* XXX TODO: 11ac */ if (src->rate != dst->rate) { if (!checkrate(rs, src->rate) && (!is11n || !checkmcs(rs_ht, src->rate))) { error = EINVAL; goto fail; } changed++; } if (src->rssi != dst->rssi) changed++; } if (changed) { /* * Copy new parameters in place and notify the * driver so it can push state to the device. */ /* XXX locking? */ for (mode = IEEE80211_MODE_11A; mode < nmodes; mode++) { if (isset(ic->ic_modecaps, mode)) vap->iv_roamparms[mode] = parms->params[mode]; } if (vap->iv_roaming == IEEE80211_ROAMING_DEVICE) error = ERESTART; } fail: IEEE80211_FREE(parms, M_TEMP); return error; } static int ieee80211_ioctl_settxparams(struct ieee80211vap *vap, const struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_txparams_req parms; /* XXX stack use? */ struct ieee80211_txparam *src, *dst; const struct ieee80211_htrateset *rs_ht; const struct ieee80211_rateset *rs; int error, mode, changed, is11n, nmodes; /* NB: accept short requests for backwards compat */ if (ireq->i_len > sizeof(parms)) return EINVAL; error = copyin(ireq->i_data, &parms, ireq->i_len); if (error != 0) return error; nmodes = ireq->i_len / sizeof(struct ieee80211_txparam); changed = 0; /* validate parameters and check if anything changed */ for (mode = IEEE80211_MODE_11A; mode < nmodes; mode++) { if (isclr(ic->ic_modecaps, mode)) continue; src = &parms.params[mode]; dst = &vap->iv_txparms[mode]; rs = &ic->ic_sup_rates[mode]; /* NB: 11n maps to legacy */ rs_ht = &ic->ic_sup_htrates; is11n = (mode == IEEE80211_MODE_11NA || mode == IEEE80211_MODE_11NG); if (src->ucastrate != dst->ucastrate) { if (!checkrate(rs, src->ucastrate) && (!is11n || !checkmcs(rs_ht, src->ucastrate))) return EINVAL; changed++; } if (src->mcastrate != dst->mcastrate) { if (!checkrate(rs, src->mcastrate) && (!is11n || !checkmcs(rs_ht, src->mcastrate))) return EINVAL; changed++; } if (src->mgmtrate != dst->mgmtrate) { if (!checkrate(rs, src->mgmtrate) && (!is11n || !checkmcs(rs_ht, src->mgmtrate))) return EINVAL; changed++; } if (src->maxretry != dst->maxretry) /* NB: no bounds */ changed++; } if (changed) { /* * Copy new parameters in place and notify the * driver so it can push state to the device. */ for (mode = IEEE80211_MODE_11A; mode < nmodes; mode++) { if (isset(ic->ic_modecaps, mode)) vap->iv_txparms[mode] = parms.params[mode]; } /* XXX could be more intelligent, e.g. don't reset if setting not being used */ return ENETRESET; } return 0; } /* * Application Information Element support. */ static int setappie(struct ieee80211_appie **aie, const struct ieee80211req *ireq) { struct ieee80211_appie *app = *aie; struct ieee80211_appie *napp; int error; if (ireq->i_len == 0) { /* delete any existing ie */ if (app != NULL) { *aie = NULL; /* XXX racey */ IEEE80211_FREE(app, M_80211_NODE_IE); } return 0; } if (!(2 <= ireq->i_len && ireq->i_len <= IEEE80211_MAX_APPIE)) return EINVAL; /* * Allocate a new appie structure and copy in the user data. * When done swap in the new structure. Note that we do not * guard against users holding a ref to the old structure; * this must be handled outside this code. * * XXX bad bad bad */ napp = (struct ieee80211_appie *) IEEE80211_MALLOC( sizeof(struct ieee80211_appie) + ireq->i_len, M_80211_NODE_IE, IEEE80211_M_NOWAIT); if (napp == NULL) return ENOMEM; /* XXX holding ic lock */ error = copyin(ireq->i_data, napp->ie_data, ireq->i_len); if (error) { IEEE80211_FREE(napp, M_80211_NODE_IE); return error; } napp->ie_len = ireq->i_len; *aie = napp; if (app != NULL) IEEE80211_FREE(app, M_80211_NODE_IE); return 0; } static void setwparsnie(struct ieee80211vap *vap, uint8_t *ie, int space) { /* validate data is present as best we can */ if (space == 0 || 2+ie[1] > space) return; if (ie[0] == IEEE80211_ELEMID_VENDOR) vap->iv_wpa_ie = ie; else if (ie[0] == IEEE80211_ELEMID_RSN) vap->iv_rsn_ie = ie; } static int ieee80211_ioctl_setappie_locked(struct ieee80211vap *vap, const struct ieee80211req *ireq, int fc0) { int error; IEEE80211_LOCK_ASSERT(vap->iv_ic); switch (fc0 & IEEE80211_FC0_SUBTYPE_MASK) { case IEEE80211_FC0_SUBTYPE_BEACON: if (vap->iv_opmode != IEEE80211_M_HOSTAP && vap->iv_opmode != IEEE80211_M_IBSS) { error = EINVAL; break; } error = setappie(&vap->iv_appie_beacon, ireq); if (error == 0) ieee80211_beacon_notify(vap, IEEE80211_BEACON_APPIE); break; case IEEE80211_FC0_SUBTYPE_PROBE_RESP: error = setappie(&vap->iv_appie_proberesp, ireq); break; case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: if (vap->iv_opmode == IEEE80211_M_HOSTAP) error = setappie(&vap->iv_appie_assocresp, ireq); else error = EINVAL; break; case IEEE80211_FC0_SUBTYPE_PROBE_REQ: error = setappie(&vap->iv_appie_probereq, ireq); break; case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: if (vap->iv_opmode == IEEE80211_M_STA) error = setappie(&vap->iv_appie_assocreq, ireq); else error = EINVAL; break; case (IEEE80211_APPIE_WPA & IEEE80211_FC0_SUBTYPE_MASK): error = setappie(&vap->iv_appie_wpa, ireq); if (error == 0) { /* * Must split single blob of data into separate * WPA and RSN ie's because they go in different * locations in the mgt frames. * XXX use IEEE80211_IOC_WPA2 so user code does split */ vap->iv_wpa_ie = NULL; vap->iv_rsn_ie = NULL; if (vap->iv_appie_wpa != NULL) { struct ieee80211_appie *appie = vap->iv_appie_wpa; uint8_t *data = appie->ie_data; /* XXX ie length validate is painful, cheat */ setwparsnie(vap, data, appie->ie_len); setwparsnie(vap, data + 2 + data[1], appie->ie_len - (2 + data[1])); } if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_IBSS) { /* * Must rebuild beacon frame as the update * mechanism doesn't handle WPA/RSN ie's. * Could extend it but it doesn't normally * change; this is just to deal with hostapd * plumbing the ie after the interface is up. */ error = ENETRESET; } } break; default: error = EINVAL; break; } return error; } static int ieee80211_ioctl_setappie(struct ieee80211vap *vap, const struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; int error; uint8_t fc0; fc0 = ireq->i_val & 0xff; if ((fc0 & IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_MGT) return EINVAL; /* NB: could check iv_opmode and reject but hardly worth the effort */ IEEE80211_LOCK(ic); error = ieee80211_ioctl_setappie_locked(vap, ireq, fc0); IEEE80211_UNLOCK(ic); return error; } static int ieee80211_ioctl_chanswitch(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_chanswitch_req csr; struct ieee80211_channel *c; int error; if (ireq->i_len != sizeof(csr)) return EINVAL; error = copyin(ireq->i_data, &csr, sizeof(csr)); if (error != 0) return error; /* XXX adhoc mode not supported */ if (vap->iv_opmode != IEEE80211_M_HOSTAP || (vap->iv_flags & IEEE80211_F_DOTH) == 0) return EOPNOTSUPP; c = ieee80211_find_channel(ic, csr.csa_chan.ic_freq, csr.csa_chan.ic_flags); if (c == NULL) return ENOENT; IEEE80211_LOCK(ic); if ((ic->ic_flags & IEEE80211_F_CSAPENDING) == 0) ieee80211_csa_startswitch(ic, c, csr.csa_mode, csr.csa_count); else if (csr.csa_count == 0) ieee80211_csa_cancelswitch(ic); else error = EBUSY; IEEE80211_UNLOCK(ic); return error; } static int ieee80211_scanreq(struct ieee80211vap *vap, struct ieee80211_scan_req *sr) { #define IEEE80211_IOC_SCAN_FLAGS \ (IEEE80211_IOC_SCAN_NOPICK | IEEE80211_IOC_SCAN_ACTIVE | \ IEEE80211_IOC_SCAN_PICK1ST | IEEE80211_IOC_SCAN_BGSCAN | \ IEEE80211_IOC_SCAN_ONCE | IEEE80211_IOC_SCAN_NOBCAST | \ IEEE80211_IOC_SCAN_NOJOIN | IEEE80211_IOC_SCAN_FLUSH | \ IEEE80211_IOC_SCAN_CHECK) struct ieee80211com *ic = vap->iv_ic; int error, i; /* convert duration */ if (sr->sr_duration == IEEE80211_IOC_SCAN_FOREVER) sr->sr_duration = IEEE80211_SCAN_FOREVER; else { if (sr->sr_duration < IEEE80211_IOC_SCAN_DURATION_MIN || sr->sr_duration > IEEE80211_IOC_SCAN_DURATION_MAX) return EINVAL; sr->sr_duration = msecs_to_ticks(sr->sr_duration); } /* convert min/max channel dwell */ if (sr->sr_mindwell != 0) sr->sr_mindwell = msecs_to_ticks(sr->sr_mindwell); if (sr->sr_maxdwell != 0) sr->sr_maxdwell = msecs_to_ticks(sr->sr_maxdwell); /* NB: silently reduce ssid count to what is supported */ if (sr->sr_nssid > IEEE80211_SCAN_MAX_SSID) sr->sr_nssid = IEEE80211_SCAN_MAX_SSID; for (i = 0; i < sr->sr_nssid; i++) if (sr->sr_ssid[i].len > IEEE80211_NWID_LEN) return EINVAL; /* cleanse flags just in case, could reject if invalid flags */ sr->sr_flags &= IEEE80211_IOC_SCAN_FLAGS; /* * If the driver does not support BGSCAN, or BGSCAN is disabled * do not allow the IEEE80211_SCAN_BGSCAN flag to go through * to avoid accidentally enabling BGSCANs. * Also if not STA mode [see ieee80211_vap_setup()]. */ if ((vap->iv_caps & IEEE80211_C_BGSCAN) == 0 || (vap->iv_flags & IEEE80211_F_BGSCAN) == 0 || vap->iv_opmode != IEEE80211_M_STA) sr->sr_flags &= ~IEEE80211_IOC_SCAN_BGSCAN; /* * Add an implicit NOPICK if the vap is not marked UP. This * allows applications to scan without joining a bss (or picking * a channel and setting up a bss) and without forcing manual * roaming mode--you just need to mark the parent device UP. */ if ((vap->iv_ifp->if_flags & IFF_UP) == 0) sr->sr_flags |= IEEE80211_IOC_SCAN_NOPICK; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: vap %p iv_state %#x (%s) flags 0x%x%s " "duration 0x%x mindwell %u maxdwell %u nssid %d\n", __func__, vap, vap->iv_state, ieee80211_state_name[vap->iv_state], sr->sr_flags, (vap->iv_ifp->if_flags & IFF_UP) == 0 ? " (!IFF_UP)" : "", sr->sr_duration, sr->sr_mindwell, sr->sr_maxdwell, sr->sr_nssid); /* * If we are in INIT state then the driver has never had a chance * to setup hardware state to do a scan; we must use the state * machine to get us up to the SCAN state but once we reach SCAN * state we then want to use the supplied params. Stash the * parameters in the vap and mark IEEE80211_FEXT_SCANREQ; the * state machines will recognize this and use the stashed params * to issue the scan request. * * Otherwise just invoke the scan machinery directly. */ IEEE80211_LOCK(ic); if (ic->ic_nrunning == 0) { IEEE80211_UNLOCK(ic); return ENXIO; } if (vap->iv_state == IEEE80211_S_INIT) { /* NB: clobbers previous settings */ vap->iv_scanreq_flags = sr->sr_flags; vap->iv_scanreq_duration = sr->sr_duration; vap->iv_scanreq_nssid = sr->sr_nssid; for (i = 0; i < sr->sr_nssid; i++) { vap->iv_scanreq_ssid[i].len = sr->sr_ssid[i].len; memcpy(vap->iv_scanreq_ssid[i].ssid, sr->sr_ssid[i].ssid, sr->sr_ssid[i].len); } vap->iv_flags_ext |= IEEE80211_FEXT_SCANREQ; IEEE80211_UNLOCK(ic); ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); } else { vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANREQ; IEEE80211_UNLOCK(ic); if (sr->sr_flags & IEEE80211_IOC_SCAN_CHECK) { error = ieee80211_check_scan(vap, sr->sr_flags, sr->sr_duration, sr->sr_mindwell, sr->sr_maxdwell, sr->sr_nssid, /* NB: cheat, we assume structures are compatible */ (const struct ieee80211_scan_ssid *) &sr->sr_ssid[0]); } else { error = ieee80211_start_scan(vap, sr->sr_flags, sr->sr_duration, sr->sr_mindwell, sr->sr_maxdwell, sr->sr_nssid, /* NB: cheat, we assume structures are compatible */ (const struct ieee80211_scan_ssid *) &sr->sr_ssid[0]); } if (error == 0) return EINPROGRESS; } return 0; #undef IEEE80211_IOC_SCAN_FLAGS } static int ieee80211_ioctl_scanreq(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211_scan_req *sr; int error; if (ireq->i_len != sizeof(*sr)) return EINVAL; sr = IEEE80211_MALLOC(sizeof(*sr), M_TEMP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (sr == NULL) return ENOMEM; error = copyin(ireq->i_data, sr, sizeof(*sr)); if (error != 0) goto bad; error = ieee80211_scanreq(vap, sr); bad: IEEE80211_FREE(sr, M_TEMP); return error; } static int ieee80211_ioctl_setstavlan(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211_node *ni; struct ieee80211req_sta_vlan vlan; int error; if (ireq->i_len != sizeof(vlan)) return EINVAL; error = copyin(ireq->i_data, &vlan, sizeof(vlan)); if (error != 0) return error; if (!IEEE80211_ADDR_EQ(vlan.sv_macaddr, zerobssid)) { ni = ieee80211_find_vap_node(&vap->iv_ic->ic_sta, vap, vlan.sv_macaddr); if (ni == NULL) return ENOENT; } else ni = ieee80211_ref_node(vap->iv_bss); ni->ni_vlan = vlan.sv_vlan; ieee80211_free_node(ni); return error; } static int isvap11g(const struct ieee80211vap *vap) { const struct ieee80211_node *bss = vap->iv_bss; return bss->ni_chan != IEEE80211_CHAN_ANYC && IEEE80211_IS_CHAN_ANYG(bss->ni_chan); } static int isvapht(const struct ieee80211vap *vap) { const struct ieee80211_node *bss = vap->iv_bss; return bss->ni_chan != IEEE80211_CHAN_ANYC && IEEE80211_IS_CHAN_HT(bss->ni_chan); } /* * Dummy ioctl set handler so the linker set is defined. */ static int dummy_ioctl_set(struct ieee80211vap *vap, struct ieee80211req *ireq) { return ENOSYS; } IEEE80211_IOCTL_SET(dummy, dummy_ioctl_set); static int ieee80211_ioctl_setdefault(struct ieee80211vap *vap, struct ieee80211req *ireq) { ieee80211_ioctl_setfunc * const *set; int error; SET_FOREACH(set, ieee80211_ioctl_setset) { error = (*set)(vap, ireq); if (error != ENOSYS) return error; } return EINVAL; } static int ieee80211_ioctl_set80211(struct ieee80211vap *vap, u_long cmd, struct ieee80211req *ireq) { struct ieee80211com *ic = vap->iv_ic; int error; const struct ieee80211_authenticator *auth; uint8_t tmpkey[IEEE80211_KEYBUF_SIZE]; char tmpssid[IEEE80211_NWID_LEN]; uint8_t tmpbssid[IEEE80211_ADDR_LEN]; struct ieee80211_key *k; u_int kid; uint32_t flags; error = 0; switch (ireq->i_type) { case IEEE80211_IOC_SSID: if (ireq->i_val != 0 || ireq->i_len > IEEE80211_NWID_LEN) return EINVAL; error = copyin(ireq->i_data, tmpssid, ireq->i_len); if (error) break; memset(vap->iv_des_ssid[0].ssid, 0, IEEE80211_NWID_LEN); vap->iv_des_ssid[0].len = ireq->i_len; memcpy(vap->iv_des_ssid[0].ssid, tmpssid, ireq->i_len); vap->iv_des_nssid = (ireq->i_len > 0); error = ENETRESET; break; case IEEE80211_IOC_WEP: switch (ireq->i_val) { case IEEE80211_WEP_OFF: vap->iv_flags &= ~IEEE80211_F_PRIVACY; vap->iv_flags &= ~IEEE80211_F_DROPUNENC; break; case IEEE80211_WEP_ON: vap->iv_flags |= IEEE80211_F_PRIVACY; vap->iv_flags |= IEEE80211_F_DROPUNENC; break; case IEEE80211_WEP_MIXED: vap->iv_flags |= IEEE80211_F_PRIVACY; vap->iv_flags &= ~IEEE80211_F_DROPUNENC; break; } error = ENETRESET; break; case IEEE80211_IOC_WEPKEY: kid = (u_int) ireq->i_val; if (kid >= IEEE80211_WEP_NKID) return EINVAL; k = &vap->iv_nw_keys[kid]; if (ireq->i_len == 0) { /* zero-len =>'s delete any existing key */ (void) ieee80211_crypto_delkey(vap, k); break; } if (ireq->i_len > sizeof(tmpkey)) return EINVAL; memset(tmpkey, 0, sizeof(tmpkey)); error = copyin(ireq->i_data, tmpkey, ireq->i_len); if (error) break; ieee80211_key_update_begin(vap); k->wk_keyix = kid; /* NB: force fixed key id */ if (ieee80211_crypto_newkey(vap, IEEE80211_CIPHER_WEP, IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV, k)) { k->wk_keylen = ireq->i_len; memcpy(k->wk_key, tmpkey, sizeof(tmpkey)); IEEE80211_ADDR_COPY(k->wk_macaddr, vap->iv_myaddr); if (!ieee80211_crypto_setkey(vap, k)) error = EINVAL; } else error = EINVAL; ieee80211_key_update_end(vap); break; case IEEE80211_IOC_WEPTXKEY: kid = (u_int) ireq->i_val; if (kid >= IEEE80211_WEP_NKID && (uint16_t) kid != IEEE80211_KEYIX_NONE) return EINVAL; /* * Firmware devices may need to be told about an explicit * key index here, versus just inferring it from the * key set / change. Since we may also need to pause * things like transmit before the key is updated, * give the driver a chance to flush things by tying * into key update begin/end. */ ieee80211_key_update_begin(vap); ieee80211_crypto_set_deftxkey(vap, kid); ieee80211_key_update_end(vap); break; case IEEE80211_IOC_AUTHMODE: switch (ireq->i_val) { case IEEE80211_AUTH_WPA: case IEEE80211_AUTH_8021X: /* 802.1x */ case IEEE80211_AUTH_OPEN: /* open */ case IEEE80211_AUTH_SHARED: /* shared-key */ case IEEE80211_AUTH_AUTO: /* auto */ auth = ieee80211_authenticator_get(ireq->i_val); if (auth == NULL) return EINVAL; break; default: return EINVAL; } switch (ireq->i_val) { case IEEE80211_AUTH_WPA: /* WPA w/ 802.1x */ vap->iv_flags |= IEEE80211_F_PRIVACY; ireq->i_val = IEEE80211_AUTH_8021X; break; case IEEE80211_AUTH_OPEN: /* open */ vap->iv_flags &= ~(IEEE80211_F_WPA|IEEE80211_F_PRIVACY); break; case IEEE80211_AUTH_SHARED: /* shared-key */ case IEEE80211_AUTH_8021X: /* 802.1x */ vap->iv_flags &= ~IEEE80211_F_WPA; /* both require a key so mark the PRIVACY capability */ vap->iv_flags |= IEEE80211_F_PRIVACY; break; case IEEE80211_AUTH_AUTO: /* auto */ vap->iv_flags &= ~IEEE80211_F_WPA; /* XXX PRIVACY handling? */ /* XXX what's the right way to do this? */ break; } /* NB: authenticator attach/detach happens on state change */ vap->iv_bss->ni_authmode = ireq->i_val; /* XXX mixed/mode/usage? */ vap->iv_auth = auth; error = ENETRESET; break; case IEEE80211_IOC_CHANNEL: error = ieee80211_ioctl_setchannel(vap, ireq); break; case IEEE80211_IOC_POWERSAVE: switch (ireq->i_val) { case IEEE80211_POWERSAVE_OFF: if (vap->iv_flags & IEEE80211_F_PMGTON) { ieee80211_syncflag(vap, -IEEE80211_F_PMGTON); error = ERESTART; } break; case IEEE80211_POWERSAVE_ON: if ((vap->iv_caps & IEEE80211_C_PMGT) == 0) error = EOPNOTSUPP; else if ((vap->iv_flags & IEEE80211_F_PMGTON) == 0) { ieee80211_syncflag(vap, IEEE80211_F_PMGTON); error = ERESTART; } break; default: error = EINVAL; break; } break; case IEEE80211_IOC_POWERSAVESLEEP: if (ireq->i_val < 0) return EINVAL; ic->ic_lintval = ireq->i_val; error = ERESTART; break; case IEEE80211_IOC_RTSTHRESHOLD: if (!(IEEE80211_RTS_MIN <= ireq->i_val && ireq->i_val <= IEEE80211_RTS_MAX)) return EINVAL; vap->iv_rtsthreshold = ireq->i_val; error = ERESTART; break; case IEEE80211_IOC_PROTMODE: if (ireq->i_val > IEEE80211_PROT_RTSCTS) return EINVAL; vap->iv_protmode = (enum ieee80211_protmode)ireq->i_val; /* NB: if not operating in 11g this can wait */ if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan)) error = ERESTART; /* driver callback for protection mode update */ ieee80211_vap_update_erp_protmode(vap); break; case IEEE80211_IOC_TXPOWER: if ((ic->ic_caps & IEEE80211_C_TXPMGT) == 0) return EOPNOTSUPP; if (!(IEEE80211_TXPOWER_MIN <= ireq->i_val && ireq->i_val <= IEEE80211_TXPOWER_MAX)) return EINVAL; ic->ic_txpowlimit = ireq->i_val; error = ERESTART; break; case IEEE80211_IOC_ROAMING: if (!(IEEE80211_ROAMING_DEVICE <= ireq->i_val && ireq->i_val <= IEEE80211_ROAMING_MANUAL)) return EINVAL; vap->iv_roaming = (enum ieee80211_roamingmode)ireq->i_val; /* XXXX reset? */ break; case IEEE80211_IOC_PRIVACY: if (ireq->i_val) { /* XXX check for key state? */ vap->iv_flags |= IEEE80211_F_PRIVACY; } else vap->iv_flags &= ~IEEE80211_F_PRIVACY; /* XXX ERESTART? */ break; case IEEE80211_IOC_DROPUNENCRYPTED: if (ireq->i_val) vap->iv_flags |= IEEE80211_F_DROPUNENC; else vap->iv_flags &= ~IEEE80211_F_DROPUNENC; /* XXX ERESTART? */ break; case IEEE80211_IOC_WPAKEY: error = ieee80211_ioctl_setkey(vap, ireq); break; case IEEE80211_IOC_DELKEY: error = ieee80211_ioctl_delkey(vap, ireq); break; case IEEE80211_IOC_MLME: error = ieee80211_ioctl_setmlme(vap, ireq); break; case IEEE80211_IOC_COUNTERMEASURES: if (ireq->i_val) { if ((vap->iv_flags & IEEE80211_F_WPA) == 0) return EOPNOTSUPP; vap->iv_flags |= IEEE80211_F_COUNTERM; } else vap->iv_flags &= ~IEEE80211_F_COUNTERM; /* XXX ERESTART? */ break; case IEEE80211_IOC_WPA: if (ireq->i_val > 3) return EINVAL; /* XXX verify ciphers available */ flags = vap->iv_flags & ~IEEE80211_F_WPA; switch (ireq->i_val) { case 0: /* wpa_supplicant calls this to clear the WPA config */ break; case 1: if (!(vap->iv_caps & IEEE80211_C_WPA1)) return EOPNOTSUPP; flags |= IEEE80211_F_WPA1; break; case 2: if (!(vap->iv_caps & IEEE80211_C_WPA2)) return EOPNOTSUPP; flags |= IEEE80211_F_WPA2; break; case 3: if ((vap->iv_caps & IEEE80211_C_WPA) != IEEE80211_C_WPA) return EOPNOTSUPP; flags |= IEEE80211_F_WPA1 | IEEE80211_F_WPA2; break; default: /* Can't set any -> error */ return EOPNOTSUPP; } vap->iv_flags = flags; error = ERESTART; /* NB: can change beacon frame */ break; case IEEE80211_IOC_WME: if (ireq->i_val) { if ((vap->iv_caps & IEEE80211_C_WME) == 0) return EOPNOTSUPP; ieee80211_syncflag(vap, IEEE80211_F_WME); } else ieee80211_syncflag(vap, -IEEE80211_F_WME); error = ERESTART; /* NB: can change beacon frame */ break; case IEEE80211_IOC_HIDESSID: if (ireq->i_val) vap->iv_flags |= IEEE80211_F_HIDESSID; else vap->iv_flags &= ~IEEE80211_F_HIDESSID; error = ERESTART; /* XXX ENETRESET? */ break; case IEEE80211_IOC_APBRIDGE: if (ireq->i_val == 0) vap->iv_flags |= IEEE80211_F_NOBRIDGE; else vap->iv_flags &= ~IEEE80211_F_NOBRIDGE; break; case IEEE80211_IOC_BSSID: if (ireq->i_len != sizeof(tmpbssid)) return EINVAL; error = copyin(ireq->i_data, tmpbssid, ireq->i_len); if (error) break; IEEE80211_ADDR_COPY(vap->iv_des_bssid, tmpbssid); if (IEEE80211_ADDR_EQ(vap->iv_des_bssid, zerobssid)) vap->iv_flags &= ~IEEE80211_F_DESBSSID; else vap->iv_flags |= IEEE80211_F_DESBSSID; error = ENETRESET; break; case IEEE80211_IOC_CHANLIST: error = ieee80211_ioctl_setchanlist(vap, ireq); break; #define OLD_IEEE80211_IOC_SCAN_REQ 23 #ifdef OLD_IEEE80211_IOC_SCAN_REQ case OLD_IEEE80211_IOC_SCAN_REQ: IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: active scan request\n", __func__); /* * If we are in INIT state then the driver has never * had a chance to setup hardware state to do a scan; * use the state machine to get us up the SCAN state. * Otherwise just invoke the scan machinery to start * a one-time scan. */ if (vap->iv_state == IEEE80211_S_INIT) ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); else (void) ieee80211_start_scan(vap, IEEE80211_SCAN_ACTIVE | IEEE80211_SCAN_NOPICK | IEEE80211_SCAN_ONCE, IEEE80211_SCAN_FOREVER, 0, 0, /* XXX use ioctl params */ vap->iv_des_nssid, vap->iv_des_ssid); break; #endif /* OLD_IEEE80211_IOC_SCAN_REQ */ case IEEE80211_IOC_SCAN_REQ: error = ieee80211_ioctl_scanreq(vap, ireq); break; case IEEE80211_IOC_SCAN_CANCEL: IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: cancel scan\n", __func__); ieee80211_cancel_scan(vap); break; case IEEE80211_IOC_HTCONF: if (ireq->i_val & 1) ieee80211_syncflag_ht(vap, IEEE80211_FHT_HT); else ieee80211_syncflag_ht(vap, -IEEE80211_FHT_HT); if (ireq->i_val & 2) ieee80211_syncflag_ht(vap, IEEE80211_FHT_USEHT40); else ieee80211_syncflag_ht(vap, -IEEE80211_FHT_USEHT40); error = ENETRESET; break; case IEEE80211_IOC_ADDMAC: case IEEE80211_IOC_DELMAC: error = ieee80211_ioctl_macmac(vap, ireq); break; case IEEE80211_IOC_MACCMD: error = ieee80211_ioctl_setmaccmd(vap, ireq); break; case IEEE80211_IOC_STA_STATS: error = ieee80211_ioctl_setstastats(vap, ireq); break; case IEEE80211_IOC_STA_TXPOW: error = ieee80211_ioctl_setstatxpow(vap, ireq); break; case IEEE80211_IOC_WME_CWMIN: /* WME: CWmin */ case IEEE80211_IOC_WME_CWMAX: /* WME: CWmax */ case IEEE80211_IOC_WME_AIFS: /* WME: AIFS */ case IEEE80211_IOC_WME_TXOPLIMIT: /* WME: txops limit */ case IEEE80211_IOC_WME_ACM: /* WME: ACM (bss only) */ case IEEE80211_IOC_WME_ACKPOLICY: /* WME: ACK policy (!bss only) */ error = ieee80211_ioctl_setwmeparam(vap, ireq); break; case IEEE80211_IOC_DTIM_PERIOD: if (vap->iv_opmode != IEEE80211_M_HOSTAP && vap->iv_opmode != IEEE80211_M_MBSS && vap->iv_opmode != IEEE80211_M_IBSS) return EINVAL; if (IEEE80211_DTIM_MIN <= ireq->i_val && ireq->i_val <= IEEE80211_DTIM_MAX) { vap->iv_dtim_period = ireq->i_val; error = ENETRESET; /* requires restart */ } else error = EINVAL; break; case IEEE80211_IOC_BEACON_INTERVAL: if (vap->iv_opmode != IEEE80211_M_HOSTAP && vap->iv_opmode != IEEE80211_M_MBSS && vap->iv_opmode != IEEE80211_M_IBSS) return EINVAL; if (IEEE80211_BINTVAL_MIN <= ireq->i_val && ireq->i_val <= IEEE80211_BINTVAL_MAX) { ic->ic_bintval = ireq->i_val; error = ENETRESET; /* requires restart */ } else error = EINVAL; break; case IEEE80211_IOC_PUREG: if (ireq->i_val) vap->iv_flags |= IEEE80211_F_PUREG; else vap->iv_flags &= ~IEEE80211_F_PUREG; /* NB: reset only if we're operating on an 11g channel */ if (isvap11g(vap)) error = ENETRESET; break; case IEEE80211_IOC_QUIET: vap->iv_quiet= ireq->i_val; break; case IEEE80211_IOC_QUIET_COUNT: vap->iv_quiet_count=ireq->i_val; break; case IEEE80211_IOC_QUIET_PERIOD: vap->iv_quiet_period=ireq->i_val; break; case IEEE80211_IOC_QUIET_OFFSET: vap->iv_quiet_offset=ireq->i_val; break; case IEEE80211_IOC_QUIET_DUR: if(ireq->i_val < vap->iv_bss->ni_intval) vap->iv_quiet_duration = ireq->i_val; else error = EINVAL; break; case IEEE80211_IOC_BGSCAN: if (ireq->i_val) { if ((vap->iv_caps & IEEE80211_C_BGSCAN) == 0) return EOPNOTSUPP; vap->iv_flags |= IEEE80211_F_BGSCAN; } else vap->iv_flags &= ~IEEE80211_F_BGSCAN; break; case IEEE80211_IOC_BGSCAN_IDLE: if (ireq->i_val >= IEEE80211_BGSCAN_IDLE_MIN) vap->iv_bgscanidle = ireq->i_val*hz/1000; else error = EINVAL; break; case IEEE80211_IOC_BGSCAN_INTERVAL: if (ireq->i_val >= IEEE80211_BGSCAN_INTVAL_MIN) vap->iv_bgscanintvl = ireq->i_val*hz; else error = EINVAL; break; case IEEE80211_IOC_SCANVALID: if (ireq->i_val >= IEEE80211_SCAN_VALID_MIN) vap->iv_scanvalid = ireq->i_val*hz; else error = EINVAL; break; case IEEE80211_IOC_FRAGTHRESHOLD: if ((vap->iv_caps & IEEE80211_C_TXFRAG) == 0 && ireq->i_val != IEEE80211_FRAG_MAX) return EOPNOTSUPP; if (!(IEEE80211_FRAG_MIN <= ireq->i_val && ireq->i_val <= IEEE80211_FRAG_MAX)) return EINVAL; vap->iv_fragthreshold = ireq->i_val; error = ERESTART; break; case IEEE80211_IOC_BURST: if (ireq->i_val) { if ((vap->iv_caps & IEEE80211_C_BURST) == 0) return EOPNOTSUPP; ieee80211_syncflag(vap, IEEE80211_F_BURST); } else ieee80211_syncflag(vap, -IEEE80211_F_BURST); error = ERESTART; break; case IEEE80211_IOC_BMISSTHRESHOLD: if (!(IEEE80211_HWBMISS_MIN <= ireq->i_val && ireq->i_val <= IEEE80211_HWBMISS_MAX)) return EINVAL; vap->iv_bmissthreshold = ireq->i_val; error = ERESTART; break; case IEEE80211_IOC_CURCHAN: error = ieee80211_ioctl_setcurchan(vap, ireq); break; case IEEE80211_IOC_SHORTGI: if (ireq->i_val) { #define IEEE80211_HTCAP_SHORTGI \ (IEEE80211_HTCAP_SHORTGI20 | IEEE80211_HTCAP_SHORTGI40) if (((ireq->i_val ^ vap->iv_htcaps) & IEEE80211_HTCAP_SHORTGI) != 0) return EINVAL; if (ireq->i_val & IEEE80211_HTCAP_SHORTGI20) vap->iv_flags_ht |= IEEE80211_FHT_SHORTGI20; if (ireq->i_val & IEEE80211_HTCAP_SHORTGI40) vap->iv_flags_ht |= IEEE80211_FHT_SHORTGI40; #undef IEEE80211_HTCAP_SHORTGI } else vap->iv_flags_ht &= ~(IEEE80211_FHT_SHORTGI20 | IEEE80211_FHT_SHORTGI40); error = ERESTART; break; case IEEE80211_IOC_AMPDU: if (ireq->i_val && (vap->iv_htcaps & IEEE80211_HTC_AMPDU) == 0) return EINVAL; if (ireq->i_val & 1) vap->iv_flags_ht |= IEEE80211_FHT_AMPDU_TX; else vap->iv_flags_ht &= ~IEEE80211_FHT_AMPDU_TX; if (ireq->i_val & 2) vap->iv_flags_ht |= IEEE80211_FHT_AMPDU_RX; else vap->iv_flags_ht &= ~IEEE80211_FHT_AMPDU_RX; /* NB: reset only if we're operating on an 11n channel */ if (isvapht(vap)) error = ERESTART; break; case IEEE80211_IOC_AMPDU_LIMIT: /* XXX TODO: figure out ampdu_limit versus ampdu_rxmax */ if (!(IEEE80211_HTCAP_MAXRXAMPDU_8K <= ireq->i_val && ireq->i_val <= IEEE80211_HTCAP_MAXRXAMPDU_64K)) return EINVAL; if (vap->iv_opmode == IEEE80211_M_HOSTAP) vap->iv_ampdu_rxmax = ireq->i_val; else vap->iv_ampdu_limit = ireq->i_val; error = ERESTART; break; case IEEE80211_IOC_AMPDU_DENSITY: if (!(IEEE80211_HTCAP_MPDUDENSITY_NA <= ireq->i_val && ireq->i_val <= IEEE80211_HTCAP_MPDUDENSITY_16)) return EINVAL; vap->iv_ampdu_density = ireq->i_val; error = ERESTART; break; case IEEE80211_IOC_AMSDU: if (ireq->i_val && (vap->iv_htcaps & IEEE80211_HTC_AMSDU) == 0) return EINVAL; if (ireq->i_val & 1) vap->iv_flags_ht |= IEEE80211_FHT_AMSDU_TX; else vap->iv_flags_ht &= ~IEEE80211_FHT_AMSDU_TX; if (ireq->i_val & 2) vap->iv_flags_ht |= IEEE80211_FHT_AMSDU_RX; else vap->iv_flags_ht &= ~IEEE80211_FHT_AMSDU_RX; /* NB: reset only if we're operating on an 11n channel */ if (isvapht(vap)) error = ERESTART; break; case IEEE80211_IOC_AMSDU_LIMIT: /* XXX validate */ vap->iv_amsdu_limit = ireq->i_val; /* XXX truncation? */ break; case IEEE80211_IOC_PUREN: if (ireq->i_val) { if ((vap->iv_flags_ht & IEEE80211_FHT_HT) == 0) return EINVAL; vap->iv_flags_ht |= IEEE80211_FHT_PUREN; } else vap->iv_flags_ht &= ~IEEE80211_FHT_PUREN; /* NB: reset only if we're operating on an 11n channel */ if (isvapht(vap)) error = ERESTART; break; case IEEE80211_IOC_DOTH: if (ireq->i_val) { #if 0 /* XXX no capability */ if ((vap->iv_caps & IEEE80211_C_DOTH) == 0) return EOPNOTSUPP; #endif vap->iv_flags |= IEEE80211_F_DOTH; } else vap->iv_flags &= ~IEEE80211_F_DOTH; error = ENETRESET; break; case IEEE80211_IOC_REGDOMAIN: error = ieee80211_ioctl_setregdomain(vap, ireq); break; case IEEE80211_IOC_ROAM: error = ieee80211_ioctl_setroam(vap, ireq); break; case IEEE80211_IOC_TXPARAMS: error = ieee80211_ioctl_settxparams(vap, ireq); break; case IEEE80211_IOC_HTCOMPAT: if (ireq->i_val) { if ((vap->iv_flags_ht & IEEE80211_FHT_HT) == 0) return EOPNOTSUPP; vap->iv_flags_ht |= IEEE80211_FHT_HTCOMPAT; } else vap->iv_flags_ht &= ~IEEE80211_FHT_HTCOMPAT; /* NB: reset only if we're operating on an 11n channel */ if (isvapht(vap)) error = ERESTART; break; case IEEE80211_IOC_DWDS: if (ireq->i_val) { /* NB: DWDS only makes sense for WDS-capable devices */ if ((ic->ic_caps & IEEE80211_C_WDS) == 0) return EOPNOTSUPP; /* NB: DWDS is used only with ap+sta vaps */ if (vap->iv_opmode != IEEE80211_M_HOSTAP && vap->iv_opmode != IEEE80211_M_STA) return EINVAL; vap->iv_flags |= IEEE80211_F_DWDS; if (vap->iv_opmode == IEEE80211_M_STA) vap->iv_flags_ext |= IEEE80211_FEXT_4ADDR; } else { vap->iv_flags &= ~IEEE80211_F_DWDS; if (vap->iv_opmode == IEEE80211_M_STA) vap->iv_flags_ext &= ~IEEE80211_FEXT_4ADDR; } break; case IEEE80211_IOC_INACTIVITY: if (ireq->i_val) vap->iv_flags_ext |= IEEE80211_FEXT_INACT; else vap->iv_flags_ext &= ~IEEE80211_FEXT_INACT; break; case IEEE80211_IOC_APPIE: error = ieee80211_ioctl_setappie(vap, ireq); break; case IEEE80211_IOC_WPS: if (ireq->i_val) { if ((vap->iv_caps & IEEE80211_C_WPA) == 0) return EOPNOTSUPP; vap->iv_flags_ext |= IEEE80211_FEXT_WPS; } else vap->iv_flags_ext &= ~IEEE80211_FEXT_WPS; break; case IEEE80211_IOC_TSN: if (ireq->i_val) { if ((vap->iv_caps & IEEE80211_C_WPA) == 0) return EOPNOTSUPP; vap->iv_flags_ext |= IEEE80211_FEXT_TSN; } else vap->iv_flags_ext &= ~IEEE80211_FEXT_TSN; break; case IEEE80211_IOC_CHANSWITCH: error = ieee80211_ioctl_chanswitch(vap, ireq); break; case IEEE80211_IOC_DFS: if (ireq->i_val) { if ((vap->iv_caps & IEEE80211_C_DFS) == 0) return EOPNOTSUPP; /* NB: DFS requires 11h support */ if ((vap->iv_flags & IEEE80211_F_DOTH) == 0) return EINVAL; vap->iv_flags_ext |= IEEE80211_FEXT_DFS; } else vap->iv_flags_ext &= ~IEEE80211_FEXT_DFS; break; case IEEE80211_IOC_DOTD: if (ireq->i_val) vap->iv_flags_ext |= IEEE80211_FEXT_DOTD; else vap->iv_flags_ext &= ~IEEE80211_FEXT_DOTD; if (vap->iv_opmode == IEEE80211_M_STA) error = ENETRESET; break; case IEEE80211_IOC_HTPROTMODE: if (ireq->i_val > IEEE80211_PROT_RTSCTS) return EINVAL; vap->iv_htprotmode = ireq->i_val ? IEEE80211_PROT_RTSCTS : IEEE80211_PROT_NONE; /* NB: if not operating in 11n this can wait */ if (isvapht(vap)) error = ERESTART; /* Notify driver layer of HT protmode changes */ ieee80211_vap_update_ht_protmode(vap); break; case IEEE80211_IOC_STA_VLAN: error = ieee80211_ioctl_setstavlan(vap, ireq); break; case IEEE80211_IOC_SMPS: if ((ireq->i_val &~ IEEE80211_HTCAP_SMPS) != 0 || ireq->i_val == 0x0008) /* value of 2 is reserved */ return EINVAL; if (ireq->i_val != IEEE80211_HTCAP_SMPS_OFF && (vap->iv_htcaps & IEEE80211_HTC_SMPS) == 0) return EOPNOTSUPP; vap->iv_htcaps = (vap->iv_htcaps &~ IEEE80211_HTCAP_SMPS) | ireq->i_val; /* NB: if not operating in 11n this can wait */ if (isvapht(vap)) error = ERESTART; break; case IEEE80211_IOC_RIFS: if (ireq->i_val != 0) { if ((vap->iv_htcaps & IEEE80211_HTC_RIFS) == 0) return EOPNOTSUPP; vap->iv_flags_ht |= IEEE80211_FHT_RIFS; } else vap->iv_flags_ht &= ~IEEE80211_FHT_RIFS; /* NB: if not operating in 11n this can wait */ if (isvapht(vap)) error = ERESTART; break; case IEEE80211_IOC_STBC: /* Check if we can do STBC TX/RX before changing the setting */ if ((ireq->i_val & 1) && ((vap->iv_htcaps & IEEE80211_HTCAP_TXSTBC) == 0)) return EOPNOTSUPP; if ((ireq->i_val & 2) && ((vap->iv_htcaps & IEEE80211_HTCAP_RXSTBC) == 0)) return EOPNOTSUPP; /* TX */ if (ireq->i_val & 1) vap->iv_flags_ht |= IEEE80211_FHT_STBC_TX; else vap->iv_flags_ht &= ~IEEE80211_FHT_STBC_TX; /* RX */ if (ireq->i_val & 2) vap->iv_flags_ht |= IEEE80211_FHT_STBC_RX; else vap->iv_flags_ht &= ~IEEE80211_FHT_STBC_RX; /* NB: reset only if we're operating on an 11n channel */ if (isvapht(vap)) error = ERESTART; break; case IEEE80211_IOC_LDPC: /* Check if we can do LDPC TX/RX before changing the setting */ if ((ireq->i_val & 1) && (vap->iv_htcaps & IEEE80211_HTC_TXLDPC) == 0) return EOPNOTSUPP; if ((ireq->i_val & 2) && (vap->iv_htcaps & IEEE80211_HTCAP_LDPC) == 0) return EOPNOTSUPP; /* TX */ if (ireq->i_val & 1) vap->iv_flags_ht |= IEEE80211_FHT_LDPC_TX; else vap->iv_flags_ht &= ~IEEE80211_FHT_LDPC_TX; /* RX */ if (ireq->i_val & 2) vap->iv_flags_ht |= IEEE80211_FHT_LDPC_RX; else vap->iv_flags_ht &= ~IEEE80211_FHT_LDPC_RX; /* NB: reset only if we're operating on an 11n channel */ if (isvapht(vap)) error = ERESTART; break; case IEEE80211_IOC_UAPSD: if ((vap->iv_caps & IEEE80211_C_UAPSD) == 0) return EOPNOTSUPP; if (ireq->i_val == 0) vap->iv_flags_ext &= ~IEEE80211_FEXT_UAPSD; else if (ireq->i_val == 1) vap->iv_flags_ext |= IEEE80211_FEXT_UAPSD; else return EINVAL; break; /* VHT */ case IEEE80211_IOC_VHTCONF: if (ireq->i_val & IEEE80211_FVHT_VHT) ieee80211_syncflag_vht(vap, IEEE80211_FVHT_VHT); else ieee80211_syncflag_vht(vap, -IEEE80211_FVHT_VHT); if (ireq->i_val & IEEE80211_FVHT_USEVHT40) ieee80211_syncflag_vht(vap, IEEE80211_FVHT_USEVHT40); else ieee80211_syncflag_vht(vap, -IEEE80211_FVHT_USEVHT40); if (ireq->i_val & IEEE80211_FVHT_USEVHT80) ieee80211_syncflag_vht(vap, IEEE80211_FVHT_USEVHT80); else ieee80211_syncflag_vht(vap, -IEEE80211_FVHT_USEVHT80); if (ireq->i_val & IEEE80211_FVHT_USEVHT160) ieee80211_syncflag_vht(vap, IEEE80211_FVHT_USEVHT160); else ieee80211_syncflag_vht(vap, -IEEE80211_FVHT_USEVHT160); if (ireq->i_val & IEEE80211_FVHT_USEVHT80P80) ieee80211_syncflag_vht(vap, IEEE80211_FVHT_USEVHT80P80); else ieee80211_syncflag_vht(vap, -IEEE80211_FVHT_USEVHT80P80); /* Check if we can do STBC TX/RX before changing the setting. */ if ((ireq->i_val & IEEE80211_FVHT_STBC_TX) && ((vap->iv_vht_cap.vht_cap_info & IEEE80211_VHTCAP_TXSTBC) == 0)) return EOPNOTSUPP; if ((ireq->i_val & IEEE80211_FVHT_STBC_RX) && ((vap->iv_vht_cap.vht_cap_info & IEEE80211_VHTCAP_RXSTBC_MASK) == 0)) return EOPNOTSUPP; /* TX */ if (ireq->i_val & IEEE80211_FVHT_STBC_TX) ieee80211_syncflag_vht(vap, IEEE80211_FVHT_STBC_TX); else ieee80211_syncflag_vht(vap, -IEEE80211_FVHT_STBC_TX); /* RX */ if (ireq->i_val & IEEE80211_FVHT_STBC_RX) ieee80211_syncflag_vht(vap, IEEE80211_FVHT_STBC_RX); else ieee80211_syncflag_vht(vap, -IEEE80211_FVHT_STBC_RX); error = ENETRESET; break; default: error = ieee80211_ioctl_setdefault(vap, ireq); break; } /* * The convention is that ENETRESET means an operation * requires a complete re-initialization of the device (e.g. * changing something that affects the association state). * ERESTART means the request may be handled with only a * reload of the hardware state. We hand ERESTART requests * to the iv_reset callback so the driver can decide. If * a device does not fillin iv_reset then it defaults to one * that returns ENETRESET. Otherwise a driver may return * ENETRESET (in which case a full reset will be done) or * 0 to mean there's no need to do anything (e.g. when the * change has no effect on the driver/device). */ if (error == ERESTART) error = IFNET_IS_UP_RUNNING(vap->iv_ifp) ? vap->iv_reset(vap, ireq->i_type) : 0; if (error == ENETRESET) { /* XXX need to re-think AUTO handling */ if (IS_UP_AUTO(vap)) ieee80211_init(vap); error = 0; } return error; } int ieee80211_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct ieee80211vap *vap = ifp->if_softc; struct ieee80211com *ic = vap->iv_ic; int error = 0, wait = 0, ic_used; struct ifreq *ifr; struct ifaddr *ifa; /* XXX */ ic_used = (cmd != SIOCSIFMTU && cmd != SIOCG80211STATS); if (ic_used && (error = ieee80211_com_vincref(vap)) != 0) return (error); switch (cmd) { case SIOCSIFFLAGS: IEEE80211_LOCK(ic); if ((ifp->if_flags ^ vap->iv_ifflags) & IFF_PROMISC) { /* * Enable promiscuous mode when: * 1. Interface is not a member of bridge, or * 2. Requested by user, or * 3. In monitor (or adhoc-demo) mode. */ if (ifp->if_bridge == NULL || (ifp->if_flags & IFF_PPROMISC) != 0 || vap->iv_opmode == IEEE80211_M_MONITOR || (vap->iv_opmode == IEEE80211_M_AHDEMO && (vap->iv_caps & IEEE80211_C_TDMA) == 0)) { ieee80211_promisc(vap, ifp->if_flags & IFF_PROMISC); vap->iv_ifflags ^= IFF_PROMISC; } } if ((ifp->if_flags ^ vap->iv_ifflags) & IFF_ALLMULTI) { ieee80211_allmulti(vap, ifp->if_flags & IFF_ALLMULTI); vap->iv_ifflags ^= IFF_ALLMULTI; } if (ifp->if_flags & IFF_UP) { /* * Bring ourself up unless we're already operational. * If we're the first vap and the parent is not up * then it will automatically be brought up as a * side-effect of bringing ourself up. */ if (vap->iv_state == IEEE80211_S_INIT) { if (ic->ic_nrunning == 0) wait = 1; ieee80211_start_locked(vap); } } else if (ieee80211_vap_ifp_check_is_running(vap)) { /* * Stop ourself. If we are the last vap to be * marked down the parent will also be taken down. */ if (ic->ic_nrunning == 1) wait = 1; ieee80211_stop_locked(vap); } IEEE80211_UNLOCK(ic); /* Wait for parent ioctl handler if it was queued */ if (wait) { ieee80211_waitfor_parent(ic); ieee80211_vap_sync_mac_address(vap); } break; case SIOCADDMULTI: case SIOCDELMULTI: ieee80211_runtask(ic, &ic->ic_mcast_task); break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: ifr = (struct ifreq *)data; error = ifmedia_ioctl(ifp, ifr, &vap->iv_media, cmd); break; case SIOCG80211: error = ieee80211_ioctl_get80211(vap, cmd, (struct ieee80211req *) data); break; case SIOCS80211: error = ieee80211_priv_check_vap_manage(cmd, vap, ifp); if (error == 0) error = ieee80211_ioctl_set80211(vap, cmd, (struct ieee80211req *) data); break; case SIOCG80211STATS: ifr = (struct ifreq *)data; error = copyout(&vap->iv_stats, ifr_data_get_ptr(ifr), sizeof(vap->iv_stats)); break; case SIOCSIFMTU: ifr = (struct ifreq *)data; if (!(IEEE80211_MTU_MIN <= ifr->ifr_mtu && ifr->ifr_mtu <= IEEE80211_MTU_MAX)) error = EINVAL; else ifp->if_mtu = ifr->ifr_mtu; break; case SIOCSIFADDR: /* * XXX Handle this directly so we can suppress if_init calls. * XXX This should be done in ether_ioctl but for the moment * XXX there are too many other parts of the system that * XXX set IFF_UP and so suppress if_init being called when * XXX it should be. */ ifa = (struct ifaddr *) data; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: if ((ifp->if_flags & IFF_UP) == 0) { ifp->if_flags |= IFF_UP; ifp->if_init(ifp->if_softc); } arp_ifinit(ifp, ifa); break; #endif default: if ((ifp->if_flags & IFF_UP) == 0) { ifp->if_flags |= IFF_UP; ifp->if_init(ifp->if_softc); } break; } break; case SIOCSIFLLADDR: error = ieee80211_priv_check_vap_setmac(cmd, vap, ifp); if (error == 0) break; /* Fallthrough */ default: /* * Pass unknown ioctls first to the driver, and if it * returns ENOTTY, then to the generic Ethernet handler. */ if (ic->ic_ioctl != NULL && (error = ic->ic_ioctl(ic, cmd, data)) != ENOTTY) break; error = ether_ioctl(ifp, cmd, data); break; } if (ic_used) ieee80211_com_vdecref(vap); return (error); } diff --git a/sys/net80211/ieee80211_mesh.c b/sys/net80211/ieee80211_mesh.c index dac023af1e41..4a9cd5e66a97 100644 --- a/sys/net80211/ieee80211_mesh.c +++ b/sys/net80211/ieee80211_mesh.c @@ -1,3613 +1,3614 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2009 The FreeBSD Foundation * * This software was developed by Rui Paulo under sponsorship from the * FreeBSD Foundation. * * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ /* * IEEE 802.11s Mesh Point (MBSS) support. * * Based on March 2009, D3.0 802.11s draft spec. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #include #include static void mesh_rt_flush_invalid(struct ieee80211vap *); static int mesh_select_proto_path(struct ieee80211vap *, const char *); static int mesh_select_proto_metric(struct ieee80211vap *, const char *); static void mesh_vattach(struct ieee80211vap *); static int mesh_newstate(struct ieee80211vap *, enum ieee80211_state, int); static void mesh_rt_cleanup_cb(void *); static void mesh_gatemode_setup(struct ieee80211vap *); static void mesh_gatemode_cb(void *); static void mesh_linkchange(struct ieee80211_node *, enum ieee80211_mesh_mlstate); static void mesh_checkid(void *, struct ieee80211_node *); static uint32_t mesh_generateid(struct ieee80211vap *); static int mesh_checkpseq(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN], uint32_t); static void mesh_transmit_to_gate(struct ieee80211vap *, struct mbuf *, struct ieee80211_mesh_route *); static void mesh_forward(struct ieee80211vap *, struct mbuf *, const struct ieee80211_meshcntl *); static int mesh_input(struct ieee80211_node *, struct mbuf *, const struct ieee80211_rx_stats *rxs, int, int); static void mesh_recv_mgmt(struct ieee80211_node *, struct mbuf *, int, const struct ieee80211_rx_stats *rxs, int, int); static void mesh_recv_ctl(struct ieee80211_node *, struct mbuf *, int); static void mesh_peer_timeout_setup(struct ieee80211_node *); static void mesh_peer_timeout_backoff(struct ieee80211_node *); static void mesh_peer_timeout_cb(void *); static __inline void mesh_peer_timeout_stop(struct ieee80211_node *); static int mesh_verify_meshid(struct ieee80211vap *, const uint8_t *); static int mesh_verify_meshconf(struct ieee80211vap *, const uint8_t *); static int mesh_verify_meshpeer(struct ieee80211vap *, uint8_t, const uint8_t *); uint32_t mesh_airtime_calc(struct ieee80211_node *); /* * Timeout values come from the specification and are in milliseconds. */ static SYSCTL_NODE(_net_wlan, OID_AUTO, mesh, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "IEEE 802.11s parameters"); static int ieee80211_mesh_gateint = -1; SYSCTL_PROC(_net_wlan_mesh, OID_AUTO, gateint, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &ieee80211_mesh_gateint, 0, ieee80211_sysctl_msecs_ticks, "I", "mesh gate interval (ms)"); static int ieee80211_mesh_retrytimeout = -1; SYSCTL_PROC(_net_wlan_mesh, OID_AUTO, retrytimeout, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &ieee80211_mesh_retrytimeout, 0, ieee80211_sysctl_msecs_ticks, "I", "Retry timeout (msec)"); static int ieee80211_mesh_holdingtimeout = -1; SYSCTL_PROC(_net_wlan_mesh, OID_AUTO, holdingtimeout, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &ieee80211_mesh_holdingtimeout, 0, ieee80211_sysctl_msecs_ticks, "I", "Holding state timeout (msec)"); static int ieee80211_mesh_confirmtimeout = -1; SYSCTL_PROC(_net_wlan_mesh, OID_AUTO, confirmtimeout, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &ieee80211_mesh_confirmtimeout, 0, ieee80211_sysctl_msecs_ticks, "I", "Confirm state timeout (msec)"); static int ieee80211_mesh_backofftimeout = -1; SYSCTL_PROC(_net_wlan_mesh, OID_AUTO, backofftimeout, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &ieee80211_mesh_backofftimeout, 0, ieee80211_sysctl_msecs_ticks, "I", "Backoff timeout (msec). This is to throutles peering forever when " "not receiving answer or is rejected by a neighbor"); static int ieee80211_mesh_maxretries = 2; SYSCTL_INT(_net_wlan_mesh, OID_AUTO, maxretries, CTLFLAG_RW, &ieee80211_mesh_maxretries, 0, "Maximum retries during peer link establishment"); static int ieee80211_mesh_maxholding = 2; SYSCTL_INT(_net_wlan_mesh, OID_AUTO, maxholding, CTLFLAG_RW, &ieee80211_mesh_maxholding, 0, "Maximum times we are allowed to transition to HOLDING state before " "backinoff during peer link establishment"); static const uint8_t broadcastaddr[IEEE80211_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; static ieee80211_recv_action_func mesh_recv_action_meshpeering_open; static ieee80211_recv_action_func mesh_recv_action_meshpeering_confirm; static ieee80211_recv_action_func mesh_recv_action_meshpeering_close; static ieee80211_recv_action_func mesh_recv_action_meshlmetric; static ieee80211_recv_action_func mesh_recv_action_meshgate; static ieee80211_send_action_func mesh_send_action_meshpeering_open; static ieee80211_send_action_func mesh_send_action_meshpeering_confirm; static ieee80211_send_action_func mesh_send_action_meshpeering_close; static ieee80211_send_action_func mesh_send_action_meshlmetric; static ieee80211_send_action_func mesh_send_action_meshgate; static const struct ieee80211_mesh_proto_metric mesh_metric_airtime = { .mpm_descr = "AIRTIME", .mpm_ie = IEEE80211_MESHCONF_METRIC_AIRTIME, .mpm_metric = mesh_airtime_calc, }; static struct ieee80211_mesh_proto_path mesh_proto_paths[4]; static struct ieee80211_mesh_proto_metric mesh_proto_metrics[4]; MALLOC_DEFINE(M_80211_MESH_PREQ, "80211preq", "802.11 MESH Path Request frame"); MALLOC_DEFINE(M_80211_MESH_PREP, "80211prep", "802.11 MESH Path Reply frame"); MALLOC_DEFINE(M_80211_MESH_PERR, "80211perr", "802.11 MESH Path Error frame"); /* The longer one of the lifetime should be stored as new lifetime */ #define MESH_ROUTE_LIFETIME_MAX(a, b) (a > b ? a : b) MALLOC_DEFINE(M_80211_MESH_RT, "80211mesh_rt", "802.11s routing table"); MALLOC_DEFINE(M_80211_MESH_GT_RT, "80211mesh_gt", "802.11s known gates table"); /* * Helper functions to manipulate the Mesh routing table. */ static struct ieee80211_mesh_route * mesh_rt_find_locked(struct ieee80211_mesh_state *ms, const uint8_t dest[IEEE80211_ADDR_LEN]) { struct ieee80211_mesh_route *rt; MESH_RT_LOCK_ASSERT(ms); TAILQ_FOREACH(rt, &ms->ms_routes, rt_next) { if (IEEE80211_ADDR_EQ(dest, rt->rt_dest)) return rt; } return NULL; } static struct ieee80211_mesh_route * mesh_rt_add_locked(struct ieee80211vap *vap, const uint8_t dest[IEEE80211_ADDR_LEN]) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rt; KASSERT(!IEEE80211_ADDR_EQ(broadcastaddr, dest), ("%s: adding broadcast to the routing table", __func__)); MESH_RT_LOCK_ASSERT(ms); rt = IEEE80211_MALLOC(ALIGN(sizeof(struct ieee80211_mesh_route)) + ms->ms_ppath->mpp_privlen, M_80211_MESH_RT, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (rt != NULL) { rt->rt_vap = vap; IEEE80211_ADDR_COPY(rt->rt_dest, dest); rt->rt_priv = (void *)ALIGN(&rt[1]); MESH_RT_ENTRY_LOCK_INIT(rt, "MBSS_RT"); callout_init(&rt->rt_discovery, 1); rt->rt_updtime = ticks; /* create time */ TAILQ_INSERT_TAIL(&ms->ms_routes, rt, rt_next); } return rt; } struct ieee80211_mesh_route * ieee80211_mesh_rt_find(struct ieee80211vap *vap, const uint8_t dest[IEEE80211_ADDR_LEN]) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rt; MESH_RT_LOCK(ms); rt = mesh_rt_find_locked(ms, dest); MESH_RT_UNLOCK(ms); return rt; } struct ieee80211_mesh_route * ieee80211_mesh_rt_add(struct ieee80211vap *vap, const uint8_t dest[IEEE80211_ADDR_LEN]) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rt; KASSERT(ieee80211_mesh_rt_find(vap, dest) == NULL, ("%s: duplicate entry in the routing table", __func__)); KASSERT(!IEEE80211_ADDR_EQ(vap->iv_myaddr, dest), ("%s: adding self to the routing table", __func__)); MESH_RT_LOCK(ms); rt = mesh_rt_add_locked(vap, dest); MESH_RT_UNLOCK(ms); return rt; } /* * Update the route lifetime and returns the updated lifetime. * If new_lifetime is zero and route is timedout it will be invalidated. * new_lifetime is in msec */ int ieee80211_mesh_rt_update(struct ieee80211_mesh_route *rt, int new_lifetime) { int timesince, now; uint32_t lifetime = 0; KASSERT(rt != NULL, ("route is NULL")); now = ticks; MESH_RT_ENTRY_LOCK(rt); /* dont clobber a proxy entry gated by us */ if (rt->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY && rt->rt_nhops == 0) { MESH_RT_ENTRY_UNLOCK(rt); return rt->rt_lifetime; } timesince = ticks_to_msecs(now - rt->rt_updtime); rt->rt_updtime = now; if (timesince >= rt->rt_lifetime) { if (new_lifetime != 0) { rt->rt_lifetime = new_lifetime; } else { rt->rt_flags &= ~IEEE80211_MESHRT_FLAGS_VALID; rt->rt_lifetime = 0; } } else { /* update what is left of lifetime */ rt->rt_lifetime = rt->rt_lifetime - timesince; rt->rt_lifetime = MESH_ROUTE_LIFETIME_MAX( new_lifetime, rt->rt_lifetime); } lifetime = rt->rt_lifetime; MESH_RT_ENTRY_UNLOCK(rt); return lifetime; } /* * Add a proxy route (as needed) for the specified destination. */ void ieee80211_mesh_proxy_check(struct ieee80211vap *vap, const uint8_t dest[IEEE80211_ADDR_LEN]) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rt; MESH_RT_LOCK(ms); rt = mesh_rt_find_locked(ms, dest); if (rt == NULL) { rt = mesh_rt_add_locked(vap, dest); if (rt == NULL) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, dest, "%s", "unable to add proxy entry"); vap->iv_stats.is_mesh_rtaddfailed++; } else { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, dest, "%s", "add proxy entry"); IEEE80211_ADDR_COPY(rt->rt_mesh_gate, vap->iv_myaddr); IEEE80211_ADDR_COPY(rt->rt_nexthop, vap->iv_myaddr); rt->rt_flags |= IEEE80211_MESHRT_FLAGS_VALID | IEEE80211_MESHRT_FLAGS_PROXY; } } else if ((rt->rt_flags & IEEE80211_MESHRT_FLAGS_VALID) == 0) { KASSERT(rt->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY, ("no proxy flag for poxy entry")); struct ieee80211com *ic = vap->iv_ic; /* * Fix existing entry created by received frames from * stations that have some memory of dest. We also * flush any frames held on the staging queue; delivering * them is too much trouble right now. */ IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, dest, "%s", "fix proxy entry"); IEEE80211_ADDR_COPY(rt->rt_nexthop, vap->iv_myaddr); rt->rt_flags |= IEEE80211_MESHRT_FLAGS_VALID | IEEE80211_MESHRT_FLAGS_PROXY; /* XXX belongs in hwmp */ ieee80211_ageq_drain_node(&ic->ic_stageq, (void *)(uintptr_t) ieee80211_mac_hash(ic, dest)); /* XXX stat? */ } MESH_RT_UNLOCK(ms); } static __inline void mesh_rt_del(struct ieee80211_mesh_state *ms, struct ieee80211_mesh_route *rt) { TAILQ_REMOVE(&ms->ms_routes, rt, rt_next); /* * Grab the lock before destroying it, to be sure no one else * is holding the route. */ MESH_RT_ENTRY_LOCK(rt); callout_drain(&rt->rt_discovery); MESH_RT_ENTRY_LOCK_DESTROY(rt); IEEE80211_FREE(rt, M_80211_MESH_RT); } void ieee80211_mesh_rt_del(struct ieee80211vap *vap, const uint8_t dest[IEEE80211_ADDR_LEN]) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rt, *next; MESH_RT_LOCK(ms); TAILQ_FOREACH_SAFE(rt, &ms->ms_routes, rt_next, next) { if (IEEE80211_ADDR_EQ(rt->rt_dest, dest)) { if (rt->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY) { ms->ms_ppath->mpp_senderror(vap, dest, rt, IEEE80211_REASON_MESH_PERR_NO_PROXY); } else { ms->ms_ppath->mpp_senderror(vap, dest, rt, IEEE80211_REASON_MESH_PERR_DEST_UNREACH); } mesh_rt_del(ms, rt); MESH_RT_UNLOCK(ms); return; } } MESH_RT_UNLOCK(ms); } void ieee80211_mesh_rt_flush(struct ieee80211vap *vap) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rt, *next; if (ms == NULL) return; MESH_RT_LOCK(ms); TAILQ_FOREACH_SAFE(rt, &ms->ms_routes, rt_next, next) mesh_rt_del(ms, rt); MESH_RT_UNLOCK(ms); } void ieee80211_mesh_rt_flush_peer(struct ieee80211vap *vap, const uint8_t peer[IEEE80211_ADDR_LEN]) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rt, *next; MESH_RT_LOCK(ms); TAILQ_FOREACH_SAFE(rt, &ms->ms_routes, rt_next, next) { if (IEEE80211_ADDR_EQ(rt->rt_nexthop, peer)) mesh_rt_del(ms, rt); } MESH_RT_UNLOCK(ms); } /* * Flush expired routing entries, i.e. those in invalid state for * some time. */ static void mesh_rt_flush_invalid(struct ieee80211vap *vap) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rt, *next; if (ms == NULL) return; MESH_RT_LOCK(ms); TAILQ_FOREACH_SAFE(rt, &ms->ms_routes, rt_next, next) { /* Discover paths will be deleted by their own callout */ if (rt->rt_flags & IEEE80211_MESHRT_FLAGS_DISCOVER) continue; ieee80211_mesh_rt_update(rt, 0); if ((rt->rt_flags & IEEE80211_MESHRT_FLAGS_VALID) == 0) mesh_rt_del(ms, rt); } MESH_RT_UNLOCK(ms); } int ieee80211_mesh_register_proto_path(const struct ieee80211_mesh_proto_path *mpp) { int i, firstempty = -1; for (i = 0; i < nitems(mesh_proto_paths); i++) { if (strncmp(mpp->mpp_descr, mesh_proto_paths[i].mpp_descr, IEEE80211_MESH_PROTO_DSZ) == 0) return EEXIST; if (!mesh_proto_paths[i].mpp_active && firstempty == -1) firstempty = i; } if (firstempty < 0) return ENOSPC; memcpy(&mesh_proto_paths[firstempty], mpp, sizeof(*mpp)); mesh_proto_paths[firstempty].mpp_active = 1; return 0; } int ieee80211_mesh_register_proto_metric(const struct ieee80211_mesh_proto_metric *mpm) { int i, firstempty = -1; for (i = 0; i < nitems(mesh_proto_metrics); i++) { if (strncmp(mpm->mpm_descr, mesh_proto_metrics[i].mpm_descr, IEEE80211_MESH_PROTO_DSZ) == 0) return EEXIST; if (!mesh_proto_metrics[i].mpm_active && firstempty == -1) firstempty = i; } if (firstempty < 0) return ENOSPC; memcpy(&mesh_proto_metrics[firstempty], mpm, sizeof(*mpm)); mesh_proto_metrics[firstempty].mpm_active = 1; return 0; } static int mesh_select_proto_path(struct ieee80211vap *vap, const char *name) { struct ieee80211_mesh_state *ms = vap->iv_mesh; int i; for (i = 0; i < nitems(mesh_proto_paths); i++) { if (strcasecmp(mesh_proto_paths[i].mpp_descr, name) == 0) { ms->ms_ppath = &mesh_proto_paths[i]; return 0; } } return ENOENT; } static int mesh_select_proto_metric(struct ieee80211vap *vap, const char *name) { struct ieee80211_mesh_state *ms = vap->iv_mesh; int i; for (i = 0; i < nitems(mesh_proto_metrics); i++) { if (strcasecmp(mesh_proto_metrics[i].mpm_descr, name) == 0) { ms->ms_pmetric = &mesh_proto_metrics[i]; return 0; } } return ENOENT; } static void mesh_gatemode_setup(struct ieee80211vap *vap) { struct ieee80211_mesh_state *ms = vap->iv_mesh; /* * NB: When a mesh gate is running as a ROOT it shall * not send out periodic GANNs but instead mark the * mesh gate flag for the corresponding proactive PREQ * and RANN frames. */ if (ms->ms_flags & IEEE80211_MESHFLAGS_ROOT || (ms->ms_flags & IEEE80211_MESHFLAGS_GATE) == 0) { callout_drain(&ms->ms_gatetimer); return ; } callout_reset(&ms->ms_gatetimer, ieee80211_mesh_gateint, mesh_gatemode_cb, vap); } static void mesh_gatemode_cb(void *arg) { struct ieee80211vap *vap = (struct ieee80211vap *)arg; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_meshgann_ie gann; gann.gann_flags = 0; /* Reserved */ gann.gann_hopcount = 0; gann.gann_ttl = ms->ms_ttl; IEEE80211_ADDR_COPY(gann.gann_addr, vap->iv_myaddr); gann.gann_seq = ms->ms_gateseq++; gann.gann_interval = ieee80211_mesh_gateint; IEEE80211_NOTE(vap, IEEE80211_MSG_MESH, vap->iv_bss, "send broadcast GANN (seq %u)", gann.gann_seq); ieee80211_send_action(vap->iv_bss, IEEE80211_ACTION_CAT_MESH, IEEE80211_ACTION_MESH_GANN, &gann); mesh_gatemode_setup(vap); } static void ieee80211_mesh_init(void) { memset(mesh_proto_paths, 0, sizeof(mesh_proto_paths)); memset(mesh_proto_metrics, 0, sizeof(mesh_proto_metrics)); /* * Setup mesh parameters that depends on the clock frequency. */ ieee80211_mesh_gateint = msecs_to_ticks(10000); ieee80211_mesh_retrytimeout = msecs_to_ticks(40); ieee80211_mesh_holdingtimeout = msecs_to_ticks(40); ieee80211_mesh_confirmtimeout = msecs_to_ticks(40); ieee80211_mesh_backofftimeout = msecs_to_ticks(5000); /* * Register action frame handlers. */ ieee80211_recv_action_register(IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_OPEN, mesh_recv_action_meshpeering_open); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CONFIRM, mesh_recv_action_meshpeering_confirm); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, mesh_recv_action_meshpeering_close); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_MESH, IEEE80211_ACTION_MESH_LMETRIC, mesh_recv_action_meshlmetric); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_MESH, IEEE80211_ACTION_MESH_GANN, mesh_recv_action_meshgate); ieee80211_send_action_register(IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_OPEN, mesh_send_action_meshpeering_open); ieee80211_send_action_register(IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CONFIRM, mesh_send_action_meshpeering_confirm); ieee80211_send_action_register(IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, mesh_send_action_meshpeering_close); ieee80211_send_action_register(IEEE80211_ACTION_CAT_MESH, IEEE80211_ACTION_MESH_LMETRIC, mesh_send_action_meshlmetric); ieee80211_send_action_register(IEEE80211_ACTION_CAT_MESH, IEEE80211_ACTION_MESH_GANN, mesh_send_action_meshgate); /* * Register Airtime Link Metric. */ ieee80211_mesh_register_proto_metric(&mesh_metric_airtime); } SYSINIT(wlan_mesh, SI_SUB_DRIVERS, SI_ORDER_FIRST, ieee80211_mesh_init, NULL); void ieee80211_mesh_attach(struct ieee80211com *ic) { ic->ic_vattach[IEEE80211_M_MBSS] = mesh_vattach; } void ieee80211_mesh_detach(struct ieee80211com *ic) { } static void mesh_vdetach_peers(void *arg, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; uint16_t args[3]; if (ni->ni_mlstate == IEEE80211_NODE_MESH_ESTABLISHED) { args[0] = ni->ni_mlpid; args[1] = ni->ni_mllid; args[2] = IEEE80211_REASON_PEER_LINK_CANCELED; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, args); } callout_drain(&ni->ni_mltimer); /* XXX belongs in hwmp */ ieee80211_ageq_drain_node(&ic->ic_stageq, (void *)(uintptr_t) ieee80211_mac_hash(ic, ni->ni_macaddr)); } static void mesh_vdetach(struct ieee80211vap *vap) { struct ieee80211_mesh_state *ms = vap->iv_mesh; callout_drain(&ms->ms_cleantimer); ieee80211_iterate_nodes(&vap->iv_ic->ic_sta, mesh_vdetach_peers, NULL); ieee80211_mesh_rt_flush(vap); MESH_RT_LOCK_DESTROY(ms); ms->ms_ppath->mpp_vdetach(vap); IEEE80211_FREE(vap->iv_mesh, M_80211_VAP); vap->iv_mesh = NULL; } static void mesh_vattach(struct ieee80211vap *vap) { struct ieee80211_mesh_state *ms; vap->iv_newstate = mesh_newstate; vap->iv_input = mesh_input; vap->iv_opdetach = mesh_vdetach; vap->iv_recv_mgmt = mesh_recv_mgmt; vap->iv_recv_ctl = mesh_recv_ctl; ms = IEEE80211_MALLOC(sizeof(struct ieee80211_mesh_state), M_80211_VAP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (ms == NULL) { - printf("%s: couldn't alloc MBSS state\n", __func__); + net80211_vap_printf(vap, "%s: couldn't alloc MBSS state\n", + __func__); return; } vap->iv_mesh = ms; ms->ms_seq = 0; ms->ms_flags = (IEEE80211_MESHFLAGS_AP | IEEE80211_MESHFLAGS_FWD); ms->ms_ttl = IEEE80211_MESH_DEFAULT_TTL; TAILQ_INIT(&ms->ms_known_gates); TAILQ_INIT(&ms->ms_routes); MESH_RT_LOCK_INIT(ms, "MBSS"); callout_init(&ms->ms_cleantimer, 1); callout_init(&ms->ms_gatetimer, 1); ms->ms_gateseq = 0; mesh_select_proto_metric(vap, "AIRTIME"); KASSERT(ms->ms_pmetric, ("ms_pmetric == NULL")); mesh_select_proto_path(vap, "HWMP"); KASSERT(ms->ms_ppath, ("ms_ppath == NULL")); ms->ms_ppath->mpp_vattach(vap); } /* * IEEE80211_M_MBSS vap state machine handler. */ static int mesh_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; enum ieee80211_state ostate; IEEE80211_LOCK_ASSERT(ic); ostate = vap->iv_state; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (%d)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg); vap->iv_state = nstate; /* state transition */ if (ostate != IEEE80211_S_SCAN) ieee80211_cancel_scan(vap); /* background scan */ ni = vap->iv_bss; /* NB: no reference held */ if (nstate != IEEE80211_S_RUN && ostate == IEEE80211_S_RUN) { callout_drain(&ms->ms_cleantimer); callout_drain(&ms->ms_gatetimer); } switch (nstate) { case IEEE80211_S_INIT: switch (ostate) { case IEEE80211_S_SCAN: ieee80211_cancel_scan(vap); break; case IEEE80211_S_CAC: ieee80211_dfs_cac_stop(vap); break; case IEEE80211_S_RUN: ieee80211_iterate_nodes(&ic->ic_sta, mesh_vdetach_peers, NULL); break; default: break; } if (ostate != IEEE80211_S_INIT) { /* NB: optimize INIT -> INIT case */ ieee80211_reset_bss(vap); ieee80211_mesh_rt_flush(vap); } break; case IEEE80211_S_SCAN: switch (ostate) { case IEEE80211_S_INIT: if (vap->iv_des_chan != IEEE80211_CHAN_ANYC && !IEEE80211_IS_CHAN_RADAR(vap->iv_des_chan) && ms->ms_idlen != 0) { /* * Already have a channel and a mesh ID; bypass * the scan and startup immediately. */ ieee80211_create_ibss(vap, vap->iv_des_chan); break; } /* * Initiate a scan. We can come here as a result * of an IEEE80211_IOC_SCAN_REQ too in which case * the vap will be marked with IEEE80211_FEXT_SCANREQ * and the scan request parameters will be present * in iv_scanreq. Otherwise we do the default. */ if (vap->iv_flags_ext & IEEE80211_FEXT_SCANREQ) { ieee80211_check_scan(vap, vap->iv_scanreq_flags, vap->iv_scanreq_duration, vap->iv_scanreq_mindwell, vap->iv_scanreq_maxdwell, vap->iv_scanreq_nssid, vap->iv_scanreq_ssid); vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANREQ; } else ieee80211_check_scan_current(vap); break; default: break; } break; case IEEE80211_S_CAC: /* * Start CAC on a DFS channel. We come here when starting * a bss on a DFS channel (see ieee80211_create_ibss). */ ieee80211_dfs_cac_start(vap); break; case IEEE80211_S_RUN: switch (ostate) { case IEEE80211_S_INIT: /* * Already have a channel; bypass the * scan and startup immediately. * Note that ieee80211_create_ibss will call * back to do a RUN->RUN state change. */ ieee80211_create_ibss(vap, ieee80211_ht_adjust_channel(ic, ic->ic_curchan, vap->iv_flags_ht)); /* NB: iv_bss is changed on return */ break; case IEEE80211_S_CAC: /* * NB: This is the normal state change when CAC * expires and no radar was detected; no need to * clear the CAC timer as it's already expired. */ /* fall thru... */ case IEEE80211_S_CSA: #if 0 /* * Shorten inactivity timer of associated stations * to weed out sta's that don't follow a CSA. */ ieee80211_iterate_nodes(&ic->ic_sta, sta_csa, vap); #endif /* * Update bss node channel to reflect where * we landed after CSA. */ ieee80211_node_set_chan(ni, ieee80211_ht_adjust_channel(ic, ic->ic_curchan, ieee80211_htchanflags(ni->ni_chan))); /* XXX bypass debug msgs */ break; case IEEE80211_S_SCAN: case IEEE80211_S_RUN: #ifdef IEEE80211_DEBUG if (ieee80211_msg_debug(vap)) { ieee80211_note(vap, "synchronized with %s meshid ", ether_sprintf(ni->ni_meshid)); ieee80211_print_essid(ni->ni_meshid, ni->ni_meshidlen); /* XXX MCS/HT */ - printf(" channel %d\n", + net80211_printf(" channel %d\n", ieee80211_chan2ieee(ic, ic->ic_curchan)); } #endif break; default: break; } ieee80211_node_authorize(ni); callout_reset(&ms->ms_cleantimer, ms->ms_ppath->mpp_inact, mesh_rt_cleanup_cb, vap); mesh_gatemode_setup(vap); break; default: break; } /* NB: ostate not nstate */ ms->ms_ppath->mpp_newstate(vap, ostate, arg); return 0; } static void mesh_rt_cleanup_cb(void *arg) { struct ieee80211vap *vap = arg; struct ieee80211_mesh_state *ms = vap->iv_mesh; mesh_rt_flush_invalid(vap); callout_reset(&ms->ms_cleantimer, ms->ms_ppath->mpp_inact, mesh_rt_cleanup_cb, vap); } /* * Mark a mesh STA as gate and return a pointer to it. * If this is first time, we create a new gate route. * Always update the path route to this mesh gate. */ struct ieee80211_mesh_gate_route * ieee80211_mesh_mark_gate(struct ieee80211vap *vap, const uint8_t *addr, struct ieee80211_mesh_route *rt) { struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_gate_route *gr = NULL, *next; int found = 0; MESH_RT_LOCK(ms); TAILQ_FOREACH_SAFE(gr, &ms->ms_known_gates, gr_next, next) { if (IEEE80211_ADDR_EQ(gr->gr_addr, addr)) { found = 1; break; } } if (!found) { /* New mesh gate add it to known table. */ IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, addr, "%s", "stored new gate information from pro-PREQ."); gr = IEEE80211_MALLOC(ALIGN(sizeof(struct ieee80211_mesh_gate_route)), M_80211_MESH_GT_RT, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); IEEE80211_ADDR_COPY(gr->gr_addr, addr); TAILQ_INSERT_TAIL(&ms->ms_known_gates, gr, gr_next); } gr->gr_route = rt; /* TODO: link from path route to gate route */ MESH_RT_UNLOCK(ms); return gr; } /* * Helper function to note the Mesh Peer Link FSM change. */ static void mesh_linkchange(struct ieee80211_node *ni, enum ieee80211_mesh_mlstate state) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_mesh_state *ms = vap->iv_mesh; #ifdef IEEE80211_DEBUG static const char *meshlinkstates[] = { [IEEE80211_NODE_MESH_IDLE] = "IDLE", [IEEE80211_NODE_MESH_OPENSNT] = "OPEN SENT", [IEEE80211_NODE_MESH_OPENRCV] = "OPEN RECEIVED", [IEEE80211_NODE_MESH_CONFIRMRCV] = "CONFIRM RECEIVED", [IEEE80211_NODE_MESH_ESTABLISHED] = "ESTABLISHED", [IEEE80211_NODE_MESH_HOLDING] = "HOLDING" }; #endif IEEE80211_NOTE(vap, IEEE80211_MSG_MESH, ni, "peer link: %s -> %s", meshlinkstates[ni->ni_mlstate], meshlinkstates[state]); /* track neighbor count */ if (state == IEEE80211_NODE_MESH_ESTABLISHED && ni->ni_mlstate != IEEE80211_NODE_MESH_ESTABLISHED) { KASSERT(ms->ms_neighbors < 65535, ("neighbor count overflow")); ms->ms_neighbors++; ieee80211_beacon_notify(vap, IEEE80211_BEACON_MESHCONF); } else if (ni->ni_mlstate == IEEE80211_NODE_MESH_ESTABLISHED && state != IEEE80211_NODE_MESH_ESTABLISHED) { KASSERT(ms->ms_neighbors > 0, ("neighbor count 0")); ms->ms_neighbors--; ieee80211_beacon_notify(vap, IEEE80211_BEACON_MESHCONF); } ni->ni_mlstate = state; switch (state) { case IEEE80211_NODE_MESH_HOLDING: ms->ms_ppath->mpp_peerdown(ni); break; case IEEE80211_NODE_MESH_ESTABLISHED: ieee80211_mesh_discover(vap, ni->ni_macaddr, NULL); break; default: break; } } /* * Helper function to generate a unique local ID required for mesh * peer establishment. */ static void mesh_checkid(void *arg, struct ieee80211_node *ni) { uint16_t *r = arg; if (*r == ni->ni_mllid) *(uint16_t *)arg = 0; } static uint32_t mesh_generateid(struct ieee80211vap *vap) { int maxiter = 4; uint16_t r; do { net80211_get_random_bytes(&r, 2); ieee80211_iterate_nodes(&vap->iv_ic->ic_sta, mesh_checkid, &r); maxiter--; } while (r == 0 && maxiter > 0); return r; } /* * Verifies if we already received this packet by checking its * sequence number. * Returns 0 if the frame is to be accepted, 1 otherwise. */ static int mesh_checkpseq(struct ieee80211vap *vap, const uint8_t source[IEEE80211_ADDR_LEN], uint32_t seq) { struct ieee80211_mesh_route *rt; rt = ieee80211_mesh_rt_find(vap, source); if (rt == NULL) { rt = ieee80211_mesh_rt_add(vap, source); if (rt == NULL) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, source, "%s", "add mcast route failed"); vap->iv_stats.is_mesh_rtaddfailed++; return 1; } IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, source, "add mcast route, mesh seqno %d", seq); rt->rt_lastmseq = seq; return 0; } if (IEEE80211_MESH_SEQ_GEQ(rt->rt_lastmseq, seq)) { return 1; } else { rt->rt_lastmseq = seq; return 0; } } /* * Iterate the routing table and locate the next hop. */ struct ieee80211_node * ieee80211_mesh_find_txnode(struct ieee80211vap *vap, const uint8_t dest[IEEE80211_ADDR_LEN]) { struct ieee80211_mesh_route *rt; rt = ieee80211_mesh_rt_find(vap, dest); if (rt == NULL) return NULL; if ((rt->rt_flags & IEEE80211_MESHRT_FLAGS_VALID) == 0) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, dest, "%s: !valid, flags 0x%x", __func__, rt->rt_flags); /* XXX stat */ return NULL; } if (rt->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY) { rt = ieee80211_mesh_rt_find(vap, rt->rt_mesh_gate); if (rt == NULL) return NULL; if ((rt->rt_flags & IEEE80211_MESHRT_FLAGS_VALID) == 0) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, dest, "%s: meshgate !valid, flags 0x%x", __func__, rt->rt_flags); /* XXX stat */ return NULL; } } return ieee80211_find_txnode(vap, rt->rt_nexthop); } static void mesh_transmit_to_gate(struct ieee80211vap *vap, struct mbuf *m, struct ieee80211_mesh_route *rt_gate) { struct ifnet *ifp = vap->iv_ifp; struct ieee80211_node *ni; IEEE80211_TX_UNLOCK_ASSERT(vap->iv_ic); ni = ieee80211_mesh_find_txnode(vap, rt_gate->rt_dest); if (ni == NULL) { if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(m); return; } /* * Send through the VAP packet transmit path. * This consumes the node ref grabbed above and * the mbuf, regardless of whether there's a problem * or not. */ (void) ieee80211_vap_pkt_send_dest(vap, m, ni); } /* * Forward the queued frames to known valid mesh gates. * Assume destination to be outside the MBSS (i.e. proxy entry), * If no valid mesh gates are known silently discard queued frames. * After transmitting frames to all known valid mesh gates, this route * will be marked invalid, and a new path discovery will happen in the hopes * that (at least) one of the mesh gates have a new proxy entry for us to use. */ void ieee80211_mesh_forward_to_gates(struct ieee80211vap *vap, struct ieee80211_mesh_route *rt_dest) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rt_gate; struct ieee80211_mesh_gate_route *gr = NULL, *gr_next; struct mbuf *m, *mcopy, *next; IEEE80211_TX_UNLOCK_ASSERT(ic); KASSERT( rt_dest->rt_flags == IEEE80211_MESHRT_FLAGS_DISCOVER, ("Route is not marked with IEEE80211_MESHRT_FLAGS_DISCOVER")); /* XXX: send to more than one valid mash gate */ MESH_RT_LOCK(ms); m = ieee80211_ageq_remove(&ic->ic_stageq, (struct ieee80211_node *)(uintptr_t) ieee80211_mac_hash(ic, rt_dest->rt_dest)); TAILQ_FOREACH_SAFE(gr, &ms->ms_known_gates, gr_next, gr_next) { rt_gate = gr->gr_route; if (rt_gate == NULL) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_HWMP, rt_dest->rt_dest, "mesh gate with no path %6D", gr->gr_addr, ":"); continue; } if ((rt_gate->rt_flags & IEEE80211_MESHRT_FLAGS_VALID) == 0) continue; KASSERT(rt_gate->rt_flags & IEEE80211_MESHRT_FLAGS_GATE, ("route not marked as a mesh gate")); KASSERT((rt_gate->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY) == 0, ("found mesh gate that is also marked porxy")); /* * convert route to a proxy route gated by the current * mesh gate, this is needed so encap can built data * frame with correct address. */ rt_dest->rt_flags = IEEE80211_MESHRT_FLAGS_PROXY | IEEE80211_MESHRT_FLAGS_VALID; rt_dest->rt_ext_seq = 1; /* random value */ IEEE80211_ADDR_COPY(rt_dest->rt_mesh_gate, rt_gate->rt_dest); IEEE80211_ADDR_COPY(rt_dest->rt_nexthop, rt_gate->rt_nexthop); rt_dest->rt_metric = rt_gate->rt_metric; rt_dest->rt_nhops = rt_gate->rt_nhops; ieee80211_mesh_rt_update(rt_dest, ms->ms_ppath->mpp_inact); MESH_RT_UNLOCK(ms); /* XXX: lock?? */ mcopy = m_dup(m, IEEE80211_M_NOWAIT); for (; mcopy != NULL; mcopy = next) { next = mcopy->m_nextpkt; mcopy->m_nextpkt = NULL; IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_HWMP, rt_dest->rt_dest, "flush queued frame %p len %d", mcopy, mcopy->m_pkthdr.len); mesh_transmit_to_gate(vap, mcopy, rt_gate); } MESH_RT_LOCK(ms); } rt_dest->rt_flags = 0; /* Mark invalid */ m_freem(m); MESH_RT_UNLOCK(ms); } /* * Forward the specified frame. * Decrement the TTL and set TA to our MAC address. */ static void mesh_forward(struct ieee80211vap *vap, struct mbuf *m, const struct ieee80211_meshcntl *mc) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ifnet *ifp = vap->iv_ifp; const struct ieee80211_frame *wh = mtod(m, const struct ieee80211_frame *); struct mbuf *mcopy; struct ieee80211_meshcntl *mccopy; struct ieee80211_frame *whcopy; struct ieee80211_node *ni; int err; /* This is called from the RX path - don't hold this lock */ IEEE80211_TX_UNLOCK_ASSERT(ic); /* * mesh ttl of 1 means we are the last one receiving it, * according to amendment we decrement and then check if * 0, if so we dont forward. */ if (mc->mc_ttl < 1) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_MESH, wh, "%s", "frame not fwd'd, ttl 1"); vap->iv_stats.is_mesh_fwd_ttl++; return; } if (!(ms->ms_flags & IEEE80211_MESHFLAGS_FWD)) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_MESH, wh, "%s", "frame not fwd'd, fwding disabled"); vap->iv_stats.is_mesh_fwd_disabled++; return; } mcopy = m_dup(m, IEEE80211_M_NOWAIT); if (mcopy == NULL) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_MESH, wh, "%s", "frame not fwd'd, cannot dup"); vap->iv_stats.is_mesh_fwd_nobuf++; if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return; } mcopy = m_pullup(mcopy, ieee80211_hdrspace(ic, wh) + sizeof(struct ieee80211_meshcntl)); if (mcopy == NULL) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_MESH, wh, "%s", "frame not fwd'd, too short"); vap->iv_stats.is_mesh_fwd_tooshort++; if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(mcopy); return; } whcopy = mtod(mcopy, struct ieee80211_frame *); mccopy = (struct ieee80211_meshcntl *) (mtod(mcopy, uint8_t *) + ieee80211_hdrspace(ic, wh)); /* XXX clear other bits? */ whcopy->i_fc[1] &= ~IEEE80211_FC1_RETRY; IEEE80211_ADDR_COPY(whcopy->i_addr2, vap->iv_myaddr); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { ni = ieee80211_ref_node(vap->iv_bss); mcopy->m_flags |= M_MCAST; } else { ni = ieee80211_mesh_find_txnode(vap, whcopy->i_addr3); if (ni == NULL) { /* * [Optional] any of the following three actions: * o silently discard * o trigger a path discovery * o inform TA that meshDA is unknown. */ IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_MESH, wh, "%s", "frame not fwd'd, no path"); ms->ms_ppath->mpp_senderror(vap, whcopy->i_addr3, NULL, IEEE80211_REASON_MESH_PERR_NO_FI); vap->iv_stats.is_mesh_fwd_nopath++; m_freem(mcopy); return; } IEEE80211_ADDR_COPY(whcopy->i_addr1, ni->ni_macaddr); } KASSERT(mccopy->mc_ttl > 0, ("%s called with wrong ttl", __func__)); mccopy->mc_ttl--; /* XXX calculate priority so drivers can find the tx queue */ M_WME_SETAC(mcopy, WME_AC_BE); /* XXX do we know m_nextpkt is NULL? */ MPASS((mcopy->m_pkthdr.csum_flags & CSUM_SND_TAG) == 0); mcopy->m_pkthdr.rcvif = (void *) ni; /* * XXX this bypasses all of the VAP TX handling; it passes frames * directly to the parent interface. * * Because of this, there's no TX lock being held as there's no * encaps state being used. * * Doing a direct parent transmit may not be the correct thing * to do here; we'll have to re-think this soon. */ IEEE80211_TX_LOCK(ic); err = ieee80211_parent_xmitpkt(ic, mcopy); IEEE80211_TX_UNLOCK(ic); if (!err) if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); } static struct mbuf * mesh_decap(struct ieee80211vap *vap, struct mbuf *m, int hdrlen, int meshdrlen) { #define WHDIR(wh) ((wh)->i_fc[1] & IEEE80211_FC1_DIR_MASK) #define MC01(mc) ((const struct ieee80211_meshcntl_ae01 *)mc) uint8_t b[sizeof(struct ieee80211_qosframe_addr4) + sizeof(struct ieee80211_meshcntl_ae10)]; const struct ieee80211_qosframe_addr4 *wh; const struct ieee80211_meshcntl_ae10 *mc; struct ether_header *eh; struct llc *llc; int ae; if (m->m_len < hdrlen + sizeof(*llc) && (m = m_pullup(m, hdrlen + sizeof(*llc))) == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_ANY, "discard data frame: %s", "m_pullup failed"); vap->iv_stats.is_rx_tooshort++; return NULL; } memcpy(b, mtod(m, caddr_t), hdrlen); wh = (const struct ieee80211_qosframe_addr4 *)&b[0]; mc = (const struct ieee80211_meshcntl_ae10 *)&b[hdrlen - meshdrlen]; KASSERT(WHDIR(wh) == IEEE80211_FC1_DIR_FROMDS || WHDIR(wh) == IEEE80211_FC1_DIR_DSTODS, ("bogus dir, fc 0x%x:0x%x", wh->i_fc[0], wh->i_fc[1])); llc = (struct llc *)(mtod(m, caddr_t) + hdrlen); if (llc->llc_dsap == LLC_SNAP_LSAP && llc->llc_ssap == LLC_SNAP_LSAP && llc->llc_control == LLC_UI && llc->llc_snap.org_code[0] == 0 && llc->llc_snap.org_code[1] == 0 && llc->llc_snap.org_code[2] == 0 && /* NB: preserve AppleTalk frames that have a native SNAP hdr */ !(llc->llc_snap.ether_type == htons(ETHERTYPE_AARP) || llc->llc_snap.ether_type == htons(ETHERTYPE_IPX))) { m_adj(m, hdrlen + sizeof(struct llc) - sizeof(*eh)); llc = NULL; } else { m_adj(m, hdrlen - sizeof(*eh)); } eh = mtod(m, struct ether_header *); ae = mc->mc_flags & IEEE80211_MESH_AE_MASK; if (WHDIR(wh) == IEEE80211_FC1_DIR_FROMDS) { IEEE80211_ADDR_COPY(eh->ether_dhost, wh->i_addr1); if (ae == IEEE80211_MESH_AE_00) { IEEE80211_ADDR_COPY(eh->ether_shost, wh->i_addr3); } else if (ae == IEEE80211_MESH_AE_01) { IEEE80211_ADDR_COPY(eh->ether_shost, MC01(mc)->mc_addr4); } else { IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, (const struct ieee80211_frame *)wh, NULL, "bad AE %d", ae); vap->iv_stats.is_mesh_badae++; m_freem(m); return NULL; } } else { if (ae == IEEE80211_MESH_AE_00) { IEEE80211_ADDR_COPY(eh->ether_dhost, wh->i_addr3); IEEE80211_ADDR_COPY(eh->ether_shost, wh->i_addr4); } else if (ae == IEEE80211_MESH_AE_10) { IEEE80211_ADDR_COPY(eh->ether_dhost, mc->mc_addr5); IEEE80211_ADDR_COPY(eh->ether_shost, mc->mc_addr6); } else { IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, (const struct ieee80211_frame *)wh, NULL, "bad AE %d", ae); vap->iv_stats.is_mesh_badae++; m_freem(m); return NULL; } } #ifndef __NO_STRICT_ALIGNMENT if (!ALIGNED_POINTER(mtod(m, caddr_t) + sizeof(*eh), uint32_t)) { m = ieee80211_realign(vap, m, sizeof(*eh)); if (m == NULL) return NULL; } #endif /* !__NO_STRICT_ALIGNMENT */ if (llc != NULL) { eh = mtod(m, struct ether_header *); eh->ether_type = htons(m->m_pkthdr.len - sizeof(*eh)); } return m; #undef WDIR #undef MC01 } /* * Return non-zero if the unicast mesh data frame should be processed * locally. Frames that are not proxy'd have our address, otherwise * we need to consult the routing table to look for a proxy entry. */ static __inline int mesh_isucastforme(struct ieee80211vap *vap, const struct ieee80211_frame *wh, const struct ieee80211_meshcntl *mc) { int ae = mc->mc_flags & 3; KASSERT((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) == IEEE80211_FC1_DIR_DSTODS, ("bad dir 0x%x:0x%x", wh->i_fc[0], wh->i_fc[1])); KASSERT(ae == IEEE80211_MESH_AE_00 || ae == IEEE80211_MESH_AE_10, ("bad AE %d", ae)); if (ae == IEEE80211_MESH_AE_10) { /* ucast w/ proxy */ const struct ieee80211_meshcntl_ae10 *mc10 = (const struct ieee80211_meshcntl_ae10 *) mc; struct ieee80211_mesh_route *rt = ieee80211_mesh_rt_find(vap, mc10->mc_addr5); /* check for proxy route to ourself */ return (rt != NULL && (rt->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY)); } else /* ucast w/o proxy */ return IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_myaddr); } /* * Verifies transmitter, updates lifetime, precursor list and forwards data. * > 0 means we have forwarded data and no need to process locally * == 0 means we want to process locally (and we may have forwarded data * < 0 means there was an error and data should be discarded */ static int mesh_recv_indiv_data_to_fwrd(struct ieee80211vap *vap, struct mbuf *m, struct ieee80211_frame *wh, const struct ieee80211_meshcntl *mc) { struct ieee80211_qosframe_addr4 *qwh; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rt_meshda, *rt_meshsa; /* This is called from the RX path - don't hold this lock */ IEEE80211_TX_UNLOCK_ASSERT(vap->iv_ic); qwh = (struct ieee80211_qosframe_addr4 *)wh; /* * TODO: * o verify addr2 is a legitimate transmitter * o lifetime of precursor of addr3 (addr2) is max(init, curr) * o lifetime of precursor of addr4 (nexthop) is max(init, curr) */ /* set lifetime of addr3 (meshDA) to initial value */ rt_meshda = ieee80211_mesh_rt_find(vap, qwh->i_addr3); if (rt_meshda == NULL) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, qwh->i_addr2, "no route to meshDA(%6D)", qwh->i_addr3, ":"); /* * [Optional] any of the following three actions: * o silently discard [X] * o trigger a path discovery [ ] * o inform TA that meshDA is unknown. [ ] */ /* XXX: stats */ return (-1); } ieee80211_mesh_rt_update(rt_meshda, ticks_to_msecs( ms->ms_ppath->mpp_inact)); /* set lifetime of addr4 (meshSA) to initial value */ rt_meshsa = ieee80211_mesh_rt_find(vap, qwh->i_addr4); KASSERT(rt_meshsa != NULL, ("no route")); ieee80211_mesh_rt_update(rt_meshsa, ticks_to_msecs( ms->ms_ppath->mpp_inact)); mesh_forward(vap, m, mc); return (1); /* dont process locally */ } /* * Verifies transmitter, updates lifetime, precursor list and process data * locally, if data is proxy with AE = 10 it could mean data should go * on another mesh path or data should be forwarded to the DS. * * > 0 means we have forwarded data and no need to process locally * == 0 means we want to process locally (and we may have forwarded data * < 0 means there was an error and data should be discarded */ static int mesh_recv_indiv_data_to_me(struct ieee80211vap *vap, struct mbuf *m, struct ieee80211_frame *wh, const struct ieee80211_meshcntl *mc) { struct ieee80211_qosframe_addr4 *qwh; const struct ieee80211_meshcntl_ae10 *mc10; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_route *rt; int ae; /* This is called from the RX path - don't hold this lock */ IEEE80211_TX_UNLOCK_ASSERT(vap->iv_ic); qwh = (struct ieee80211_qosframe_addr4 *)wh; mc10 = (const struct ieee80211_meshcntl_ae10 *)mc; /* * TODO: * o verify addr2 is a legitimate transmitter * o lifetime of precursor entry is max(init, curr) */ /* set lifetime of addr4 (meshSA) to initial value */ rt = ieee80211_mesh_rt_find(vap, qwh->i_addr4); KASSERT(rt != NULL, ("no route")); ieee80211_mesh_rt_update(rt, ticks_to_msecs(ms->ms_ppath->mpp_inact)); rt = NULL; ae = mc10->mc_flags & IEEE80211_MESH_AE_MASK; KASSERT(ae == IEEE80211_MESH_AE_00 || ae == IEEE80211_MESH_AE_10, ("bad AE %d", ae)); if (ae == IEEE80211_MESH_AE_10) { if (IEEE80211_ADDR_EQ(mc10->mc_addr5, qwh->i_addr3)) { return (0); /* process locally */ } rt = ieee80211_mesh_rt_find(vap, mc10->mc_addr5); if (rt != NULL && (rt->rt_flags & IEEE80211_MESHRT_FLAGS_VALID) && (rt->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY) == 0) { /* * Forward on another mesh-path, according to * amendment as specified in 9.32.4.1 */ IEEE80211_ADDR_COPY(qwh->i_addr3, mc10->mc_addr5); mesh_forward(vap, m, (const struct ieee80211_meshcntl *)mc10); return (1); /* dont process locally */ } /* * All other cases: forward of MSDUs from the MBSS to DS indiv. * addressed according to 13.11.3.2. */ IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_OUTPUT, qwh->i_addr2, "forward frame to DS, SA(%6D) DA(%6D)", mc10->mc_addr6, ":", mc10->mc_addr5, ":"); } return (0); /* process locally */ } /* * Try to forward the group addressed data on to other mesh STAs, and * also to the DS. * * > 0 means we have forwarded data and no need to process locally * == 0 means we want to process locally (and we may have forwarded data * < 0 means there was an error and data should be discarded */ static int mesh_recv_group_data(struct ieee80211vap *vap, struct mbuf *m, struct ieee80211_frame *wh, const struct ieee80211_meshcntl *mc) { #define MC01(mc) ((const struct ieee80211_meshcntl_ae01 *)mc) struct ieee80211_mesh_state *ms = vap->iv_mesh; /* This is called from the RX path - don't hold this lock */ IEEE80211_TX_UNLOCK_ASSERT(vap->iv_ic); mesh_forward(vap, m, mc); if(mc->mc_ttl > 0) { if (mc->mc_flags & IEEE80211_MESH_AE_01) { /* * Forward of MSDUs from the MBSS to DS group addressed * (according to 13.11.3.2) * This happens by delivering the packet, and a bridge * will sent it on another port member. */ if (ms->ms_flags & IEEE80211_MESHFLAGS_GATE && ms->ms_flags & IEEE80211_MESHFLAGS_FWD) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, MC01(mc)->mc_addr4, "%s", "forward from MBSS to the DS"); } } } return (0); /* process locally */ #undef MC01 } static int mesh_input(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { #define HAS_SEQ(type) ((type & 0x4) == 0) #define MC01(mc) ((const struct ieee80211_meshcntl_ae01 *)mc) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = vap->iv_ifp; struct ieee80211_frame *wh; const struct ieee80211_meshcntl *mc; int hdrspace, meshdrlen, need_tap, error; uint8_t dir, type, subtype, ae; uint32_t seq; const uint8_t *addr; uint8_t qos[2]; KASSERT(ni != NULL, ("null node")); ni->ni_inact = ni->ni_inact_reload; need_tap = 1; /* mbuf need to be tapped. */ type = -1; /* undefined */ /* This is called from the RX path - don't hold this lock */ IEEE80211_TX_UNLOCK_ASSERT(ic); 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 (!IEEE80211_IS_FC0_CHECK_VER(wh, 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) { IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; 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++; if (! ieee80211_check_rxseq(ni, wh, wh->i_addr1, rxs)) goto out; } } #ifdef IEEE80211_DEBUG /* * It's easier, but too expensive, to simulate different mesh * topologies by consulting the ACL policy very early, so do this * only under DEBUG. * * NB: this check is also done upon peering link initiation. */ if (vap->iv_acl != NULL && !vap->iv_acl->iac_check(vap, wh)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ACL, wh, NULL, "%s", "disallowed by ACL"); vap->iv_stats.is_rx_acl++; goto out; } #endif switch (type) { case IEEE80211_FC0_TYPE_DATA: if (ni == vap->iv_bss) goto out; if (ni->ni_mlstate != IEEE80211_NODE_MESH_ESTABLISHED) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_MESH, ni->ni_macaddr, NULL, "peer link not yet established (%d)", ni->ni_mlstate); vap->iv_stats.is_mesh_nolink++; goto out; } if (dir != IEEE80211_FC1_DIR_FROMDS && 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 err; } /* All Mesh data frames are QoS subtype */ if (!HAS_SEQ(type)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "incorrect subtype 0x%x", subtype); vap->iv_stats.is_rx_badsubtype++; goto err; } /* * Next up, any fragmentation. * XXX: we defrag before we even try to forward, * Mesh Control field is not present in sub-sequent * fragmented frames. This is in contrast to Draft 4.0. */ hdrspace = ieee80211_hdrspace(ic, wh); if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { m = ieee80211_defrag(ni, m, hdrspace, 0); if (m == NULL) { /* Fragment dropped or frame not complete yet */ goto out; } } wh = mtod(m, struct ieee80211_frame *); /* NB: after defrag */ /* * Now we have a complete Mesh Data frame. */ /* * Only fromDStoDS data frames use 4 address qos frames * as specified in amendment. Otherwise addr4 is located * in the Mesh Control field and a 3 address qos frame * is used. */ *(uint16_t *)qos = *(uint16_t *)ieee80211_getqos(wh); /* * NB: The mesh STA sets the Mesh Control Present * subfield to 1 in the Mesh Data frame containing * an unfragmented MSDU, an A-MSDU, or the first * fragment of an MSDU. * After defrag it should always be present. */ if (!(qos[1] & IEEE80211_QOS_MC)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_MESH, ni->ni_macaddr, NULL, "%s", "Mesh control field not present"); vap->iv_stats.is_rx_elem_missing++; /* XXX: kinda */ goto err; } /* pull up enough to get to the mesh control */ if (m->m_len < hdrspace + sizeof(struct ieee80211_meshcntl) && (m = m_pullup(m, hdrspace + sizeof(struct ieee80211_meshcntl))) == 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 */ } /* * Now calculate the full extent of the headers. Note * mesh_decap will pull up anything we didn't get * above when it strips the 802.11 headers. */ mc = (const struct ieee80211_meshcntl *) (mtod(m, const uint8_t *) + hdrspace); ae = mc->mc_flags & IEEE80211_MESH_AE_MASK; meshdrlen = sizeof(struct ieee80211_meshcntl) + ae * IEEE80211_ADDR_LEN; hdrspace += meshdrlen; /* pull complete hdrspace = ieee80211_hdrspace + meshcontrol */ if ((meshdrlen > sizeof(struct ieee80211_meshcntl)) && (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 */ } /* XXX: are we sure there is no reallocating after m_pullup? */ seq = le32dec(mc->mc_seq); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) addr = wh->i_addr3; else if (ae == IEEE80211_MESH_AE_01) addr = MC01(mc)->mc_addr4; else addr = ((struct ieee80211_qosframe_addr4 *)wh)->i_addr4; if (IEEE80211_ADDR_EQ(vap->iv_myaddr, addr)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, addr, "data", "%s", "not to me"); vap->iv_stats.is_rx_wrongbss++; /* XXX kinda */ goto out; } if (mesh_checkpseq(vap, addr, seq) != 0) { vap->iv_stats.is_rx_dup++; goto out; } /* This code "routes" the frame to the right control path */ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { if (IEEE80211_ADDR_EQ(vap->iv_myaddr, wh->i_addr3)) error = mesh_recv_indiv_data_to_me(vap, m, wh, mc); else if (IEEE80211_IS_MULTICAST(wh->i_addr3)) error = mesh_recv_group_data(vap, m, wh, mc); else error = mesh_recv_indiv_data_to_fwrd(vap, m, wh, mc); } else error = mesh_recv_group_data(vap, m, wh, mc); if (error < 0) goto err; else if (error > 0) goto out; if (ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); need_tap = 0; /* * Finally, strip the 802.11 header. */ m = mesh_decap(vap, m, hdrspace, meshdrlen); 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; } if (qos[0] & IEEE80211_QOS_AMSDU) { m = ieee80211_decap_amsdu(ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; } ieee80211_deliver_data(vap, ni, m); return type; 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, "mgt", "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) && (vap->iv_ic->ic_flags & IEEE80211_F_SCAN)) || ieee80211_msg_dumppkts(vap)) { net80211_vap_printf(vap, "received %s from %s rssi %d\n", ieee80211_mgt_subtype_name(subtype), ether_sprintf(wh->i_addr2), rssi); } #endif if (IEEE80211_IS_PROTECTED(wh)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "WEP set but not permitted"); vap->iv_stats.is_rx_mgtdiscard++; /* XXX */ goto out; } vap->iv_recv_mgmt(ni, m, subtype, rxs, rssi, nf); goto out; 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: if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); out: if (m != NULL) { if (need_tap && ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); m_freem(m); } return type; #undef HAS_SEQ #undef MC01 } static void mesh_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_channel *rxchan = ic->ic_curchan; struct ieee80211_frame *wh; struct ieee80211_mesh_route *rt; uint8_t *frm, *efrm; wh = mtod(m0, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; efrm = mtod(m0, uint8_t *) + m0->m_len; switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: case IEEE80211_FC0_SUBTYPE_BEACON: { struct ieee80211_scanparams scan; struct ieee80211_channel *c; /* * We process beacon/probe response * frames to discover neighbors. */ if (rxs != NULL) { c = ieee80211_lookup_channel_rxstatus(vap, rxs); if (c != NULL) rxchan = c; } if (ieee80211_parse_beacon(ni, m0, rxchan, &scan) != 0) return; /* * Count frame now that we know it's to be processed. */ if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) { vap->iv_stats.is_rx_beacon++; /* XXX remove */ IEEE80211_NODE_STAT(ni, rx_beacons); } else IEEE80211_NODE_STAT(ni, rx_proberesp); /* * If scanning, just pass information to the scan module. */ if (ic->ic_flags & IEEE80211_F_SCAN) { if (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN) { /* * Actively scanning a channel marked passive; * send a probe request now that we know there * is 802.11 traffic present. * * XXX check if the beacon we recv'd gives * us what we need and suppress the probe req */ ieee80211_probe_curchan(vap, true); ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN; } ieee80211_add_scan(vap, rxchan, &scan, wh, subtype, rssi, nf); return; } /* The rest of this code assumes we are running */ if (vap->iv_state != IEEE80211_S_RUN) return; /* * Ignore non-mesh STAs. */ if ((scan.capinfo & (IEEE80211_CAPINFO_ESS|IEEE80211_CAPINFO_IBSS)) || scan.meshid == NULL || scan.meshconf == NULL) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "beacon", "%s", "not a mesh sta"); vap->iv_stats.is_mesh_wrongmesh++; return; } /* * Ignore STAs for other mesh networks. */ if (memcmp(scan.meshid+2, ms->ms_id, ms->ms_idlen) != 0 || mesh_verify_meshconf(vap, scan.meshconf)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "beacon", "%s", "not for our mesh"); vap->iv_stats.is_mesh_wrongmesh++; return; } /* * Peer only based on the current ACL policy. */ if (vap->iv_acl != NULL && !vap->iv_acl->iac_check(vap, wh)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ACL, wh, NULL, "%s", "disallowed by ACL"); vap->iv_stats.is_rx_acl++; return; } /* * Do neighbor discovery. */ if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) { /* * Create a new entry in the neighbor table. */ ni = ieee80211_add_neighbor(vap, wh, &scan); } /* * Automatically peer with discovered nodes if possible. */ if (ni != vap->iv_bss && (ms->ms_flags & IEEE80211_MESHFLAGS_AP)) { switch (ni->ni_mlstate) { case IEEE80211_NODE_MESH_IDLE: { uint16_t args[1]; /* Wait for backoff callout to reset counter */ if (ni->ni_mlhcnt >= ieee80211_mesh_maxholding) return; ni->ni_mlpid = mesh_generateid(vap); if (ni->ni_mlpid == 0) return; mesh_linkchange(ni, IEEE80211_NODE_MESH_OPENSNT); args[0] = ni->ni_mlpid; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_OPEN, args); ni->ni_mlrcnt = 0; mesh_peer_timeout_setup(ni); break; } case IEEE80211_NODE_MESH_ESTABLISHED: { /* * Valid beacon from a peer mesh STA * bump TA lifetime */ rt = ieee80211_mesh_rt_find(vap, wh->i_addr2); if(rt != NULL) { ieee80211_mesh_rt_update(rt, ticks_to_msecs( ms->ms_ppath->mpp_inact)); } break; } default: break; /* ignore */ } } break; } case IEEE80211_FC0_SUBTYPE_PROBE_REQ: { uint8_t *ssid, *meshid, *rates, *xrates; if (vap->iv_state != IEEE80211_S_RUN) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "wrong state %s", ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_rx_mgtdiscard++; return; } if (IEEE80211_IS_MULTICAST(wh->i_addr2)) { /* frame must be directed */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not unicast"); vap->iv_stats.is_rx_mgtdiscard++; /* XXX stat */ return; } /* * prreq frame format * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates * [tlv] mesh id */ ssid = meshid = rates = xrates = NULL; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); 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_MESHID: meshid = frm; break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN, return); IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE, return); if (xrates != NULL) IEEE80211_VERIFY_ELEMENT(xrates, IEEE80211_RATE_MAXSIZE - rates[1], return); if (meshid != NULL) { IEEE80211_VERIFY_ELEMENT(meshid, IEEE80211_MESHID_LEN, return); /* NB: meshid, not ssid */ IEEE80211_VERIFY_SSID(vap->iv_bss, meshid, return); } /* XXX find a better class or define it's own */ IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, wh->i_addr2, "%s", "recv probe req"); /* * Some legacy 11b clients cannot hack a complete * probe response frame. When the request includes * only a bare-bones rate set, communicate this to * the transmit side. */ ieee80211_send_proberesp(vap, wh->i_addr2, 0); break; } case IEEE80211_FC0_SUBTYPE_ACTION: case IEEE80211_FC0_SUBTYPE_ACTION_NOACK: if (ni == vap->iv_bss) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "unknown node"); vap->iv_stats.is_rx_mgtdiscard++; } else if (!IEEE80211_ADDR_EQ(vap->iv_myaddr, wh->i_addr1) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not for us"); vap->iv_stats.is_rx_mgtdiscard++; } else if (vap->iv_state != IEEE80211_S_RUN) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "wrong state %s", ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_rx_mgtdiscard++; } else { if (ieee80211_parse_action(ni, m0) == 0) (void)ic->ic_recv_action(ni, wh, frm, efrm); } break; case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: case IEEE80211_FC0_SUBTYPE_TIMING_ADV: case IEEE80211_FC0_SUBTYPE_ATIM: case IEEE80211_FC0_SUBTYPE_DISASSOC: case IEEE80211_FC0_SUBTYPE_AUTH: case IEEE80211_FC0_SUBTYPE_DEAUTH: IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not handled"); vap->iv_stats.is_rx_mgtdiscard++; break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "mgt", "subtype 0x%x not handled", subtype); vap->iv_stats.is_rx_badsubtype++; break; } } static void mesh_recv_ctl(struct ieee80211_node *ni, struct mbuf *m, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_BAR: ieee80211_recv_bar(ni, m); break; } } /* * Parse meshpeering action ie's for MPM frames */ static const struct ieee80211_meshpeer_ie * mesh_parse_meshpeering_action(struct ieee80211_node *ni, const struct ieee80211_frame *wh, /* XXX for VERIFY_LENGTH */ const uint8_t *frm, const uint8_t *efrm, struct ieee80211_meshpeer_ie *mp, uint8_t subtype) { struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_meshpeer_ie *mpie; uint16_t args[3]; const uint8_t *meshid, *meshconf; uint8_t sendclose = 0; /* 1 = MPM frame rejected, close will be sent */ meshid = meshconf = NULL; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return NULL); switch (*frm) { case IEEE80211_ELEMID_MESHID: meshid = frm; break; case IEEE80211_ELEMID_MESHCONF: meshconf = frm; break; case IEEE80211_ELEMID_MESHPEER: mpie = (const struct ieee80211_meshpeer_ie *) frm; memset(mp, 0, sizeof(*mp)); mp->peer_len = mpie->peer_len; mp->peer_proto = le16dec(&mpie->peer_proto); mp->peer_llinkid = le16dec(&mpie->peer_llinkid); switch (subtype) { case IEEE80211_ACTION_MESHPEERING_CONFIRM: mp->peer_linkid = le16dec(&mpie->peer_linkid); break; case IEEE80211_ACTION_MESHPEERING_CLOSE: /* NB: peer link ID is optional */ if (mpie->peer_len == (IEEE80211_MPM_BASE_SZ + 2)) { mp->peer_linkid = 0; mp->peer_rcode = le16dec(&mpie->peer_linkid); } else { mp->peer_linkid = le16dec(&mpie->peer_linkid); mp->peer_rcode = le16dec(&mpie->peer_rcode); } break; } break; } frm += frm[1] + 2; } /* * Verify the contents of the frame. * If it fails validation, close the peer link. */ if (mesh_verify_meshpeer(vap, subtype, (const uint8_t *)mp)) { sendclose = 1; IEEE80211_DISCARD(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_MESH, wh, NULL, "%s", "MPM validation failed"); } /* If meshid is not the same reject any frames type. */ if (sendclose == 0 && mesh_verify_meshid(vap, meshid)) { sendclose = 1; IEEE80211_DISCARD(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_MESH, wh, NULL, "%s", "not for our mesh"); if (subtype == IEEE80211_ACTION_MESHPEERING_CLOSE) { /* * Standard not clear about this, if we dont ignore * there will be an endless loop between nodes sending * CLOSE frames between each other with wrong meshid. * Discard and timers will bring FSM to IDLE state. */ return NULL; } } /* * Close frames are accepted if meshid is the same. * Verify the other two types. */ if (sendclose == 0 && subtype != IEEE80211_ACTION_MESHPEERING_CLOSE && mesh_verify_meshconf(vap, meshconf)) { sendclose = 1; IEEE80211_DISCARD(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_MESH, wh, NULL, "%s", "configuration mismatch"); } if (sendclose) { vap->iv_stats.is_rx_mgtdiscard++; switch (ni->ni_mlstate) { case IEEE80211_NODE_MESH_IDLE: case IEEE80211_NODE_MESH_ESTABLISHED: case IEEE80211_NODE_MESH_HOLDING: /* ignore */ break; case IEEE80211_NODE_MESH_OPENSNT: case IEEE80211_NODE_MESH_OPENRCV: case IEEE80211_NODE_MESH_CONFIRMRCV: args[0] = ni->ni_mlpid; args[1] = ni->ni_mllid; /* Reason codes for rejection */ switch (subtype) { case IEEE80211_ACTION_MESHPEERING_OPEN: args[2] = IEEE80211_REASON_MESH_CPVIOLATION; break; case IEEE80211_ACTION_MESHPEERING_CONFIRM: args[2] = IEEE80211_REASON_MESH_INCONS_PARAMS; break; } ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, args); mesh_linkchange(ni, IEEE80211_NODE_MESH_HOLDING); mesh_peer_timeout_setup(ni); break; } return NULL; } return (const struct ieee80211_meshpeer_ie *) mp; } static int mesh_recv_action_meshpeering_open(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_meshpeer_ie ie; const struct ieee80211_meshpeer_ie *meshpeer; uint16_t args[3]; /* +2+2 for action + code + capabilites */ meshpeer = mesh_parse_meshpeering_action(ni, wh, frm+2+2, efrm, &ie, IEEE80211_ACTION_MESHPEERING_OPEN); if (meshpeer == NULL) { return 0; } /* XXX move up */ IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_MESH, ni, "recv PEER OPEN, lid 0x%x", meshpeer->peer_llinkid); switch (ni->ni_mlstate) { case IEEE80211_NODE_MESH_IDLE: /* Reject open request if reached our maximum neighbor count */ if (ms->ms_neighbors >= IEEE80211_MESH_MAX_NEIGHBORS) { args[0] = meshpeer->peer_llinkid; args[1] = 0; args[2] = IEEE80211_REASON_MESH_MAX_PEERS; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, args); /* stay in IDLE state */ return (0); } /* Open frame accepted */ mesh_linkchange(ni, IEEE80211_NODE_MESH_OPENRCV); ni->ni_mllid = meshpeer->peer_llinkid; ni->ni_mlpid = mesh_generateid(vap); if (ni->ni_mlpid == 0) return 0; /* XXX */ args[0] = ni->ni_mlpid; /* Announce we're open too... */ ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_OPEN, args); /* ...and confirm the link. */ args[0] = ni->ni_mlpid; args[1] = ni->ni_mllid; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CONFIRM, args); mesh_peer_timeout_setup(ni); break; case IEEE80211_NODE_MESH_OPENRCV: /* Wrong Link ID */ if (ni->ni_mllid != meshpeer->peer_llinkid) { args[0] = ni->ni_mllid; args[1] = ni->ni_mlpid; args[2] = IEEE80211_REASON_PEER_LINK_CANCELED; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, args); mesh_linkchange(ni, IEEE80211_NODE_MESH_HOLDING); mesh_peer_timeout_setup(ni); break; } /* Duplicate open, confirm again. */ args[0] = ni->ni_mlpid; args[1] = ni->ni_mllid; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CONFIRM, args); break; case IEEE80211_NODE_MESH_OPENSNT: ni->ni_mllid = meshpeer->peer_llinkid; mesh_linkchange(ni, IEEE80211_NODE_MESH_OPENRCV); args[0] = ni->ni_mlpid; args[1] = ni->ni_mllid; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CONFIRM, args); /* NB: don't setup/clear any timeout */ break; case IEEE80211_NODE_MESH_CONFIRMRCV: if (ni->ni_mlpid != meshpeer->peer_linkid || ni->ni_mllid != meshpeer->peer_llinkid) { args[0] = ni->ni_mlpid; args[1] = ni->ni_mllid; args[2] = IEEE80211_REASON_PEER_LINK_CANCELED; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, args); mesh_linkchange(ni, IEEE80211_NODE_MESH_HOLDING); mesh_peer_timeout_setup(ni); break; } mesh_linkchange(ni, IEEE80211_NODE_MESH_ESTABLISHED); ni->ni_mllid = meshpeer->peer_llinkid; args[0] = ni->ni_mlpid; args[1] = ni->ni_mllid; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CONFIRM, args); mesh_peer_timeout_stop(ni); break; case IEEE80211_NODE_MESH_ESTABLISHED: if (ni->ni_mllid != meshpeer->peer_llinkid) { args[0] = ni->ni_mllid; args[1] = ni->ni_mlpid; args[2] = IEEE80211_REASON_PEER_LINK_CANCELED; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, args); mesh_linkchange(ni, IEEE80211_NODE_MESH_HOLDING); mesh_peer_timeout_setup(ni); break; } args[0] = ni->ni_mlpid; args[1] = ni->ni_mllid; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CONFIRM, args); break; case IEEE80211_NODE_MESH_HOLDING: args[0] = ni->ni_mlpid; args[1] = meshpeer->peer_llinkid; /* Standard not clear about what the reaason code should be */ args[2] = IEEE80211_REASON_PEER_LINK_CANCELED; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, args); break; } return 0; } static int mesh_recv_action_meshpeering_confirm(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_meshpeer_ie ie; const struct ieee80211_meshpeer_ie *meshpeer; uint16_t args[3]; /* +2+2+2+2 for action + code + capabilites + status code + AID */ meshpeer = mesh_parse_meshpeering_action(ni, wh, frm+2+2+2+2, efrm, &ie, IEEE80211_ACTION_MESHPEERING_CONFIRM); if (meshpeer == NULL) { return 0; } IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_MESH, ni, "recv PEER CONFIRM, local id 0x%x, peer id 0x%x", meshpeer->peer_llinkid, meshpeer->peer_linkid); switch (ni->ni_mlstate) { case IEEE80211_NODE_MESH_OPENRCV: mesh_linkchange(ni, IEEE80211_NODE_MESH_ESTABLISHED); mesh_peer_timeout_stop(ni); break; case IEEE80211_NODE_MESH_OPENSNT: mesh_linkchange(ni, IEEE80211_NODE_MESH_CONFIRMRCV); mesh_peer_timeout_setup(ni); break; case IEEE80211_NODE_MESH_HOLDING: args[0] = ni->ni_mlpid; args[1] = meshpeer->peer_llinkid; /* Standard not clear about what the reaason code should be */ args[2] = IEEE80211_REASON_PEER_LINK_CANCELED; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, args); break; case IEEE80211_NODE_MESH_CONFIRMRCV: if (ni->ni_mllid != meshpeer->peer_llinkid) { args[0] = ni->ni_mlpid; args[1] = ni->ni_mllid; args[2] = IEEE80211_REASON_PEER_LINK_CANCELED; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, args); mesh_linkchange(ni, IEEE80211_NODE_MESH_HOLDING); mesh_peer_timeout_setup(ni); } break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_MESH, wh, NULL, "received confirm in invalid state %d", ni->ni_mlstate); vap->iv_stats.is_rx_mgtdiscard++; break; } return 0; } static int mesh_recv_action_meshpeering_close(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { struct ieee80211_meshpeer_ie ie; const struct ieee80211_meshpeer_ie *meshpeer; uint16_t args[3]; /* +2 for action + code */ meshpeer = mesh_parse_meshpeering_action(ni, wh, frm+2, efrm, &ie, IEEE80211_ACTION_MESHPEERING_CLOSE); if (meshpeer == NULL) { return 0; } /* * XXX: check reason code, for example we could receive * IEEE80211_REASON_MESH_MAX_PEERS then we should not attempt * to peer again. */ IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_MESH, ni, "%s", "recv PEER CLOSE"); switch (ni->ni_mlstate) { case IEEE80211_NODE_MESH_IDLE: /* ignore */ break; case IEEE80211_NODE_MESH_OPENRCV: case IEEE80211_NODE_MESH_OPENSNT: case IEEE80211_NODE_MESH_CONFIRMRCV: case IEEE80211_NODE_MESH_ESTABLISHED: args[0] = ni->ni_mlpid; args[1] = ni->ni_mllid; args[2] = IEEE80211_REASON_MESH_CLOSE_RCVD; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, args); mesh_linkchange(ni, IEEE80211_NODE_MESH_HOLDING); mesh_peer_timeout_setup(ni); break; case IEEE80211_NODE_MESH_HOLDING: mesh_linkchange(ni, IEEE80211_NODE_MESH_IDLE); mesh_peer_timeout_stop(ni); break; } return 0; } /* * Link Metric handling. */ static int mesh_recv_action_meshlmetric(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { const struct ieee80211_meshlmetric_ie *ie = (const struct ieee80211_meshlmetric_ie *) (frm+2); /* action + code */ struct ieee80211_meshlmetric_ie lm_rep; if (ie->lm_flags & IEEE80211_MESH_LMETRIC_FLAGS_REQ) { lm_rep.lm_flags = 0; lm_rep.lm_metric = mesh_airtime_calc(ni); ieee80211_send_action(ni, IEEE80211_ACTION_CAT_MESH, IEEE80211_ACTION_MESH_LMETRIC, &lm_rep); } /* XXX: else do nothing for now */ return 0; } /* * Parse meshgate action ie's for GANN frames. * Returns -1 if parsing fails, otherwise 0. */ static int mesh_parse_meshgate_action(struct ieee80211_node *ni, const struct ieee80211_frame *wh, /* XXX for VERIFY_LENGTH */ struct ieee80211_meshgann_ie *ie, const uint8_t *frm, const uint8_t *efrm) { struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_meshgann_ie *gannie; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return -1); switch (*frm) { case IEEE80211_ELEMID_MESHGANN: gannie = (const struct ieee80211_meshgann_ie *) frm; memset(ie, 0, sizeof(*ie)); ie->gann_ie = gannie->gann_ie; ie->gann_len = gannie->gann_len; ie->gann_flags = gannie->gann_flags; ie->gann_hopcount = gannie->gann_hopcount; ie->gann_ttl = gannie->gann_ttl; IEEE80211_ADDR_COPY(ie->gann_addr, gannie->gann_addr); ie->gann_seq = le32dec(&gannie->gann_seq); ie->gann_interval = le16dec(&gannie->gann_interval); break; } frm += frm[1] + 2; } return 0; } /* * Mesh Gate Announcement handling. */ static int mesh_recv_action_meshgate(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_mesh_gate_route *gr, *next; struct ieee80211_mesh_route *rt_gate; struct ieee80211_meshgann_ie pgann; struct ieee80211_meshgann_ie ie; int found = 0; /* +2 for action + code */ if (mesh_parse_meshgate_action(ni, wh, &ie, frm+2, efrm) != 0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_MESH, ni->ni_macaddr, NULL, "%s", "GANN parsing failed"); vap->iv_stats.is_rx_mgtdiscard++; return (0); } if (IEEE80211_ADDR_EQ(vap->iv_myaddr, ie.gann_addr)) return 0; IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, ni->ni_macaddr, "received GANN, meshgate: %6D (seq %u)", ie.gann_addr, ":", ie.gann_seq); if (ms == NULL) return (0); MESH_RT_LOCK(ms); TAILQ_FOREACH_SAFE(gr, &ms->ms_known_gates, gr_next, next) { if (!IEEE80211_ADDR_EQ(gr->gr_addr, ie.gann_addr)) continue; if (ie.gann_seq <= gr->gr_lastseq) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_MESH, ni->ni_macaddr, NULL, "GANN old seqno %u <= %u", ie.gann_seq, gr->gr_lastseq); MESH_RT_UNLOCK(ms); return (0); } /* corresponding mesh gate found & GANN accepted */ found = 1; break; } if (found == 0) { /* this GANN is from a new mesh Gate add it to known table. */ IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, ie.gann_addr, "stored new GANN information, seq %u.", ie.gann_seq); gr = IEEE80211_MALLOC(ALIGN(sizeof(struct ieee80211_mesh_gate_route)), M_80211_MESH_GT_RT, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); IEEE80211_ADDR_COPY(gr->gr_addr, ie.gann_addr); TAILQ_INSERT_TAIL(&ms->ms_known_gates, gr, gr_next); } gr->gr_lastseq = ie.gann_seq; /* check if we have a path to this gate */ rt_gate = mesh_rt_find_locked(ms, gr->gr_addr); if (rt_gate != NULL && rt_gate->rt_flags & IEEE80211_MESHRT_FLAGS_VALID) { gr->gr_route = rt_gate; rt_gate->rt_flags |= IEEE80211_MESHRT_FLAGS_GATE; } MESH_RT_UNLOCK(ms); /* popagate only if decremented ttl >= 1 && forwarding is enabled */ if ((ie.gann_ttl - 1) < 1 && !(ms->ms_flags & IEEE80211_MESHFLAGS_FWD)) return 0; pgann.gann_flags = ie.gann_flags; /* Reserved */ pgann.gann_hopcount = ie.gann_hopcount + 1; pgann.gann_ttl = ie.gann_ttl - 1; IEEE80211_ADDR_COPY(pgann.gann_addr, ie.gann_addr); pgann.gann_seq = ie.gann_seq; pgann.gann_interval = ie.gann_interval; IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, ie.gann_addr, "%s", "propagate GANN"); ieee80211_send_action(vap->iv_bss, IEEE80211_ACTION_CAT_MESH, IEEE80211_ACTION_MESH_GANN, &pgann); return 0; } static int mesh_send_action(struct ieee80211_node *ni, const uint8_t sa[IEEE80211_ADDR_LEN], const uint8_t da[IEEE80211_ADDR_LEN], struct mbuf *m) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_bpf_params params; int ret; KASSERT(ni != NULL, ("null node")); if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT, ni, "block %s frame in CAC state", "Mesh action"); vap->iv_stats.is_tx_badstate++; ieee80211_free_node(ni); m_freem(m); return EIO; /* XXX */ } M_PREPEND(m, sizeof(struct ieee80211_frame), IEEE80211_M_NOWAIT); if (m == NULL) { ieee80211_free_node(ni); return ENOMEM; } IEEE80211_TX_LOCK(ic); ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_ACTION, IEEE80211_NONQOS_TID, sa, da, sa); m->m_flags |= M_ENCAP; /* mark encapsulated */ memset(¶ms, 0, sizeof(params)); params.ibp_pri = WME_AC_VO; params.ibp_rate0 = ni->ni_txparms->mgmtrate; if (IEEE80211_IS_MULTICAST(da)) params.ibp_try0 = 1; else params.ibp_try0 = ni->ni_txparms->maxretry; params.ibp_power = ni->ni_txpower; IEEE80211_NODE_STAT(ni, tx_mgmt); ret = ieee80211_raw_output(vap, ni, m, ¶ms); IEEE80211_TX_UNLOCK(ic); return (ret); } #define ADDSHORT(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = (v) >> 8; \ frm += 2; \ } while (0) #define ADDWORD(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = ((v) >> 8) & 0xff; \ frm[2] = ((v) >> 16) & 0xff; \ frm[3] = ((v) >> 24) & 0xff; \ frm += 4; \ } while (0) static int mesh_send_action_meshpeering_open(struct ieee80211_node *ni, int category, int action, void *args0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; uint16_t *args = args0; const struct ieee80211_rateset *rs; struct mbuf *m; uint8_t *frm; IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_MESH, ni, "send PEER OPEN action: localid 0x%x", args[0]); IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) /* action+category */ + sizeof(uint16_t) /* capabilites */ + 2 + IEEE80211_RATE_SIZE + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + 2 + IEEE80211_MESHID_LEN + sizeof(struct ieee80211_meshconf_ie) + sizeof(struct ieee80211_meshpeer_ie) ); if (m != NULL) { /* * mesh peer open action frame format: * [1] category * [1] action * [2] capabilities * [tlv] rates * [tlv] xrates * [tlv] mesh id * [tlv] mesh conf * [tlv] mesh peer link mgmt */ *frm++ = category; *frm++ = action; ADDSHORT(frm, ieee80211_getcapinfo(vap, ni->ni_chan)); rs = ieee80211_get_suprates(ic, ic->ic_curchan); frm = ieee80211_add_rates(frm, rs); frm = ieee80211_add_xrates(frm, rs); frm = ieee80211_add_meshid(frm, vap); frm = ieee80211_add_meshconf(frm, vap); frm = ieee80211_add_meshpeer(frm, IEEE80211_ACTION_MESHPEERING_OPEN, args[0], 0, 0); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return mesh_send_action(ni, vap->iv_myaddr, ni->ni_macaddr, m); } else { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); return ENOMEM; } } static int mesh_send_action_meshpeering_confirm(struct ieee80211_node *ni, int category, int action, void *args0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; uint16_t *args = args0; const struct ieee80211_rateset *rs; struct mbuf *m; uint8_t *frm; IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_MESH, ni, "send PEER CONFIRM action: localid 0x%x, peerid 0x%x", args[0], args[1]); IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) /* action+category */ + sizeof(uint16_t) /* capabilites */ + sizeof(uint16_t) /* status code */ + sizeof(uint16_t) /* AID */ + 2 + IEEE80211_RATE_SIZE + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + 2 + IEEE80211_MESHID_LEN + sizeof(struct ieee80211_meshconf_ie) + sizeof(struct ieee80211_meshpeer_ie) ); if (m != NULL) { /* * mesh peer confirm action frame format: * [1] category * [1] action * [2] capabilities * [2] status code * [2] association id (peer ID) * [tlv] rates * [tlv] xrates * [tlv] mesh id * [tlv] mesh conf * [tlv] mesh peer link mgmt */ *frm++ = category; *frm++ = action; ADDSHORT(frm, ieee80211_getcapinfo(vap, ni->ni_chan)); ADDSHORT(frm, 0); /* status code */ ADDSHORT(frm, args[1]); /* AID */ rs = ieee80211_get_suprates(ic, ic->ic_curchan); frm = ieee80211_add_rates(frm, rs); frm = ieee80211_add_xrates(frm, rs); frm = ieee80211_add_meshid(frm, vap); frm = ieee80211_add_meshconf(frm, vap); frm = ieee80211_add_meshpeer(frm, IEEE80211_ACTION_MESHPEERING_CONFIRM, args[0], args[1], 0); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return mesh_send_action(ni, vap->iv_myaddr, ni->ni_macaddr, m); } else { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); return ENOMEM; } } static int mesh_send_action_meshpeering_close(struct ieee80211_node *ni, int category, int action, void *args0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; uint16_t *args = args0; struct mbuf *m; uint8_t *frm; IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_MESH, ni, "send PEER CLOSE action: localid 0x%x, peerid 0x%x reason %d (%s)", args[0], args[1], args[2], ieee80211_reason_to_string(args[2])); IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) /* action+category */ + sizeof(uint16_t) /* reason code */ + 2 + IEEE80211_MESHID_LEN + sizeof(struct ieee80211_meshpeer_ie) ); if (m != NULL) { /* * mesh peer close action frame format: * [1] category * [1] action * [tlv] mesh id * [tlv] mesh peer link mgmt */ *frm++ = category; *frm++ = action; frm = ieee80211_add_meshid(frm, vap); frm = ieee80211_add_meshpeer(frm, IEEE80211_ACTION_MESHPEERING_CLOSE, args[0], args[1], args[2]); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return mesh_send_action(ni, vap->iv_myaddr, ni->ni_macaddr, m); } else { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); return ENOMEM; } } static int mesh_send_action_meshlmetric(struct ieee80211_node *ni, int category, int action, void *arg0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_meshlmetric_ie *ie = arg0; struct mbuf *m; uint8_t *frm; if (ie->lm_flags & IEEE80211_MESH_LMETRIC_FLAGS_REQ) { IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_MESH, ni, "%s", "send LINK METRIC REQUEST action"); } else { IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_MESH, ni, "send LINK METRIC REPLY action: metric 0x%x", ie->lm_metric); } IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) + /* action+category */ sizeof(struct ieee80211_meshlmetric_ie) ); if (m != NULL) { /* * mesh link metric * [1] category * [1] action * [tlv] mesh link metric */ *frm++ = category; *frm++ = action; frm = ieee80211_add_meshlmetric(frm, ie->lm_flags, ie->lm_metric); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return mesh_send_action(ni, vap->iv_myaddr, ni->ni_macaddr, m); } else { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); return ENOMEM; } } static int mesh_send_action_meshgate(struct ieee80211_node *ni, int category, int action, void *arg0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_meshgann_ie *ie = arg0; struct mbuf *m; uint8_t *frm; IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) + /* action+category */ IEEE80211_MESHGANN_BASE_SZ ); if (m != NULL) { /* * mesh link metric * [1] category * [1] action * [tlv] mesh gate announcement */ *frm++ = category; *frm++ = action; frm = ieee80211_add_meshgate(frm, ie); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return mesh_send_action(ni, vap->iv_myaddr, broadcastaddr, m); } else { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); return ENOMEM; } } static void mesh_peer_timeout_setup(struct ieee80211_node *ni) { switch (ni->ni_mlstate) { case IEEE80211_NODE_MESH_HOLDING: ni->ni_mltval = ieee80211_mesh_holdingtimeout; break; case IEEE80211_NODE_MESH_CONFIRMRCV: ni->ni_mltval = ieee80211_mesh_confirmtimeout; break; case IEEE80211_NODE_MESH_IDLE: ni->ni_mltval = 0; break; default: ni->ni_mltval = ieee80211_mesh_retrytimeout; break; } if (ni->ni_mltval) callout_reset(&ni->ni_mltimer, ni->ni_mltval, mesh_peer_timeout_cb, ni); } /* * Same as above but backoffs timer statisically 50%. */ static void mesh_peer_timeout_backoff(struct ieee80211_node *ni) { uint32_t r; r = arc4random(); ni->ni_mltval += r % ni->ni_mltval; callout_reset(&ni->ni_mltimer, ni->ni_mltval, mesh_peer_timeout_cb, ni); } static __inline void mesh_peer_timeout_stop(struct ieee80211_node *ni) { callout_drain(&ni->ni_mltimer); } static void mesh_peer_backoff_cb(void *arg) { struct ieee80211_node *ni = (struct ieee80211_node *)arg; /* After backoff timeout, try to peer automatically again. */ ni->ni_mlhcnt = 0; } /* * Mesh Peer Link Management FSM timeout handling. */ static void mesh_peer_timeout_cb(void *arg) { struct ieee80211_node *ni = (struct ieee80211_node *)arg; uint16_t args[3]; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_MESH, ni, "mesh link timeout, state %d, retry counter %d", ni->ni_mlstate, ni->ni_mlrcnt); switch (ni->ni_mlstate) { case IEEE80211_NODE_MESH_IDLE: case IEEE80211_NODE_MESH_ESTABLISHED: break; case IEEE80211_NODE_MESH_OPENSNT: case IEEE80211_NODE_MESH_OPENRCV: if (ni->ni_mlrcnt == ieee80211_mesh_maxretries) { args[0] = ni->ni_mlpid; args[2] = IEEE80211_REASON_MESH_MAX_RETRIES; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, args); ni->ni_mlrcnt = 0; mesh_linkchange(ni, IEEE80211_NODE_MESH_HOLDING); mesh_peer_timeout_setup(ni); } else { args[0] = ni->ni_mlpid; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_OPEN, args); ni->ni_mlrcnt++; mesh_peer_timeout_backoff(ni); } break; case IEEE80211_NODE_MESH_CONFIRMRCV: args[0] = ni->ni_mlpid; args[2] = IEEE80211_REASON_MESH_CONFIRM_TIMEOUT; ieee80211_send_action(ni, IEEE80211_ACTION_CAT_SELF_PROT, IEEE80211_ACTION_MESHPEERING_CLOSE, args); mesh_linkchange(ni, IEEE80211_NODE_MESH_HOLDING); mesh_peer_timeout_setup(ni); break; case IEEE80211_NODE_MESH_HOLDING: ni->ni_mlhcnt++; if (ni->ni_mlhcnt >= ieee80211_mesh_maxholding) callout_reset(&ni->ni_mlhtimer, ieee80211_mesh_backofftimeout, mesh_peer_backoff_cb, ni); mesh_linkchange(ni, IEEE80211_NODE_MESH_IDLE); break; } } static int mesh_verify_meshid(struct ieee80211vap *vap, const uint8_t *ie) { struct ieee80211_mesh_state *ms = vap->iv_mesh; if (ie == NULL || ie[1] != ms->ms_idlen) return 1; return memcmp(ms->ms_id, ie + 2, ms->ms_idlen); } /* * Check if we are using the same algorithms for this mesh. */ static int mesh_verify_meshconf(struct ieee80211vap *vap, const uint8_t *ie) { const struct ieee80211_meshconf_ie *meshconf = (const struct ieee80211_meshconf_ie *) ie; const struct ieee80211_mesh_state *ms = vap->iv_mesh; if (meshconf == NULL) return 1; if (meshconf->conf_pselid != ms->ms_ppath->mpp_ie) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_MESH, "unknown path selection algorithm: 0x%x\n", meshconf->conf_pselid); return 1; } if (meshconf->conf_pmetid != ms->ms_pmetric->mpm_ie) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_MESH, "unknown path metric algorithm: 0x%x\n", meshconf->conf_pmetid); return 1; } if (meshconf->conf_ccid != 0) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_MESH, "unknown congestion control algorithm: 0x%x\n", meshconf->conf_ccid); return 1; } if (meshconf->conf_syncid != IEEE80211_MESHCONF_SYNC_NEIGHOFF) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_MESH, "unknown sync algorithm: 0x%x\n", meshconf->conf_syncid); return 1; } if (meshconf->conf_authid != 0) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_MESH, "unknown auth auth algorithm: 0x%x\n", meshconf->conf_pselid); return 1; } /* Not accepting peers */ if (!(meshconf->conf_cap & IEEE80211_MESHCONF_CAP_AP)) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_MESH, "not accepting peers: 0x%x\n", meshconf->conf_cap); return 1; } return 0; } static int mesh_verify_meshpeer(struct ieee80211vap *vap, uint8_t subtype, const uint8_t *ie) { const struct ieee80211_meshpeer_ie *meshpeer = (const struct ieee80211_meshpeer_ie *) ie; if (meshpeer == NULL || meshpeer->peer_len < IEEE80211_MPM_BASE_SZ || meshpeer->peer_len > IEEE80211_MPM_MAX_SZ) return 1; if (meshpeer->peer_proto != IEEE80211_MPPID_MPM) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_MESH, "Only MPM protocol is supported (proto: 0x%02X)", meshpeer->peer_proto); return 1; } switch (subtype) { case IEEE80211_ACTION_MESHPEERING_OPEN: if (meshpeer->peer_len != IEEE80211_MPM_BASE_SZ) return 1; break; case IEEE80211_ACTION_MESHPEERING_CONFIRM: if (meshpeer->peer_len != IEEE80211_MPM_BASE_SZ + 2) return 1; break; case IEEE80211_ACTION_MESHPEERING_CLOSE: if (meshpeer->peer_len < IEEE80211_MPM_BASE_SZ + 2) return 1; if (meshpeer->peer_len == (IEEE80211_MPM_BASE_SZ + 2) && meshpeer->peer_linkid != 0) return 1; if (meshpeer->peer_rcode == 0) return 1; break; } return 0; } /* * Add a Mesh ID IE to a frame. */ uint8_t * ieee80211_add_meshid(uint8_t *frm, struct ieee80211vap *vap) { struct ieee80211_mesh_state *ms = vap->iv_mesh; KASSERT(vap->iv_opmode == IEEE80211_M_MBSS, ("not a mbss vap")); *frm++ = IEEE80211_ELEMID_MESHID; *frm++ = ms->ms_idlen; memcpy(frm, ms->ms_id, ms->ms_idlen); return frm + ms->ms_idlen; } /* * Add a Mesh Configuration IE to a frame. * For now just use HWMP routing, Airtime link metric, Null Congestion * Signaling, Null Sync Protocol and Null Authentication. */ uint8_t * ieee80211_add_meshconf(uint8_t *frm, struct ieee80211vap *vap) { const struct ieee80211_mesh_state *ms = vap->iv_mesh; uint16_t caps; KASSERT(vap->iv_opmode == IEEE80211_M_MBSS, ("not a MBSS vap")); *frm++ = IEEE80211_ELEMID_MESHCONF; *frm++ = IEEE80211_MESH_CONF_SZ; *frm++ = ms->ms_ppath->mpp_ie; /* path selection */ *frm++ = ms->ms_pmetric->mpm_ie; /* link metric */ *frm++ = IEEE80211_MESHCONF_CC_DISABLED; *frm++ = IEEE80211_MESHCONF_SYNC_NEIGHOFF; *frm++ = IEEE80211_MESHCONF_AUTH_DISABLED; /* NB: set the number of neighbors before the rest */ *frm = (ms->ms_neighbors > IEEE80211_MESH_MAX_NEIGHBORS ? IEEE80211_MESH_MAX_NEIGHBORS : ms->ms_neighbors) << 1; if (ms->ms_flags & IEEE80211_MESHFLAGS_GATE) *frm |= IEEE80211_MESHCONF_FORM_GATE; frm += 1; caps = 0; if (ms->ms_flags & IEEE80211_MESHFLAGS_AP) caps |= IEEE80211_MESHCONF_CAP_AP; if (ms->ms_flags & IEEE80211_MESHFLAGS_FWD) caps |= IEEE80211_MESHCONF_CAP_FWRD; *frm++ = caps; return frm; } /* * Add a Mesh Peer Management IE to a frame. */ uint8_t * ieee80211_add_meshpeer(uint8_t *frm, uint8_t subtype, uint16_t localid, uint16_t peerid, uint16_t reason) { KASSERT(localid != 0, ("localid == 0")); *frm++ = IEEE80211_ELEMID_MESHPEER; switch (subtype) { case IEEE80211_ACTION_MESHPEERING_OPEN: *frm++ = IEEE80211_MPM_BASE_SZ; /* length */ ADDSHORT(frm, IEEE80211_MPPID_MPM); /* proto */ ADDSHORT(frm, localid); /* local ID */ break; case IEEE80211_ACTION_MESHPEERING_CONFIRM: KASSERT(peerid != 0, ("sending peer confirm without peer id")); *frm++ = IEEE80211_MPM_BASE_SZ + 2; /* length */ ADDSHORT(frm, IEEE80211_MPPID_MPM); /* proto */ ADDSHORT(frm, localid); /* local ID */ ADDSHORT(frm, peerid); /* peer ID */ break; case IEEE80211_ACTION_MESHPEERING_CLOSE: if (peerid) *frm++ = IEEE80211_MPM_MAX_SZ; /* length */ else *frm++ = IEEE80211_MPM_BASE_SZ + 2; /* length */ ADDSHORT(frm, IEEE80211_MPPID_MPM); /* proto */ ADDSHORT(frm, localid); /* local ID */ if (peerid) ADDSHORT(frm, peerid); /* peer ID */ ADDSHORT(frm, reason); break; } return frm; } /* * Compute an Airtime Link Metric for the link with this node. * * Based on Draft 3.0 spec (11B.10, p.149). */ /* * Max 802.11s overhead. */ #define IEEE80211_MESH_MAXOVERHEAD \ (sizeof(struct ieee80211_qosframe_addr4) \ + sizeof(struct ieee80211_meshcntl_ae10) \ + sizeof(struct llc) \ + IEEE80211_ADDR_LEN \ + IEEE80211_WEP_IVLEN \ + IEEE80211_WEP_KIDLEN \ + IEEE80211_WEP_CRCLEN \ + IEEE80211_WEP_MICLEN \ + IEEE80211_CRC_LEN) uint32_t mesh_airtime_calc(struct ieee80211_node *ni) { #define M_BITS 8 #define S_FACTOR (2 * M_BITS) struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = ni->ni_vap->iv_ifp; const static int nbits = 8192 << M_BITS; uint32_t overhead, rate, errrate; uint64_t res; /* Time to transmit a frame */ rate = ieee80211_node_get_txrate_dot11rate(ni); overhead = ieee80211_compute_duration(ic->ic_rt, ifp->if_mtu + IEEE80211_MESH_MAXOVERHEAD, rate, 0) << M_BITS; /* Error rate in percentage */ /* XXX assuming small failures are ok */ errrate = (((ifp->if_get_counter(ifp, IFCOUNTER_OERRORS) + ifp->if_get_counter(ifp, IFCOUNTER_IERRORS)) / 100) << M_BITS) / 100; res = (overhead + (nbits / rate)) * ((1 << S_FACTOR) / ((1 << M_BITS) - errrate)); return (uint32_t)(res >> S_FACTOR); #undef M_BITS #undef S_FACTOR } /* * Add a Mesh Link Metric report IE to a frame. */ uint8_t * ieee80211_add_meshlmetric(uint8_t *frm, uint8_t flags, uint32_t metric) { *frm++ = IEEE80211_ELEMID_MESHLINK; *frm++ = 5; *frm++ = flags; ADDWORD(frm, metric); return frm; } /* * Add a Mesh Gate Announcement IE to a frame. */ uint8_t * ieee80211_add_meshgate(uint8_t *frm, struct ieee80211_meshgann_ie *ie) { *frm++ = IEEE80211_ELEMID_MESHGANN; /* ie */ *frm++ = IEEE80211_MESHGANN_BASE_SZ; /* len */ *frm++ = ie->gann_flags; *frm++ = ie->gann_hopcount; *frm++ = ie->gann_ttl; IEEE80211_ADDR_COPY(frm, ie->gann_addr); frm += 6; ADDWORD(frm, ie->gann_seq); ADDSHORT(frm, ie->gann_interval); return frm; } #undef ADDSHORT #undef ADDWORD /* * Initialize any mesh-specific node state. */ void ieee80211_mesh_node_init(struct ieee80211vap *vap, struct ieee80211_node *ni) { ni->ni_flags |= IEEE80211_NODE_QOS; callout_init(&ni->ni_mltimer, 1); callout_init(&ni->ni_mlhtimer, 1); } /* * Cleanup any mesh-specific node state. */ void ieee80211_mesh_node_cleanup(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_mesh_state *ms = vap->iv_mesh; callout_drain(&ni->ni_mltimer); callout_drain(&ni->ni_mlhtimer); /* NB: short-circuit callbacks after mesh_vdetach */ if (vap->iv_mesh != NULL) ms->ms_ppath->mpp_peerdown(ni); } void ieee80211_parse_meshid(struct ieee80211_node *ni, const uint8_t *ie) { ni->ni_meshidlen = ie[1]; memcpy(ni->ni_meshid, ie + 2, ie[1]); } /* * Setup mesh-specific node state on neighbor discovery. */ void ieee80211_mesh_init_neighbor(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const struct ieee80211_scanparams *sp) { ieee80211_parse_meshid(ni, sp->meshid); } void ieee80211_mesh_update_beacon(struct ieee80211vap *vap, struct ieee80211_beacon_offsets *bo) { KASSERT(vap->iv_opmode == IEEE80211_M_MBSS, ("not a MBSS vap")); if (isset(bo->bo_flags, IEEE80211_BEACON_MESHCONF)) { (void)ieee80211_add_meshconf(bo->bo_meshconf, vap); clrbit(bo->bo_flags, IEEE80211_BEACON_MESHCONF); } } static int mesh_ioctl_get80211(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211_mesh_state *ms = vap->iv_mesh; uint8_t tmpmeshid[IEEE80211_NWID_LEN]; struct ieee80211_mesh_route *rt; struct ieee80211req_mesh_route *imr; size_t len, off; uint8_t *p; int error; if (vap->iv_opmode != IEEE80211_M_MBSS) return ENOSYS; error = 0; switch (ireq->i_type) { case IEEE80211_IOC_MESH_ID: ireq->i_len = ms->ms_idlen; memcpy(tmpmeshid, ms->ms_id, ireq->i_len); error = copyout(tmpmeshid, ireq->i_data, ireq->i_len); break; case IEEE80211_IOC_MESH_AP: ireq->i_val = (ms->ms_flags & IEEE80211_MESHFLAGS_AP) != 0; break; case IEEE80211_IOC_MESH_FWRD: ireq->i_val = (ms->ms_flags & IEEE80211_MESHFLAGS_FWD) != 0; break; case IEEE80211_IOC_MESH_GATE: ireq->i_val = (ms->ms_flags & IEEE80211_MESHFLAGS_GATE) != 0; break; case IEEE80211_IOC_MESH_TTL: ireq->i_val = ms->ms_ttl; break; case IEEE80211_IOC_MESH_RTCMD: switch (ireq->i_val) { case IEEE80211_MESH_RTCMD_LIST: len = 0; MESH_RT_LOCK(ms); TAILQ_FOREACH(rt, &ms->ms_routes, rt_next) { len += sizeof(*imr); } MESH_RT_UNLOCK(ms); if (len > ireq->i_len || ireq->i_len < sizeof(*imr)) { ireq->i_len = len; return ENOMEM; } ireq->i_len = len; /* XXX M_WAIT? */ p = IEEE80211_MALLOC(len, M_TEMP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (p == NULL) return ENOMEM; off = 0; MESH_RT_LOCK(ms); TAILQ_FOREACH(rt, &ms->ms_routes, rt_next) { if (off >= len) break; imr = (struct ieee80211req_mesh_route *) (p + off); IEEE80211_ADDR_COPY(imr->imr_dest, rt->rt_dest); IEEE80211_ADDR_COPY(imr->imr_nexthop, rt->rt_nexthop); imr->imr_metric = rt->rt_metric; imr->imr_nhops = rt->rt_nhops; imr->imr_lifetime = ieee80211_mesh_rt_update(rt, 0); imr->imr_lastmseq = rt->rt_lastmseq; imr->imr_flags = rt->rt_flags; /* last */ off += sizeof(*imr); } MESH_RT_UNLOCK(ms); error = copyout(p, (uint8_t *)ireq->i_data, ireq->i_len); IEEE80211_FREE(p, M_TEMP); break; case IEEE80211_MESH_RTCMD_FLUSH: case IEEE80211_MESH_RTCMD_ADD: case IEEE80211_MESH_RTCMD_DELETE: return EINVAL; default: return ENOSYS; } break; case IEEE80211_IOC_MESH_PR_METRIC: len = strlen(ms->ms_pmetric->mpm_descr); if (ireq->i_len < len) return EINVAL; ireq->i_len = len; error = copyout(ms->ms_pmetric->mpm_descr, (uint8_t *)ireq->i_data, len); break; case IEEE80211_IOC_MESH_PR_PATH: len = strlen(ms->ms_ppath->mpp_descr); if (ireq->i_len < len) return EINVAL; ireq->i_len = len; error = copyout(ms->ms_ppath->mpp_descr, (uint8_t *)ireq->i_data, len); break; default: return ENOSYS; } return error; } IEEE80211_IOCTL_GET(mesh, mesh_ioctl_get80211); static int mesh_ioctl_set80211(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211_mesh_state *ms = vap->iv_mesh; uint8_t tmpmeshid[IEEE80211_NWID_LEN]; uint8_t tmpaddr[IEEE80211_ADDR_LEN]; char tmpproto[IEEE80211_MESH_PROTO_DSZ]; int error; if (vap->iv_opmode != IEEE80211_M_MBSS) return ENOSYS; error = 0; switch (ireq->i_type) { case IEEE80211_IOC_MESH_ID: if (ireq->i_val != 0 || ireq->i_len > IEEE80211_MESHID_LEN) return EINVAL; error = copyin(ireq->i_data, tmpmeshid, ireq->i_len); if (error != 0) break; memset(ms->ms_id, 0, IEEE80211_NWID_LEN); ms->ms_idlen = ireq->i_len; memcpy(ms->ms_id, tmpmeshid, ireq->i_len); error = ENETRESET; break; case IEEE80211_IOC_MESH_AP: if (ireq->i_val) ms->ms_flags |= IEEE80211_MESHFLAGS_AP; else ms->ms_flags &= ~IEEE80211_MESHFLAGS_AP; error = ENETRESET; break; case IEEE80211_IOC_MESH_FWRD: if (ireq->i_val) ms->ms_flags |= IEEE80211_MESHFLAGS_FWD; else ms->ms_flags &= ~IEEE80211_MESHFLAGS_FWD; mesh_gatemode_setup(vap); break; case IEEE80211_IOC_MESH_GATE: if (ireq->i_val) ms->ms_flags |= IEEE80211_MESHFLAGS_GATE; else ms->ms_flags &= ~IEEE80211_MESHFLAGS_GATE; break; case IEEE80211_IOC_MESH_TTL: ms->ms_ttl = (uint8_t) ireq->i_val; break; case IEEE80211_IOC_MESH_RTCMD: switch (ireq->i_val) { case IEEE80211_MESH_RTCMD_LIST: return EINVAL; case IEEE80211_MESH_RTCMD_FLUSH: ieee80211_mesh_rt_flush(vap); break; case IEEE80211_MESH_RTCMD_ADD: error = copyin(ireq->i_data, tmpaddr, IEEE80211_ADDR_LEN); if (error != 0) break; if (IEEE80211_ADDR_EQ(vap->iv_myaddr, tmpaddr) || IEEE80211_ADDR_EQ(broadcastaddr, tmpaddr)) return EINVAL; ieee80211_mesh_discover(vap, tmpaddr, NULL); break; case IEEE80211_MESH_RTCMD_DELETE: error = copyin(ireq->i_data, tmpaddr, IEEE80211_ADDR_LEN); if (error != 0) break; ieee80211_mesh_rt_del(vap, tmpaddr); break; default: return ENOSYS; } break; case IEEE80211_IOC_MESH_PR_METRIC: error = copyin(ireq->i_data, tmpproto, sizeof(tmpproto)); if (error == 0) { error = mesh_select_proto_metric(vap, tmpproto); if (error == 0) error = ENETRESET; } break; case IEEE80211_IOC_MESH_PR_PATH: error = copyin(ireq->i_data, tmpproto, sizeof(tmpproto)); if (error == 0) { error = mesh_select_proto_path(vap, tmpproto); if (error == 0) error = ENETRESET; } break; default: return ENOSYS; } return error; } IEEE80211_IOCTL_SET(mesh, mesh_ioctl_set80211); diff --git a/sys/net80211/ieee80211_node.c b/sys/net80211/ieee80211_node.c index 360e5e7d9abd..de19bc6a7361 100644 --- a/sys/net80211/ieee80211_node.c +++ b/sys/net80211/ieee80211_node.c @@ -1,3333 +1,3335 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 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 #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #ifdef IEEE80211_SUPPORT_TDMA #include #endif #include #include #include #include #include #ifdef IEEE80211_DEBUG_REFCNT #define __debrefcnt_used #else #define __debrefcnt_used __unused #endif /* * IEEE80211_NODE_HASHSIZE must be a power of 2. */ CTASSERT((IEEE80211_NODE_HASHSIZE & (IEEE80211_NODE_HASHSIZE-1)) == 0); /* * Association id's are managed with a bit vector. */ #define IEEE80211_AID_SET(_vap, b) \ ((_vap)->iv_aid_bitmap[IEEE80211_AID(b) / 32] |= \ (1 << (IEEE80211_AID(b) % 32))) #define IEEE80211_AID_CLR(_vap, b) \ ((_vap)->iv_aid_bitmap[IEEE80211_AID(b) / 32] &= \ ~(1 << (IEEE80211_AID(b) % 32))) #define IEEE80211_AID_ISSET(_vap, b) \ ((_vap)->iv_aid_bitmap[IEEE80211_AID(b) / 32] & (1 << (IEEE80211_AID(b) % 32))) static int ieee80211_sta_join1(struct ieee80211_node *); static struct ieee80211_node *ieee80211_alloc_node( struct ieee80211_node_table *, struct ieee80211vap *, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *, int); static struct ieee80211_node *node_alloc(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN]); static int node_init(struct ieee80211_node *); static void node_cleanup(struct ieee80211_node *); static void node_free(struct ieee80211_node *); static void node_age(struct ieee80211_node *); static int8_t node_getrssi(const struct ieee80211_node *); static void node_getsignal(const struct ieee80211_node *, int8_t *, int8_t *); static void node_getmimoinfo(const struct ieee80211_node *, struct ieee80211_mimo_info *); static void __ieee80211_free_node(struct ieee80211_node *); static void node_reclaim(struct ieee80211_node_table *nt, struct ieee80211_node *ni); static void ieee80211_node_table_init(struct ieee80211com *ic, struct ieee80211_node_table *nt, const char *name, int inact, int keymaxix); static void ieee80211_node_table_reset(struct ieee80211_node_table *, struct ieee80211vap *); static void ieee80211_node_table_cleanup(struct ieee80211_node_table *nt); static void ieee80211_vap_erp_timeout(struct ieee80211vap *); MALLOC_DEFINE(M_80211_NODE, "80211node", "802.11 node state"); MALLOC_DEFINE(M_80211_NODE_IE, "80211nodeie", "802.11 node ie"); void ieee80211_node_attach(struct ieee80211com *ic) { /* XXX really want maxlen enforced per-sta */ ieee80211_ageq_init(&ic->ic_stageq, ic->ic_max_keyix * 8, "802.11 staging q"); ieee80211_node_table_init(ic, &ic->ic_sta, "station", IEEE80211_INACT_INIT, ic->ic_max_keyix); callout_init(&ic->ic_inact, 1); callout_reset(&ic->ic_inact, IEEE80211_INACT_WAIT*hz, ieee80211_node_timeout, ic); ic->ic_node_alloc = node_alloc; ic->ic_node_init = node_init; ic->ic_node_free = node_free; ic->ic_node_cleanup = node_cleanup; ic->ic_node_age = node_age; ic->ic_node_drain = node_age; /* NB: same as age */ ic->ic_node_getrssi = node_getrssi; ic->ic_node_getsignal = node_getsignal; ic->ic_node_getmimoinfo = node_getmimoinfo; /* * Set flags to be propagated to all vap's; * these define default behaviour/configuration. */ ic->ic_flags_ext |= IEEE80211_FEXT_INACT; /* inactivity processing */ } void ieee80211_node_detach(struct ieee80211com *ic) { callout_drain(&ic->ic_inact); ieee80211_node_table_cleanup(&ic->ic_sta); ieee80211_ageq_drain(&ic->ic_stageq); ieee80211_ageq_cleanup(&ic->ic_stageq); } void ieee80211_node_vattach(struct ieee80211vap *vap) { /* NB: driver can override */ vap->iv_max_aid = IEEE80211_AID_DEF; /* default station inactivity timer settings */ vap->iv_inact_init = IEEE80211_INACT_INIT; vap->iv_inact_auth = IEEE80211_INACT_AUTH; vap->iv_inact_run = IEEE80211_INACT_RUN; vap->iv_inact_probe = IEEE80211_INACT_PROBE; IEEE80211_DPRINTF(vap, IEEE80211_MSG_INACT, "%s: init %u auth %u run %u probe %u\n", __func__, vap->iv_inact_init, vap->iv_inact_auth, vap->iv_inact_run, vap->iv_inact_probe); } void ieee80211_node_latevattach(struct ieee80211vap *vap) { /* XXX should ieee80211_vap_attach(), our only caller hold the lock? */ IEEE80211_UNLOCK_ASSERT(vap->iv_ic); if (vap->iv_opmode == IEEE80211_M_HOSTAP) { /* XXX should we allow max aid to be zero? */ if (vap->iv_max_aid < IEEE80211_AID_MIN) { vap->iv_max_aid = IEEE80211_AID_MIN; net80211_vap_printf(vap, "WARNING: max aid too small, changed to %d\n", vap->iv_max_aid); } vap->iv_aid_bitmap = (uint32_t *) IEEE80211_MALLOC( howmany(vap->iv_max_aid, 32) * sizeof(uint32_t), M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (vap->iv_aid_bitmap == NULL) { /* XXX no way to recover */ - printf("%s: no memory for AID bitmap, max aid %d!\n", + net80211_vap_printf(vap, + "%s: no memory for AID bitmap, max aid %d!\n", __func__, vap->iv_max_aid); vap->iv_max_aid = 0; } } IEEE80211_LOCK(vap->iv_ic); ieee80211_reset_bss(vap); IEEE80211_UNLOCK(vap->iv_ic); vap->iv_auth = ieee80211_authenticator_get(vap->iv_bss->ni_authmode); } void ieee80211_node_vdetach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; /* XXX should ieee80211_vap_detach(), our only caller hold the lock? */ IEEE80211_UNLOCK_ASSERT(vap->iv_ic); ieee80211_node_table_reset(&ic->ic_sta, vap); IEEE80211_LOCK(ic); if (vap->iv_bss != NULL) { ieee80211_free_node(vap->iv_bss); vap->iv_update_bss(vap, NULL); } IEEE80211_UNLOCK(ic); if (vap->iv_aid_bitmap != NULL) { IEEE80211_FREE(vap->iv_aid_bitmap, M_80211_NODE); vap->iv_aid_bitmap = NULL; } } /* * Port authorize/unauthorize interfaces for use by an authenticator. */ void ieee80211_node_authorize(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; ni->ni_flags |= IEEE80211_NODE_AUTH; ni->ni_inact_reload = vap->iv_inact_run; ni->ni_inact = ni->ni_inact_reload; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact_reload %u", __func__, ni->ni_inact_reload); } void ieee80211_node_unauthorize(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; ni->ni_flags &= ~IEEE80211_NODE_AUTH; ni->ni_inact_reload = vap->iv_inact_auth; if (ni->ni_inact > ni->ni_inact_reload) ni->ni_inact = ni->ni_inact_reload; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact_reload %u inact %u", __func__, ni->ni_inact_reload, ni->ni_inact); } /* * Fix tx parameters for a node according to ``association state''. */ void ieee80211_node_setuptxparms(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; enum ieee80211_phymode mode; if (ni->ni_flags & IEEE80211_NODE_VHT) { if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) mode = IEEE80211_MODE_VHT_5GHZ; else mode = IEEE80211_MODE_VHT_2GHZ; } else if (ni->ni_flags & IEEE80211_NODE_HT) { if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) mode = IEEE80211_MODE_11NA; else mode = IEEE80211_MODE_11NG; } else { /* legacy rate handling */ if (IEEE80211_IS_CHAN_ST(ni->ni_chan)) mode = IEEE80211_MODE_STURBO_A; else if (IEEE80211_IS_CHAN_HALF(ni->ni_chan)) mode = IEEE80211_MODE_HALF; else if (IEEE80211_IS_CHAN_QUARTER(ni->ni_chan)) mode = IEEE80211_MODE_QUARTER; /* NB: 108A should be handled as 11a */ else if (IEEE80211_IS_CHAN_A(ni->ni_chan)) mode = IEEE80211_MODE_11A; else if (IEEE80211_IS_CHAN_108G(ni->ni_chan) || (ni->ni_flags & IEEE80211_NODE_ERP)) mode = IEEE80211_MODE_11G; else mode = IEEE80211_MODE_11B; } ni->ni_txparms = &vap->iv_txparms[mode]; } /* * Set/change the channel. The rate set is also updated as * to insure a consistent view by drivers. * XXX should be private but hostap needs it to deal with CSA */ void ieee80211_node_set_chan(struct ieee80211_node *ni, struct ieee80211_channel *chan) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; enum ieee80211_phymode mode; KASSERT(chan != IEEE80211_CHAN_ANYC, ("no channel")); ni->ni_chan = chan; mode = ieee80211_chan2mode(chan); if (IEEE80211_IS_CHAN_HT(chan)) { /* * We must install the legacy rate est in ni_rates and the * HT rate set in ni_htrates. */ ni->ni_htrates = *ieee80211_get_suphtrates(ic, chan); /* * Setup bss tx parameters based on operating mode. We * use legacy rates when operating in a mixed HT+non-HT bss * and non-ERP rates in 11g for mixed ERP+non-ERP bss. */ if (mode == IEEE80211_MODE_11NA && (vap->iv_flags_ht & IEEE80211_FHT_PUREN) == 0) mode = IEEE80211_MODE_11A; else if (mode == IEEE80211_MODE_11NG && (vap->iv_flags_ht & IEEE80211_FHT_PUREN) == 0) mode = IEEE80211_MODE_11G; if (mode == IEEE80211_MODE_11G && (vap->iv_flags & IEEE80211_F_PUREG) == 0) mode = IEEE80211_MODE_11B; } ni->ni_txparms = &vap->iv_txparms[mode]; ni->ni_rates = *ieee80211_get_suprates(ic, chan); } static __inline void copy_bss(struct ieee80211_node *nbss, const struct ieee80211_node *obss) { /* propagate useful state */ nbss->ni_authmode = obss->ni_authmode; nbss->ni_txpower = obss->ni_txpower; nbss->ni_vlan = obss->ni_vlan; /* XXX statistics? */ /* XXX legacy WDS bssid? */ } void ieee80211_create_ibss(struct ieee80211vap* vap, struct ieee80211_channel *chan) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: creating %s on channel %u%c flags 0x%08x\n", __func__, ieee80211_opmode_name[vap->iv_opmode], ieee80211_chan2ieee(ic, chan), ieee80211_channel_type_char(chan), chan->ic_flags); ni = ieee80211_alloc_node(&ic->ic_sta, vap, vap->iv_myaddr, __func__, __LINE__); if (ni == NULL) { /* XXX recovery? */ return; } IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_myaddr); ni->ni_esslen = vap->iv_des_ssid[0].len; memcpy(ni->ni_essid, vap->iv_des_ssid[0].ssid, ni->ni_esslen); if (vap->iv_bss != NULL) copy_bss(ni, vap->iv_bss); ni->ni_intval = ic->ic_bintval; if (vap->iv_flags & IEEE80211_F_PRIVACY) ni->ni_capinfo |= IEEE80211_CAPINFO_PRIVACY; if (ic->ic_phytype == IEEE80211_T_FH) { ni->ni_fhdwell = 200; /* XXX */ ni->ni_fhindex = 1; } if (vap->iv_opmode == IEEE80211_M_IBSS) { ni->ni_capinfo |= IEEE80211_CAPINFO_IBSS; /* XXX */ if (vap->iv_flags & IEEE80211_F_DESBSSID) IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_des_bssid); else { net80211_get_random_bytes(ni->ni_bssid, IEEE80211_ADDR_LEN); /* clear group bit, add local bit */ ni->ni_bssid[0] = (ni->ni_bssid[0] &~ 0x01) | 0x02; } } else if (vap->iv_opmode == IEEE80211_M_AHDEMO) { if (vap->iv_flags & IEEE80211_F_DESBSSID) IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_des_bssid); else #ifdef IEEE80211_SUPPORT_TDMA if ((vap->iv_caps & IEEE80211_C_TDMA) == 0) #endif memset(ni->ni_bssid, 0, IEEE80211_ADDR_LEN); #ifdef IEEE80211_SUPPORT_MESH } else if (vap->iv_opmode == IEEE80211_M_MBSS) { ni->ni_meshidlen = vap->iv_mesh->ms_idlen; memcpy(ni->ni_meshid, vap->iv_mesh->ms_id, ni->ni_meshidlen); #endif } /* * Fix the channel and related attributes. */ /* clear DFS CAC state on previous channel */ if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && ic->ic_bsschan->ic_freq != chan->ic_freq && IEEE80211_IS_CHAN_CACDONE(ic->ic_bsschan)) ieee80211_dfs_cac_clear(ic, ic->ic_bsschan); ic->ic_bsschan = chan; ieee80211_node_set_chan(ni, chan); ic->ic_curmode = ieee80211_chan2mode(chan); /* * Do mode-specific setup. */ if (IEEE80211_IS_CHAN_FULL(chan)) { if (IEEE80211_IS_CHAN_ANYG(chan)) { /* * Use a mixed 11b/11g basic rate set. */ ieee80211_setbasicrates(&ni->ni_rates, IEEE80211_MODE_11G); if (vap->iv_flags & IEEE80211_F_PUREG) { /* * Also mark OFDM rates basic so 11b * stations do not join (WiFi compliance). */ ieee80211_addbasicrates(&ni->ni_rates, IEEE80211_MODE_11A); } } else if (IEEE80211_IS_CHAN_B(chan)) { /* * Force pure 11b rate set. */ ieee80211_setbasicrates(&ni->ni_rates, IEEE80211_MODE_11B); } } /* XXX TODO: other bits and pieces - eg fast-frames? */ /* If we're an 11n channel then initialise the 11n bits */ if (IEEE80211_IS_CHAN_VHT(ni->ni_chan)) { /* XXX what else? */ ieee80211_ht_node_init(ni); ieee80211_vht_node_init(ni); } else if (IEEE80211_IS_CHAN_HT(ni->ni_chan)) { /* XXX what else? */ ieee80211_ht_node_init(ni); } (void) ieee80211_sta_join1(ieee80211_ref_node(ni)); } /* * Reset bss state on transition to the INIT state. * Clear any stations from the table (they have been * deauth'd) and reset the bss node (clears key, rate * etc. state). */ void ieee80211_reset_bss(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni, *obss; IEEE80211_LOCK_ASSERT(ic); ieee80211_node_table_reset(&ic->ic_sta, vap); /* XXX multi-bss: wrong */ ieee80211_vap_reset_erp(vap); ni = ieee80211_alloc_node(&ic->ic_sta, vap, vap->iv_myaddr, __func__, __LINE__); KASSERT(ni != NULL, ("unable to setup initial BSS node")); obss = vap->iv_update_bss(vap, ieee80211_ref_node(ni)); if (obss != NULL) { copy_bss(ni, obss); ni->ni_intval = ic->ic_bintval; ieee80211_free_node(obss); } else IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_myaddr); } static int match_ssid(const struct ieee80211_node *ni, int nssid, const struct ieee80211_scan_ssid ssids[]) { int i; for (i = 0; i < nssid; i++) { if (ni->ni_esslen == ssids[i].len && memcmp(ni->ni_essid, ssids[i].ssid, ni->ni_esslen) == 0) return 1; } return 0; } /* * Test a node for suitability/compatibility. */ static int check_bss(struct ieee80211vap *vap, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; uint8_t rate; if (isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, ni->ni_chan))) return 0; if (vap->iv_opmode == IEEE80211_M_IBSS) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) return 0; } else { if ((ni->ni_capinfo & IEEE80211_CAPINFO_ESS) == 0) return 0; } if (vap->iv_flags & IEEE80211_F_PRIVACY) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) return 0; } else { /* XXX does this mean privacy is supported or required? */ if (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) return 0; } rate = ieee80211_fix_rate(ni, &ni->ni_rates, IEEE80211_F_JOIN | IEEE80211_F_DONEGO | IEEE80211_F_DOFRATE); if (rate & IEEE80211_RATE_BASIC) return 0; if (vap->iv_des_nssid != 0 && !match_ssid(ni, vap->iv_des_nssid, vap->iv_des_ssid)) return 0; if ((vap->iv_flags & IEEE80211_F_DESBSSID) && !IEEE80211_ADDR_EQ(vap->iv_des_bssid, ni->ni_bssid)) return 0; return 1; } #ifdef IEEE80211_DEBUG /* * Display node suitability/compatibility. */ static void check_bss_debug(struct ieee80211vap *vap, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; uint8_t rate; int fail; fail = 0; if (isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, ni->ni_chan))) fail |= 0x01; if (vap->iv_opmode == IEEE80211_M_IBSS) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) fail |= 0x02; } else { if ((ni->ni_capinfo & IEEE80211_CAPINFO_ESS) == 0) fail |= 0x02; } if (vap->iv_flags & IEEE80211_F_PRIVACY) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) fail |= 0x04; } else { /* XXX does this mean privacy is supported or required? */ if (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) fail |= 0x04; } rate = ieee80211_fix_rate(ni, &ni->ni_rates, IEEE80211_F_JOIN | IEEE80211_F_DONEGO | IEEE80211_F_DOFRATE); if (rate & IEEE80211_RATE_BASIC) fail |= 0x08; if (vap->iv_des_nssid != 0 && !match_ssid(ni, vap->iv_des_nssid, vap->iv_des_ssid)) fail |= 0x10; if ((vap->iv_flags & IEEE80211_F_DESBSSID) && !IEEE80211_ADDR_EQ(vap->iv_des_bssid, ni->ni_bssid)) fail |= 0x20; - printf(" %c %s", fail ? '-' : '+', ether_sprintf(ni->ni_macaddr)); - printf(" %s%c", ether_sprintf(ni->ni_bssid), fail & 0x20 ? '!' : ' '); - printf(" %3d%c", + net80211_printf(" %c %s", fail ? '-' : '+', ether_sprintf(ni->ni_macaddr)); + net80211_printf(" %s%c", ether_sprintf(ni->ni_bssid), fail & 0x20 ? '!' : ' '); + net80211_printf(" %3d%c", ieee80211_chan2ieee(ic, ni->ni_chan), fail & 0x01 ? '!' : ' '); - printf(" %2dM%c", (rate & IEEE80211_RATE_VAL) / 2, + net80211_printf(" %2dM%c", (rate & IEEE80211_RATE_VAL) / 2, fail & 0x08 ? '!' : ' '); - printf(" %4s%c", + net80211_printf(" %4s%c", (ni->ni_capinfo & IEEE80211_CAPINFO_ESS) ? "ess" : (ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) ? "ibss" : "????", fail & 0x02 ? '!' : ' '); - printf(" %3s%c ", + net80211_printf(" %3s%c ", (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) ? "wep" : "no", fail & 0x04 ? '!' : ' '); ieee80211_print_essid(ni->ni_essid, ni->ni_esslen); - printf("%s\n", fail & 0x10 ? "!" : ""); + net80211_printf("%s\n", fail & 0x10 ? "!" : ""); } #endif /* IEEE80211_DEBUG */ int ieee80211_ibss_merge_check(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; if (ni == vap->iv_bss || IEEE80211_ADDR_EQ(ni->ni_bssid, vap->iv_bss->ni_bssid)) { /* unchanged, nothing to do */ return 0; } if (!check_bss(vap, ni)) { /* capabilities mismatch */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s: merge failed, capabilities mismatch\n", __func__); #ifdef IEEE80211_DEBUG if (ieee80211_msg_assoc(vap)) check_bss_debug(vap, ni); #endif vap->iv_stats.is_ibss_capmismatch++; return 0; } return 1; } /* * Check if the given node should populate the node table. * * We need to be in "see all beacons for all ssids" mode in order * to do IBSS merges, however this means we will populate nodes for * /all/ IBSS SSIDs, versus just the one we care about. * * So this check ensures the node can actually belong to our IBSS * configuration. For now it simply checks the SSID. */ int ieee80211_ibss_node_check_new(struct ieee80211_node *ni, const struct ieee80211_scanparams *scan) { struct ieee80211vap *vap = ni->ni_vap; int i; /* * If we have no SSID and no scan SSID, return OK. */ if (vap->iv_des_nssid == 0 && scan->ssid == NULL) goto ok; /* * If we have one of (SSID, scan SSID) then return error. */ if (!! (vap->iv_des_nssid == 0) != !! (scan->ssid == NULL)) goto mismatch; /* * Double-check - we need scan SSID. */ if (scan->ssid == NULL) goto mismatch; /* * Check if the scan SSID matches the SSID list for the VAP. */ for (i = 0; i < vap->iv_des_nssid; i++) { /* Sanity length check */ if (vap->iv_des_ssid[i].len != scan->ssid[1]) continue; /* Note: SSID in the scan entry is the IE format */ if (memcmp(vap->iv_des_ssid[i].ssid, scan->ssid + 2, vap->iv_des_ssid[i].len) == 0) goto ok; } mismatch: return (0); ok: return (1); } /* * Handle 802.11 ad hoc network merge. The * convention, set by the Wireless Ethernet Compatibility Alliance * (WECA), is that an 802.11 station will change its BSSID to match * the "oldest" 802.11 ad hoc network, on the same channel, that * has the station's desired SSID. The "oldest" 802.11 network * sends beacons with the greatest TSF timestamp. * * The caller is assumed to validate TSF's before attempting a merge. * * Return !0 if the BSSID changed, 0 otherwise. */ int ieee80211_ibss_merge(struct ieee80211_node *ni) { #ifdef IEEE80211_DEBUG struct ieee80211vap *vap = ni->ni_vap; #endif if (! ieee80211_ibss_merge_check(ni)) return 0; IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s: new bssid %s: %s preamble, %s slot time%s\n", __func__, ether_sprintf(ni->ni_bssid), vap->iv_flags&IEEE80211_F_SHPREAMBLE ? "short" : "long", vap->iv_flags&IEEE80211_F_SHSLOT ? "short" : "long", vap->iv_flags&IEEE80211_F_USEPROT ? ", protection" : "" ); return ieee80211_sta_join1(ieee80211_ref_node(ni)); } /* * Calculate HT channel promotion flags for all vaps. * This assumes ni_chan have been setup for each vap. */ static int gethtadjustflags(struct ieee80211com *ic) { struct ieee80211vap *vap; int flags; flags = 0; /* XXX locking */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { if (vap->iv_state < IEEE80211_S_RUN) continue; switch (vap->iv_opmode) { case IEEE80211_M_WDS: case IEEE80211_M_STA: case IEEE80211_M_AHDEMO: case IEEE80211_M_HOSTAP: case IEEE80211_M_IBSS: case IEEE80211_M_MBSS: flags |= ieee80211_htchanflags(vap->iv_bss->ni_chan); break; default: break; } } return flags; } /* * Calculate VHT channel promotion flags for all vaps. * This assumes ni_chan have been setup for each vap. */ static int getvhtadjustflags(struct ieee80211com *ic) { struct ieee80211vap *vap; int flags; flags = 0; /* XXX locking */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { if (vap->iv_state < IEEE80211_S_RUN) continue; switch (vap->iv_opmode) { case IEEE80211_M_WDS: case IEEE80211_M_STA: case IEEE80211_M_AHDEMO: case IEEE80211_M_HOSTAP: case IEEE80211_M_IBSS: case IEEE80211_M_MBSS: flags |= ieee80211_vhtchanflags(vap->iv_bss->ni_chan); break; default: break; } } return flags; } /* * Check if the current channel needs to change based on whether * any vap's are using HT20/HT40. This is used to sync the state * of ic_curchan after a channel width change on a running vap. * * Same applies for VHT. */ void ieee80211_sync_curchan(struct ieee80211com *ic) { struct ieee80211_channel *c; c = ieee80211_ht_adjust_channel(ic, ic->ic_curchan, gethtadjustflags(ic)); c = ieee80211_vht_adjust_channel(ic, c, getvhtadjustflags(ic)); if (c != ic->ic_curchan) { ic->ic_curchan = c; ic->ic_curmode = ieee80211_chan2mode(ic->ic_curchan); ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); IEEE80211_UNLOCK(ic); ic->ic_set_channel(ic); ieee80211_radiotap_chan_change(ic); IEEE80211_LOCK(ic); } } /* * Setup the current channel. The request channel may be * promoted if other vap's are operating with HT20/HT40. */ void ieee80211_setupcurchan(struct ieee80211com *ic, struct ieee80211_channel *c) { if (ic->ic_htcaps & IEEE80211_HTC_HT) { int flags = gethtadjustflags(ic); /* * Check for channel promotion required to support the * set of running vap's. This assumes we are called * after ni_chan is setup for each vap. */ /* XXX VHT? */ /* NB: this assumes IEEE80211_FHT_USEHT40 > IEEE80211_FHT_HT */ if (flags > ieee80211_htchanflags(c)) c = ieee80211_ht_adjust_channel(ic, c, flags); } /* * VHT promotion - this will at least promote to VHT20/40 * based on what HT has done; it may further promote the * channel to VHT80 or above. */ if (ic->ic_vht_cap.vht_cap_info != 0) { int flags = getvhtadjustflags(ic); if (flags > ieee80211_vhtchanflags(c)) c = ieee80211_vht_adjust_channel(ic, c, flags); } ic->ic_bsschan = ic->ic_curchan = c; ic->ic_curmode = ieee80211_chan2mode(ic->ic_curchan); ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); } /* * Change the current channel. The channel change is guaranteed to have * happened before the next state change. */ void ieee80211_setcurchan(struct ieee80211com *ic, struct ieee80211_channel *c) { ieee80211_setupcurchan(ic, c); ieee80211_runtask(ic, &ic->ic_chan_task); } void ieee80211_update_chw(struct ieee80211com *ic) { ieee80211_setupcurchan(ic, ic->ic_curchan); ieee80211_runtask(ic, &ic->ic_chw_task); } /* * Join the specified IBSS/BSS network. The node is assumed to * be passed in with a held reference. */ static int ieee80211_sta_join1(struct ieee80211_node *selbs) { struct ieee80211vap *vap = selbs->ni_vap; struct ieee80211com *ic = selbs->ni_ic; struct ieee80211_node *obss; int canreassoc; /* * Committed to selbs, setup state. */ IEEE80211_LOCK(ic); /* XXX may recurse here, check callers. */ obss = vap->iv_update_bss(vap, selbs); /* NB: caller assumed to bump refcnt */ IEEE80211_UNLOCK(ic); /* * Check if old+new node have the same address in which * case we can reassociate when operating in sta mode. */ /* XXX We'll not be in RUN anymore as iv_state got updated already? */ canreassoc = (obss != NULL && vap->iv_state == IEEE80211_S_RUN && IEEE80211_ADDR_EQ(obss->ni_macaddr, selbs->ni_macaddr)); if (obss != NULL) { struct ieee80211_node_table *nt = obss->ni_table; copy_bss(selbs, obss); if (nt != NULL) { ieee80211_node_decref(obss); /* iv_bss reference */ IEEE80211_NODE_LOCK(nt); node_reclaim(nt, obss); /* station table reference */ IEEE80211_NODE_UNLOCK(nt); } else { ieee80211_free_node(obss); /* iv_bss reference */ } obss = NULL; /* NB: guard against later use */ } /* * Delete unusable rates; we've already checked * that the negotiated rate set is acceptable. */ ieee80211_fix_rate(vap->iv_bss, &vap->iv_bss->ni_rates, IEEE80211_F_DODEL | IEEE80211_F_JOIN); ieee80211_setcurchan(ic, selbs->ni_chan); /* * Set the erp state (mostly the slot time) to deal with * the auto-select case; this should be redundant if the * mode is locked. */ ieee80211_vap_reset_erp(vap); ieee80211_wme_initparams(vap); if (vap->iv_opmode == IEEE80211_M_STA) { if (canreassoc) { /* Reassociate */ ieee80211_new_state(vap, IEEE80211_S_ASSOC, 1); } else { /* * Act as if we received a DEAUTH frame in case we * are invoked from the RUN state. This will cause * us to try to re-authenticate if we are operating * as a station. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_AUTH, "%s %p<%s> %s -> AUTH, FC0_SUBTYPE_DEAUTH\n", __func__, selbs, ether_sprintf(selbs->ni_macaddr), ieee80211_state_name[vap->iv_state]); ieee80211_new_state(vap, IEEE80211_S_AUTH, IEEE80211_FC0_SUBTYPE_DEAUTH); } } else ieee80211_new_state(vap, IEEE80211_S_RUN, -1); return 1; } int ieee80211_sta_join(struct ieee80211vap *vap, struct ieee80211_channel *chan, const struct ieee80211_scan_entry *se) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; int do_ht = 0; ni = ieee80211_alloc_node(&ic->ic_sta, vap, se->se_macaddr, __func__, __LINE__); if (ni == NULL) { /* XXX msg */ return 0; } /* * Expand scan state into node's format. * XXX may not need all this stuff */ IEEE80211_ADDR_COPY(ni->ni_bssid, se->se_bssid); ni->ni_esslen = se->se_ssid[1]; memcpy(ni->ni_essid, se->se_ssid+2, ni->ni_esslen); ni->ni_tstamp.tsf = se->se_tstamp.tsf; ni->ni_intval = se->se_intval; ni->ni_capinfo = se->se_capinfo; ni->ni_chan = chan; ni->ni_timoff = se->se_timoff; ni->ni_fhdwell = se->se_fhdwell; ni->ni_fhindex = se->se_fhindex; ni->ni_erp = se->se_erp; IEEE80211_RSSI_LPF(ni->ni_avgrssi, se->se_rssi); ni->ni_noise = se->se_noise; if (vap->iv_opmode == IEEE80211_M_STA) { /* NB: only infrastructure mode requires an associd */ ni->ni_flags |= IEEE80211_NODE_ASSOCID; } if (ieee80211_ies_init(&ni->ni_ies, se->se_ies.data, se->se_ies.len)) { ieee80211_ies_expand(&ni->ni_ies); #ifdef IEEE80211_SUPPORT_SUPERG if (ni->ni_ies.ath_ie != NULL) ieee80211_parse_ath(ni, ni->ni_ies.ath_ie); #endif if (ni->ni_ies.htcap_ie != NULL) ieee80211_parse_htcap(ni, ni->ni_ies.htcap_ie); if (ni->ni_ies.htinfo_ie != NULL) ieee80211_parse_htinfo(ni, ni->ni_ies.htinfo_ie); #ifdef IEEE80211_SUPPORT_MESH if (ni->ni_ies.meshid_ie != NULL) ieee80211_parse_meshid(ni, ni->ni_ies.meshid_ie); #endif #ifdef IEEE80211_SUPPORT_TDMA if (ni->ni_ies.tdma_ie != NULL) ieee80211_parse_tdma(ni, ni->ni_ies.tdma_ie); #endif if (ni->ni_ies.vhtcap_ie != NULL) ieee80211_parse_vhtcap(ni, ni->ni_ies.vhtcap_ie); if (ni->ni_ies.vhtopmode_ie != NULL) ieee80211_parse_vhtopmode(ni, ni->ni_ies.vhtopmode_ie); /* XXX parse BSSLOAD IE */ /* XXX parse TXPWRENV IE */ /* XXX parse APCHANREP IE */ } vap->iv_dtim_period = se->se_dtimperiod; vap->iv_dtim_count = 0; /* NB: must be after ni_chan is setup */ ieee80211_setup_rates(ni, se->se_rates, se->se_xrates, IEEE80211_F_DOSORT); if (ieee80211_iserp_rateset(&ni->ni_rates)) ni->ni_flags |= IEEE80211_NODE_ERP; /* * Setup HT state for this node if it's available, otherwise * non-STA modes won't pick this state up. * * For IBSS and related modes that don't go through an * association request/response, the only appropriate place * to setup the HT state is here. */ if (ni->ni_ies.htinfo_ie != NULL && ni->ni_ies.htcap_ie != NULL && vap->iv_flags_ht & IEEE80211_FHT_HT) { ieee80211_ht_node_init(ni); ieee80211_ht_updateparams(ni, ni->ni_ies.htcap_ie, ni->ni_ies.htinfo_ie); do_ht = 1; } /* * Setup VHT state for this node if it's available. * Same as the above. * * For now, don't allow 2GHz VHT operation. */ if (ni->ni_ies.vhtopmode_ie != NULL && ni->ni_ies.vhtcap_ie != NULL && vap->iv_vht_flags & IEEE80211_FVHT_VHT) { if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) { - printf("%s: BSS %6D: 2GHz channel, VHT info; ignoring\n", - __func__, - ni->ni_macaddr, - ":"); + net80211_vap_printf(ni->ni_vap, + "%s: BSS %6D: 2GHz channel, VHT info; ignoring\n", + __func__, ni->ni_macaddr, ":"); } else { ieee80211_vht_node_init(ni); ieee80211_vht_updateparams(ni, ni->ni_ies.vhtcap_ie, ni->ni_ies.vhtopmode_ie); ieee80211_setup_vht_rates(ni); do_ht = 1; } } /* Finally do the node channel change */ if (do_ht) { ieee80211_ht_updateparams_final(ni, ni->ni_ies.htcap_ie, ni->ni_ies.htinfo_ie); ieee80211_setup_htrates(ni, ni->ni_ies.htcap_ie, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, ni->ni_ies.htinfo_ie); } /* XXX else check for ath FF? */ /* XXX QoS? Difficult given that WME config is specific to a master */ ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); return ieee80211_sta_join1(ieee80211_ref_node(ni)); } /* * Leave the specified IBSS/BSS network. The node is assumed to * be passed in with a held reference. */ void ieee80211_sta_leave(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; ic->ic_node_cleanup(ni); ieee80211_notify_node_leave(ni); } /* * Send a deauthenticate frame and drop the station. */ void ieee80211_node_deauth(struct ieee80211_node *ni, int reason) { /* NB: bump the refcnt to be sure temporary nodes are not reclaimed */ ieee80211_ref_node(ni); if (ni->ni_associd != 0) IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, reason); ieee80211_node_leave(ni); ieee80211_free_node(ni); } static struct ieee80211_node * node_alloc(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211_node *ni; ni = (struct ieee80211_node *) IEEE80211_MALLOC(sizeof(struct ieee80211_node), M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); return ni; } static int node_init(struct ieee80211_node *ni) { return 0; } /* * Initialize an ie blob with the specified data. If previous * data exists re-use the data block. As a side effect we clear * all references to specific ie's; the caller is required to * recalculate them. */ int ieee80211_ies_init(struct ieee80211_ies *ies, const uint8_t *data, int len) { /* NB: assumes data+len are the last fields */ memset(ies, 0, offsetof(struct ieee80211_ies, data)); if (ies->data != NULL && ies->len != len) { /* data size changed */ IEEE80211_FREE(ies->data, M_80211_NODE_IE); ies->data = NULL; } if (ies->data == NULL) { ies->data = (uint8_t *) IEEE80211_MALLOC(len, M_80211_NODE_IE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (ies->data == NULL) { ies->len = 0; /* NB: pointers have already been zero'd above */ return 0; } } memcpy(ies->data, data, len); ies->len = len; return 1; } /* * Reclaim storage for an ie blob. */ void ieee80211_ies_cleanup(struct ieee80211_ies *ies) { if (ies->data != NULL) IEEE80211_FREE(ies->data, M_80211_NODE_IE); } /* * Expand an ie blob data contents and to fillin individual * ie pointers. The data blob is assumed to be well-formed; * we don't do any validity checking of ie lengths. */ void ieee80211_ies_expand(struct ieee80211_ies *ies) { uint8_t *ie; int ielen; ie = ies->data; ielen = ies->len; while (ielen > 1) { /* Make sure the given IE length fits into the total length. */ if ((2 + ie[1]) > ielen) { - printf("%s: malformed IEs! ies %p { data %p len %d }: " + net80211_printf("%s: malformed IEs! ies %p { data %p len %d }: " "ie %u len 2+%u > total len left %d\n", __func__, ies, ies->data, ies->len, ie[0], ie[1], ielen); return; } switch (ie[0]) { case IEEE80211_ELEMID_VENDOR: if (iswpaoui(ie)) ies->wpa_ie = ie; else if (iswmeoui(ie)) ies->wme_ie = ie; #ifdef IEEE80211_SUPPORT_SUPERG else if (isatherosoui(ie)) ies->ath_ie = ie; #endif #ifdef IEEE80211_SUPPORT_TDMA else if (istdmaoui(ie)) ies->tdma_ie = ie; #endif break; case IEEE80211_ELEMID_RSN: ies->rsn_ie = ie; break; case IEEE80211_ELEMID_HTCAP: ies->htcap_ie = ie; break; case IEEE80211_ELEMID_HTINFO: ies->htinfo_ie = ie; break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_ELEMID_MESHID: ies->meshid_ie = ie; break; #endif case IEEE80211_ELEMID_VHT_CAP: ies->vhtcap_ie = ie; break; case IEEE80211_ELEMID_VHT_OPMODE: ies->vhtopmode_ie = ie; break; case IEEE80211_ELEMID_VHT_PWR_ENV: ies->vhtpwrenv_ie = ie; break; case IEEE80211_ELEMID_BSSLOAD: ies->bssload_ie = ie; break; case IEEE80211_ELEMID_APCHANREP: ies->apchanrep_ie = ie; break; } ielen -= 2 + ie[1]; ie += 2 + ie[1]; } } /* * Reclaim any resources in a node and reset any critical * state. Typically nodes are free'd immediately after, * but in some cases the storage may be reused so we need * to insure consistent state (should probably fix that). */ static void node_cleanup(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; int i; /* NB: preserve ni_table */ if (ni->ni_flags & IEEE80211_NODE_PWR_MGT) { if (vap->iv_opmode != IEEE80211_M_STA) vap->iv_ps_sta--; ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "power save mode off, %u sta's in ps mode", vap->iv_ps_sta); } /* * Cleanup any VHT and HT-related state. */ if (ni->ni_flags & IEEE80211_NODE_VHT) ieee80211_vht_node_cleanup(ni); if (ni->ni_flags & IEEE80211_NODE_HT) ieee80211_ht_node_cleanup(ni); #ifdef IEEE80211_SUPPORT_SUPERG /* Always do FF node cleanup; for A-MSDU */ ieee80211_ff_node_cleanup(ni); #endif #ifdef IEEE80211_SUPPORT_MESH /* * Cleanup any mesh-related state. */ if (vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_node_cleanup(ni); #endif /* * Clear any staging queue entries. */ ieee80211_ageq_drain_node(&ic->ic_stageq, ni); /* * Clear AREF flag that marks the authorization refcnt bump * has happened. This is probably not needed as the node * should always be removed from the table so not found but * do it just in case. * Likewise clear the ASSOCID flag as these flags are intended * to be managed in tandem. */ ni->ni_flags &= ~(IEEE80211_NODE_AREF | IEEE80211_NODE_ASSOCID); /* * Drain power save queue and, if needed, clear TIM. */ if (ieee80211_node_psq_drain(ni) != 0 && vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); ni->ni_associd = 0; if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } /* * Preserve SSID, WPA, and WME ie's so the bss node is * reusable during a re-auth/re-assoc state transition. * If we remove these data they will not be recreated * because they come from a probe-response or beacon frame * which cannot be expected prior to the association-response. * This should not be an issue when operating in other modes * as stations leaving always go through a full state transition * which will rebuild this state. * * XXX does this leave us open to inheriting old state? */ for (i = 0; i < nitems(ni->ni_rxfrag); i++) if (ni->ni_rxfrag[i] != NULL) { m_freem(ni->ni_rxfrag[i]); ni->ni_rxfrag[i] = NULL; } /* * Must be careful here to remove any key map entry w/o a LOR. */ ieee80211_node_delucastkey(ni); } static void node_free(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; ieee80211_ratectl_node_deinit(ni); ic->ic_node_cleanup(ni); ieee80211_ies_cleanup(&ni->ni_ies); ieee80211_psq_cleanup(&ni->ni_psq); IEEE80211_FREE(ni, M_80211_NODE); } static void node_age(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; /* * Age frames on the power save queue. */ if (ieee80211_node_psq_age(ni) != 0 && ni->ni_psq.psq_len == 0 && vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); /* * Age out HT resources (e.g. frames on the * A-MPDU reorder queues). */ if (ni->ni_associd != 0 && (ni->ni_flags & IEEE80211_NODE_HT)) ieee80211_ht_node_age(ni); } static int8_t node_getrssi(const struct ieee80211_node *ni) { uint32_t avgrssi = ni->ni_avgrssi; int32_t rssi; if (avgrssi == IEEE80211_RSSI_DUMMY_MARKER) return 0; rssi = IEEE80211_RSSI_GET(avgrssi); return rssi < 0 ? 0 : rssi > 127 ? 127 : rssi; } static void node_getsignal(const struct ieee80211_node *ni, int8_t *rssi, int8_t *noise) { *rssi = node_getrssi(ni); *noise = ni->ni_noise; } static void node_getmimoinfo(const struct ieee80211_node *ni, struct ieee80211_mimo_info *info) { int i; uint32_t avgrssi; int32_t rssi; bzero(info, sizeof(*info)); for (i = 0; i < MIN(IEEE80211_MAX_CHAINS, ni->ni_mimo_chains); i++) { /* Note: for now, just pri20 channel info */ avgrssi = ni->ni_mimo_rssi_ctl[i]; if (avgrssi == IEEE80211_RSSI_DUMMY_MARKER) { info->ch[i].rssi[0] = 0; } else { rssi = IEEE80211_RSSI_GET(avgrssi); info->ch[i].rssi[0] = rssi < 0 ? 0 : rssi > 127 ? 127 : rssi; } info->ch[i].noise[0] = ni->ni_mimo_noise_ctl[i]; } /* XXX ext radios? */ /* XXX EVM? */ } static void ieee80211_add_node_nt(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { struct ieee80211com *ic = nt->nt_ic; int hash; IEEE80211_NODE_LOCK_ASSERT(nt); hash = IEEE80211_NODE_HASH(ic, ni->ni_macaddr); (void) ic; /* XXX IEEE80211_NODE_HASH */ TAILQ_INSERT_TAIL(&nt->nt_node, ni, ni_list); LIST_INSERT_HEAD(&nt->nt_hash[hash], ni, ni_hash); nt->nt_count++; ni->ni_table = nt; } static void ieee80211_del_node_nt(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { IEEE80211_NODE_LOCK_ASSERT(nt); TAILQ_REMOVE(&nt->nt_node, ni, ni_list); LIST_REMOVE(ni, ni_hash); nt->nt_count--; KASSERT(nt->nt_count >= 0, ("nt_count is negative (%d)!\n", nt->nt_count)); ni->ni_table = NULL; } static struct ieee80211_node * ieee80211_alloc_node(struct ieee80211_node_table *nt, struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211com *ic = nt->nt_ic; struct ieee80211_node *ni; ni = ic->ic_node_alloc(vap, macaddr); if (ni == NULL) { vap->iv_stats.is_rx_nodealloc++; return NULL; } IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s %p<%s> in %s table\n", __func__, ni, ether_sprintf(macaddr), nt->nt_name); IEEE80211_ADDR_COPY(ni->ni_macaddr, macaddr); ieee80211_node_initref(ni); /* mark referenced */ #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); #endif ni->ni_chan = IEEE80211_CHAN_ANYC; ni->ni_authmode = IEEE80211_AUTH_OPEN; ni->ni_txpower = ic->ic_txpowlimit; /* max power */ ni->ni_txparms = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; ieee80211_crypto_resetkey(vap, &ni->ni_ucastkey, IEEE80211_KEYIX_NONE); ni->ni_avgrssi = IEEE80211_RSSI_DUMMY_MARKER; ni->ni_inact_reload = nt->nt_inact_init; ni->ni_inact = ni->ni_inact_reload; ni->ni_ath_defkeyix = 0x7fff; ieee80211_psq_init(&ni->ni_psq, "unknown"); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_node_init(vap, ni); #endif IEEE80211_NODE_LOCK(nt); ieee80211_add_node_nt(nt, ni); ni->ni_vap = vap; ni->ni_ic = ic; IEEE80211_NODE_UNLOCK(nt); /* handle failure; free node state */ if (ic->ic_node_init(ni) != 0) { vap->iv_stats.is_rx_nodealloc++; ieee80211_psq_cleanup(&ni->ni_psq); ieee80211_ratectl_node_deinit(ni); __ieee80211_free_node(ni); return NULL; } IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact_reload %u", __func__, ni->ni_inact_reload); return ni; } /* * Craft a temporary node suitable for sending a management frame * to the specified station. We craft only as much state as we * need to do the work since the node will be immediately reclaimed * once the send completes. */ struct ieee80211_node * ieee80211_tmp_node(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; ni = ic->ic_node_alloc(vap, macaddr); if (ni != NULL) { struct ieee80211_node *bss = vap->iv_bss; IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s %p<%s>\n", __func__, ni, ether_sprintf(macaddr)); ni->ni_table = NULL; /* NB: pedantic */ ni->ni_ic = ic; /* NB: needed to set channel */ ni->ni_vap = vap; IEEE80211_ADDR_COPY(ni->ni_macaddr, macaddr); IEEE80211_ADDR_COPY(ni->ni_bssid, bss->ni_bssid); ieee80211_node_initref(ni); /* mark referenced */ #ifdef IEEE80211_DEBUG_REFCNT /* Only one caller so we skip func/line passing into the func. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, "", -1, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); #endif /* NB: required by ieee80211_fix_rate */ ieee80211_node_set_chan(ni, bss->ni_chan); ieee80211_crypto_resetkey(vap, &ni->ni_ucastkey, IEEE80211_KEYIX_NONE); ni->ni_txpower = bss->ni_txpower; /* XXX optimize away */ ieee80211_psq_init(&ni->ni_psq, "unknown"); ieee80211_ratectl_node_init(ni); /* handle failure; free node state */ if (ic->ic_node_init(ni) != 0) { vap->iv_stats.is_rx_nodealloc++; ieee80211_psq_cleanup(&ni->ni_psq); ieee80211_ratectl_node_deinit(ni); __ieee80211_free_node(ni); return NULL; } } else { /* XXX msg */ vap->iv_stats.is_rx_nodealloc++; } return ni; } struct ieee80211_node * ieee80211_dup_bss(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; ni = ieee80211_alloc_node(&ic->ic_sta, vap, macaddr, __func__, __LINE__); if (ni != NULL) { struct ieee80211_node *bss = vap->iv_bss; /* * Inherit from iv_bss. */ copy_bss(ni, bss); IEEE80211_ADDR_COPY(ni->ni_bssid, bss->ni_bssid); ieee80211_node_set_chan(ni, bss->ni_chan); } return ni; } /* * Create a bss node for a legacy WDS vap. The far end does * not associate so we just create create a new node and * simulate an association. The caller is responsible for * installing the node as the bss node and handling any further * setup work like authorizing the port. */ struct ieee80211_node * ieee80211_node_create_wds(struct ieee80211vap *vap, const uint8_t bssid[IEEE80211_ADDR_LEN], struct ieee80211_channel *chan) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; /* XXX check if node already in sta table? */ ni = ieee80211_alloc_node(&ic->ic_sta, vap, bssid, __func__, __LINE__); if (ni != NULL) { ni->ni_wdsvap = vap; IEEE80211_ADDR_COPY(ni->ni_bssid, bssid); /* * Inherit any manually configured settings. */ copy_bss(ni, vap->iv_bss); ieee80211_node_set_chan(ni, chan); /* NB: propagate ssid so available to WPA supplicant */ ni->ni_esslen = vap->iv_des_ssid[0].len; memcpy(ni->ni_essid, vap->iv_des_ssid[0].ssid, ni->ni_esslen); /* NB: no associd for peer */ /* * There are no management frames to use to * discover neighbor capabilities, so blindly * propagate the local configuration. */ if (vap->iv_flags & IEEE80211_F_WME) ni->ni_flags |= IEEE80211_NODE_QOS; #ifdef IEEE80211_SUPPORT_SUPERG if (vap->iv_flags & IEEE80211_F_FF) ni->ni_flags |= IEEE80211_NODE_FF; #endif /* XXX VHT */ if ((ic->ic_htcaps & IEEE80211_HTC_HT) && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { /* * Device is HT-capable and HT is enabled for * the vap; setup HT operation. On return * ni_chan will be adjusted to an HT channel. */ ieee80211_ht_wds_init(ni); if (vap->iv_vht_flags & IEEE80211_FVHT_VHT) { - printf("%s: TODO: vht_wds_init\n", __func__); + net80211_vap_printf(vap, + "%s: TODO: vht_wds_init\n", __func__); } } else { struct ieee80211_channel *c = ni->ni_chan; /* * Force a legacy channel to be used. */ c = ieee80211_find_channel(ic, c->ic_freq, c->ic_flags &~ IEEE80211_CHAN_HT); KASSERT(c != NULL, ("no legacy channel, %u/%x", ni->ni_chan->ic_freq, ni->ni_chan->ic_flags)); ni->ni_chan = c; } } return ni; } struct ieee80211_node * _ieee80211_find_node_locked(struct ieee80211_node_table *nt, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node *ni; int hash; IEEE80211_NODE_LOCK_ASSERT(nt); hash = IEEE80211_NODE_HASH(nt->nt_ic, macaddr); LIST_FOREACH(ni, &nt->nt_hash[hash], ni_hash) { if (IEEE80211_ADDR_EQ(ni->ni_macaddr, macaddr)) { ieee80211_ref_node(ni); /* mark referenced */ #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); #endif return ni; } } return NULL; } struct ieee80211_node * _ieee80211_find_node(struct ieee80211_node_table *nt, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node *ni; IEEE80211_NODE_LOCK(nt); ni = _ieee80211_find_node_locked(nt, macaddr, func, line); IEEE80211_NODE_UNLOCK(nt); return ni; } struct ieee80211_node * _ieee80211_find_vap_node_locked(struct ieee80211_node_table *nt, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node *ni; int hash; IEEE80211_NODE_LOCK_ASSERT(nt); hash = IEEE80211_NODE_HASH(nt->nt_ic, macaddr); LIST_FOREACH(ni, &nt->nt_hash[hash], ni_hash) { if (ni->ni_vap == vap && IEEE80211_ADDR_EQ(ni->ni_macaddr, macaddr)) { ieee80211_ref_node(ni); /* mark referenced */ #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); #endif return ni; } } return NULL; } struct ieee80211_node * _ieee80211_find_vap_node(struct ieee80211_node_table *nt, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node *ni; IEEE80211_NODE_LOCK(nt); ni = _ieee80211_find_vap_node_locked(nt, vap, macaddr, func, line); IEEE80211_NODE_UNLOCK(nt); return ni; } /* * Fake up a node; this handles node discovery in adhoc mode. * Note that for the driver's benefit we treat this like * an association so the driver has an opportunity to setup * it's private state. */ struct ieee80211_node * ieee80211_fakeup_adhoc_node(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211_node *ni; IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE | IEEE80211_MSG_ASSOC, "%s: mac<%s>\n", __func__, ether_sprintf(macaddr)); ni = ieee80211_dup_bss(vap, macaddr); if (ni != NULL) { struct ieee80211com *ic = vap->iv_ic; /* XXX no rate negotiation; just dup */ ni->ni_rates = vap->iv_bss->ni_rates; if (ieee80211_iserp_rateset(&ni->ni_rates)) ni->ni_flags |= IEEE80211_NODE_ERP; if (vap->iv_opmode == IEEE80211_M_AHDEMO) { /* * In adhoc demo mode there are no management * frames to use to discover neighbor capabilities, * so blindly propagate the local configuration * so we can do interesting things (e.g. use * WME to disable ACK's). */ /* * XXX TODO: 11n? */ if (vap->iv_flags & IEEE80211_F_WME) ni->ni_flags |= IEEE80211_NODE_QOS; #ifdef IEEE80211_SUPPORT_SUPERG if (vap->iv_flags & IEEE80211_F_FF) ni->ni_flags |= IEEE80211_NODE_FF; #endif } ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); /* * XXX TODO: 11n? At least 20MHz, at least A-MPDU RX, * not A-MPDU TX; not 11n rates, etc. We'll cycle * that after we hear that we can indeed do 11n * (either by a beacon frame or by a probe response.) */ /* * This is the first time we see the node. */ if (ic->ic_newassoc != NULL) ic->ic_newassoc(ni, 1); /* * Kick off a probe request to the given node; * we will then use the probe response to update * 11n/etc configuration state. * * XXX TODO: this isn't guaranteed, and until we get * a probe response, we won't be able to actually * do anything 802.11n related to the node. * So if this does indeed work, maybe we should hold * off on sending responses until we get the probe * response, or just default to some sensible subset * of 802.11n behaviour (eg always allow aggregation * negotiation TO us, but not FROM us, etc) so we * aren't entirely busted. */ if (vap->iv_opmode == IEEE80211_M_IBSS) { ieee80211_send_probereq(ni, /* node */ vap->iv_myaddr, /* SA */ ni->ni_macaddr, /* DA */ vap->iv_bss->ni_bssid, /* BSSID */ vap->iv_bss->ni_essid, vap->iv_bss->ni_esslen); /* SSID */ } /* XXX not right for 802.1x/WPA */ ieee80211_node_authorize(ni); } return ni; } void ieee80211_init_neighbor(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const struct ieee80211_scanparams *sp) { int do_ht_setup = 0, do_vht_setup = 0; ni->ni_esslen = sp->ssid[1]; memcpy(ni->ni_essid, sp->ssid + 2, sp->ssid[1]); IEEE80211_ADDR_COPY(ni->ni_bssid, wh->i_addr3); memcpy(ni->ni_tstamp.data, sp->tstamp, sizeof(ni->ni_tstamp)); ni->ni_intval = sp->bintval; ni->ni_capinfo = sp->capinfo; ni->ni_chan = ni->ni_ic->ic_curchan; ni->ni_fhdwell = sp->fhdwell; ni->ni_fhindex = sp->fhindex; ni->ni_erp = sp->erp; ni->ni_timoff = sp->timoff; #ifdef IEEE80211_SUPPORT_MESH if (ni->ni_vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_init_neighbor(ni, wh, sp); #endif if (ieee80211_ies_init(&ni->ni_ies, sp->ies, sp->ies_len)) { ieee80211_ies_expand(&ni->ni_ies); if (ni->ni_ies.wme_ie != NULL) ni->ni_flags |= IEEE80211_NODE_QOS; else ni->ni_flags &= ~IEEE80211_NODE_QOS; #ifdef IEEE80211_SUPPORT_SUPERG if (ni->ni_ies.ath_ie != NULL) ieee80211_parse_ath(ni, ni->ni_ies.ath_ie); #endif if (ni->ni_ies.htcap_ie != NULL) ieee80211_parse_htcap(ni, ni->ni_ies.htcap_ie); if (ni->ni_ies.htinfo_ie != NULL) ieee80211_parse_htinfo(ni, ni->ni_ies.htinfo_ie); if (ni->ni_ies.vhtcap_ie != NULL) ieee80211_parse_vhtcap(ni, ni->ni_ies.vhtcap_ie); if (ni->ni_ies.vhtopmode_ie != NULL) ieee80211_parse_vhtopmode(ni, ni->ni_ies.vhtopmode_ie); if ((ni->ni_ies.htcap_ie != NULL) && (ni->ni_ies.htinfo_ie != NULL) && (ni->ni_vap->iv_flags_ht & IEEE80211_FHT_HT)) { do_ht_setup = 1; } if ((ni->ni_ies.vhtcap_ie != NULL) && (ni->ni_ies.vhtopmode_ie != NULL) && (ni->ni_vap->iv_vht_flags & IEEE80211_FVHT_VHT)) { do_vht_setup = 1; } } /* NB: must be after ni_chan is setup */ ieee80211_setup_rates(ni, sp->rates, sp->xrates, IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); /* * If the neighbor is HT compatible, flip that on. */ if (do_ht_setup) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: doing HT setup\n", __func__); ieee80211_ht_node_init(ni); ieee80211_ht_updateparams(ni, ni->ni_ies.htcap_ie, ni->ni_ies.htinfo_ie); if (do_vht_setup) { if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) { - printf("%s: BSS %6D: 2GHz channel, VHT info; ignoring\n", - __func__, - ni->ni_macaddr, - ":"); + net80211_vap_printf(ni->ni_vap, + "%s: BSS %6D: 2GHz channel, VHT info; ignoring\n", + __func__, ni->ni_macaddr, ":"); } else { ieee80211_vht_node_init(ni); ieee80211_vht_updateparams(ni, ni->ni_ies.vhtcap_ie, ni->ni_ies.vhtopmode_ie); ieee80211_setup_vht_rates(ni); } } /* * Finally do the channel upgrade/change based * on the HT/VHT configuration. */ ieee80211_ht_updateparams_final(ni, ni->ni_ies.htcap_ie, ni->ni_ies.htinfo_ie); ieee80211_setup_htrates(ni, ni->ni_ies.htcap_ie, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, ni->ni_ies.htinfo_ie); ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); /* Reassociate; we're now 11n/11ac */ /* * XXX TODO: this is the wrong thing to do - * we're calling it with isnew=1 so the ath(4) * driver reinitialises the rate tables. * This "mostly" works for ath(4), but it won't * be right for firmware devices which allocate * node states. * * So, do we just create a new node and delete * the old one? Or? */ if (ni->ni_ic->ic_newassoc) ni->ni_ic->ic_newassoc(ni, 1); } } /* * Do node discovery in adhoc mode on receipt of a beacon * or probe response frame. Note that for the driver's * benefit we treat this like an association so the * driver has an opportunity to setup it's private state. */ struct ieee80211_node * ieee80211_add_neighbor(struct ieee80211vap *vap, const struct ieee80211_frame *wh, const struct ieee80211_scanparams *sp) { struct ieee80211_node *ni; IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s: mac<%s>\n", __func__, ether_sprintf(wh->i_addr2)); ni = ieee80211_dup_bss(vap, wh->i_addr2);/* XXX alloc_node? */ if (ni != NULL) { struct ieee80211com *ic = vap->iv_ic; ieee80211_init_neighbor(ni, wh, sp); if (ieee80211_iserp_rateset(&ni->ni_rates)) ni->ni_flags |= IEEE80211_NODE_ERP; ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); if (ic->ic_newassoc != NULL) ic->ic_newassoc(ni, 1); /* XXX not right for 802.1x/WPA */ ieee80211_node_authorize(ni); } return ni; } #define IS_PROBEREQ(wh) \ ((wh->i_fc[0] & (IEEE80211_FC0_TYPE_MASK|IEEE80211_FC0_SUBTYPE_MASK)) \ == (IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_REQ)) #define IS_BCAST_PROBEREQ(wh) \ (IS_PROBEREQ(wh) && IEEE80211_IS_MULTICAST( \ ((const struct ieee80211_frame *)(wh))->i_addr3)) static __inline struct ieee80211_node * _find_rxnode(struct ieee80211_node_table *nt, const struct ieee80211_frame_min *wh, const char *func __debrefcnt_used, int line __debrefcnt_used) { if (IS_BCAST_PROBEREQ(wh)) return NULL; /* spam bcast probe req to all vap's */ return _ieee80211_find_node_locked(nt, wh->i_addr2, func, line); } /* * Locate the node for sender, track state, and then pass the * (referenced) node up to the 802.11 layer for its use. Note * we can return NULL if the sender is not in the table. */ struct ieee80211_node * _ieee80211_find_rxnode(struct ieee80211com *ic, const struct ieee80211_frame_min *wh, const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node_table *nt; struct ieee80211_node *ni; nt = &ic->ic_sta; IEEE80211_NODE_LOCK(nt); ni = _find_rxnode(nt, wh, func, line); IEEE80211_NODE_UNLOCK(nt); return ni; } /* * Like ieee80211_find_rxnode but use the supplied h/w * key index as a hint to locate the node in the key * mapping table. If an entry is present at the key * index we return it; otherwise do a normal lookup and * update the mapping table if the station has a unicast * key assigned to it. */ struct ieee80211_node * _ieee80211_find_rxnode_withkey(struct ieee80211com *ic, const struct ieee80211_frame_min *wh, ieee80211_keyix keyix, const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node_table *nt; struct ieee80211_node *ni; nt = &ic->ic_sta; IEEE80211_NODE_LOCK(nt); if (nt->nt_keyixmap != NULL && keyix < nt->nt_keyixmax) ni = nt->nt_keyixmap[keyix]; else ni = NULL; if (ni == NULL) { ni = _find_rxnode(nt, wh, func, line); if (ni != NULL && nt->nt_keyixmap != NULL) { /* * If the station has a unicast key cache slot * assigned update the key->node mapping table. */ keyix = ni->ni_ucastkey.wk_rxkeyix; /* XXX can keyixmap[keyix] != NULL? */ if (keyix < nt->nt_keyixmax && nt->nt_keyixmap[keyix] == NULL) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: add key map entry %p<%s> refcnt %d\n", __func__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); nt->nt_keyixmap[keyix] = ieee80211_ref_node(ni); } } } else { if (IS_BCAST_PROBEREQ(wh)) ni = NULL; /* spam bcast probe req to all vap's */ else ieee80211_ref_node(ni); } IEEE80211_NODE_UNLOCK(nt); return ni; } #undef IS_BCAST_PROBEREQ #undef IS_PROBEREQ /* * Return a reference to the appropriate node for sending * a data frame. This handles node discovery in adhoc networks. */ struct ieee80211_node * _ieee80211_find_txnode(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta; struct ieee80211_node *ni; /* * The destination address should be in the node table * unless this is a multicast/broadcast frame. We can * also optimize station mode operation, all frames go * to the bss node. */ /* XXX can't hold lock across dup_bss 'cuz of recursive locking */ IEEE80211_NODE_LOCK(nt); if (vap->iv_opmode == IEEE80211_M_STA || vap->iv_opmode == IEEE80211_M_WDS || IEEE80211_IS_MULTICAST(macaddr)) ni = ieee80211_ref_node(vap->iv_bss); else ni = _ieee80211_find_node_locked(nt, macaddr, func, line); IEEE80211_NODE_UNLOCK(nt); if (ni == NULL) { if (vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_AHDEMO) { /* * In adhoc mode cons up a node for the destination. * Note that we need an additional reference for the * caller to be consistent with * ieee80211_find_node_locked. */ /* * XXX TODO: this doesn't fake up 11n state; we need * to find another way to get it upgraded. */ ni = ieee80211_fakeup_adhoc_node(vap, macaddr); if (ni != NULL) (void) ieee80211_ref_node(ni); } else { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_OUTPUT, macaddr, "no node, discard frame (%s)", __func__); vap->iv_stats.is_tx_nonode++; } } return ni; } struct ieee80211_node * _ieee80211_ref_node(struct ieee80211_node *ni, const char *func __debrefcnt_used, int line __debrefcnt_used) { #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); #endif ieee80211_node_incref(ni); return (ni); } static void __ieee80211_free_node(struct ieee80211_node *ni) { struct ieee80211_node_table *nt = ni->ni_table; /* * NB: careful about referencing the vap as it may be * gone if the last reference was held by a driver. * We know the com will always be present so it's safe * to use ni_ic below to reclaim resources. */ #if 0 IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s %p<%s> in %s table\n", __func__, ni, ether_sprintf(ni->ni_macaddr), nt != NULL ? nt->nt_name : ""); #endif if (ni->ni_associd != 0) { struct ieee80211vap *vap = ni->ni_vap; if (vap->iv_aid_bitmap != NULL) IEEE80211_AID_CLR(vap, ni->ni_associd); } if (nt != NULL) ieee80211_del_node_nt(nt, ni); ni->ni_ic->ic_node_free(ni); } /* * Clear any entry in the unicast key mapping table. */ static int node_clear_keyixmap(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { ieee80211_keyix keyix; keyix = ni->ni_ucastkey.wk_rxkeyix; if (nt->nt_keyixmap != NULL && keyix < nt->nt_keyixmax && nt->nt_keyixmap[keyix] == ni) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: %p<%s> clear key map entry %u\n", __func__, ni, ether_sprintf(ni->ni_macaddr), keyix); nt->nt_keyixmap[keyix] = NULL; ieee80211_node_decref(ni); return 1; } return 0; } void _ieee80211_free_node(struct ieee80211_node *ni, const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node_table *nt = ni->ni_table; #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)-1); #endif if (nt != NULL) { IEEE80211_NODE_LOCK(nt); if (ieee80211_node_dectestref(ni)) { /* * Last reference, reclaim state. */ __ieee80211_free_node(ni); } else if (ieee80211_node_refcnt(ni) == 1) if (node_clear_keyixmap(nt, ni)) __ieee80211_free_node(ni); IEEE80211_NODE_UNLOCK(nt); } else { if (ieee80211_node_dectestref(ni)) __ieee80211_free_node(ni); } } /* * Reclaim a unicast key and clear any key cache state. */ int ieee80211_node_delucastkey(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211_node_table *nt = &ic->ic_sta; struct ieee80211_node *nikey; ieee80211_keyix keyix; int isowned, status; /* * NB: We must beware of LOR here; deleting the key * can cause the crypto layer to block traffic updates * which can generate a LOR against the node table lock; * grab it here and stash the key index for our use below. * * Must also beware of recursion on the node table lock. * When called from node_cleanup we may already have * the node table lock held. Unfortunately there's no * way to separate out this path so we must do this * conditionally. */ isowned = IEEE80211_NODE_IS_LOCKED(nt); if (!isowned) IEEE80211_NODE_LOCK(nt); nikey = NULL; status = 1; /* NB: success */ if (!IEEE80211_KEY_UNDEFINED(&ni->ni_ucastkey)) { keyix = ni->ni_ucastkey.wk_rxkeyix; status = ieee80211_crypto_delkey(ni->ni_vap, &ni->ni_ucastkey); if (nt->nt_keyixmap != NULL && keyix < nt->nt_keyixmax) { nikey = nt->nt_keyixmap[keyix]; nt->nt_keyixmap[keyix] = NULL; } } if (!isowned) IEEE80211_NODE_UNLOCK(nt); if (nikey != NULL) { KASSERT(nikey == ni, ("key map out of sync, ni %p nikey %p", ni, nikey)); IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: delete key map entry %p<%s> refcnt %d\n", __func__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)-1); ieee80211_free_node(ni); } return status; } /* * Reclaim a node. If this is the last reference count then * do the normal free work. Otherwise remove it from the node * table and mark it gone by clearing the back-reference. */ static void node_reclaim(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { IEEE80211_NODE_LOCK_ASSERT(nt); IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: remove %p<%s> from %s table, refcnt %d\n", __func__, ni, ether_sprintf(ni->ni_macaddr), nt->nt_name, ieee80211_node_refcnt(ni)-1); /* * Clear any entry in the unicast key mapping table. * We need to do it here so rx lookups don't find it * in the mapping table even if it's not in the hash * table. We cannot depend on the mapping table entry * being cleared because the node may not be free'd. */ (void)node_clear_keyixmap(nt, ni); if (!ieee80211_node_dectestref(ni)) { /* * Other references are present, just remove the * node from the table so it cannot be found. When * the references are dropped storage will be * reclaimed. */ ieee80211_del_node_nt(nt, ni); } else __ieee80211_free_node(ni); } /* * Node table support. */ static void ieee80211_node_table_init(struct ieee80211com *ic, struct ieee80211_node_table *nt, const char *name, int inact, int keyixmax) { nt->nt_ic = ic; IEEE80211_NODE_LOCK_INIT(nt, ic->ic_name); TAILQ_INIT(&nt->nt_node); nt->nt_count = 0; nt->nt_name = name; nt->nt_inact_init = inact; nt->nt_keyixmax = keyixmax; if (nt->nt_keyixmax > 0) { nt->nt_keyixmap = (struct ieee80211_node **) IEEE80211_MALLOC( keyixmax * sizeof(struct ieee80211_node *), M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (nt->nt_keyixmap == NULL) ic_printf(ic, "Cannot allocate key index map with %u entries\n", keyixmax); } else nt->nt_keyixmap = NULL; } static void ieee80211_node_table_reset(struct ieee80211_node_table *nt, struct ieee80211vap *match) { struct ieee80211_node *ni, *next; IEEE80211_NODE_LOCK(nt); TAILQ_FOREACH_SAFE(ni, &nt->nt_node, ni_list, next) { if (match != NULL && ni->ni_vap != match) continue; /* XXX can this happen? if so need's work */ if (ni->ni_associd != 0) { struct ieee80211vap *vap = ni->ni_vap; if (vap->iv_auth->ia_node_leave != NULL) vap->iv_auth->ia_node_leave(ni); if (vap->iv_aid_bitmap != NULL) IEEE80211_AID_CLR(vap, ni->ni_associd); } ni->ni_wdsvap = NULL; /* clear reference */ node_reclaim(nt, ni); } if (match != NULL && match->iv_opmode == IEEE80211_M_WDS) { /* * Make a separate pass to clear references to this vap * held by DWDS entries. They will not be matched above * because ni_vap will point to the ap vap but we still * need to clear ni_wdsvap when the WDS vap is destroyed * and/or reset. */ TAILQ_FOREACH_SAFE(ni, &nt->nt_node, ni_list, next) if (ni->ni_wdsvap == match) ni->ni_wdsvap = NULL; } IEEE80211_NODE_UNLOCK(nt); } static void ieee80211_node_table_cleanup(struct ieee80211_node_table *nt) { ieee80211_node_table_reset(nt, NULL); if (nt->nt_keyixmap != NULL) { #ifdef DIAGNOSTIC /* XXX verify all entries are NULL */ int i; for (i = 0; i < nt->nt_keyixmax; i++) if (nt->nt_keyixmap[i] != NULL) - printf("%s: %s[%u] still active\n", __func__, + net80211_printf("%s: %s[%u] still active\n", __func__, nt->nt_name, i); #endif IEEE80211_FREE(nt->nt_keyixmap, M_80211_NODE); nt->nt_keyixmap = NULL; } IEEE80211_NODE_LOCK_DESTROY(nt); } static void timeout_stations(void *arg __unused, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; /* * Only process stations when in RUN state. This * insures, for example, that we don't timeout an * inactive station during CAC. Note that CSA state * is actually handled in ieee80211_node_timeout as * it applies to more than timeout processing. */ if (vap->iv_state != IEEE80211_S_RUN) return; /* * Ignore entries for which have yet to receive an * authentication frame. These are transient and * will be reclaimed when the last reference to them * goes away (when frame xmits complete). */ if ((vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_STA) && (ni->ni_flags & IEEE80211_NODE_AREF) == 0) return; /* * Free fragment if not needed anymore * (last fragment older than 1s). * XXX doesn't belong here, move to node_age */ if (ni->ni_rxfrag[0] != NULL && ticks > ni->ni_rxfragstamp + hz) { m_freem(ni->ni_rxfrag[0]); ni->ni_rxfrag[0] = NULL; } if (ni->ni_inact > 0) { ni->ni_inact--; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact %u inact_reload %u nrates %u", __func__, ni->ni_inact, ni->ni_inact_reload, ni->ni_rates.rs_nrates); } /* * Special case ourself; we may be idle for extended periods * of time and regardless reclaiming our state is wrong. * XXX run ic_node_age */ /* XXX before inact decrement? */ if (ni == vap->iv_bss) return; if (ni->ni_associd != 0 || (vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_AHDEMO)) { /* * Age/drain resources held by the station. */ ic->ic_node_age(ni); /* * Probe the station before time it out. We * send a null data frame which may not be * universally supported by drivers (need it * for ps-poll support so it should be...). * * XXX don't probe the station unless we've * received a frame from them (and have * some idea of the rates they are capable * of); this will get fixed more properly * soon with better handling of the rate set. */ if ((vap->iv_flags_ext & IEEE80211_FEXT_INACT) && (0 < ni->ni_inact && ni->ni_inact <= vap->iv_inact_probe) && ni->ni_rates.rs_nrates != 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_INACT | IEEE80211_MSG_NODE, ni, "%s", "probe station due to inactivity"); /* * Grab a reference so the node cannot * be reclaimed before we send the frame. * ieee80211_send_nulldata understands * we've done this and reclaims the * ref for us as needed. */ /* XXX fix this (not required anymore). */ ieee80211_ref_node(ni); /* XXX useless */ ieee80211_send_nulldata(ni); /* XXX stat? */ return; } } if ((vap->iv_flags_ext & IEEE80211_FEXT_INACT) && ni->ni_inact <= 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_INACT | IEEE80211_MSG_NODE, ni, "station timed out due to inactivity " "(refcnt %u)", ieee80211_node_refcnt(ni)); /* * Send a deauthenticate frame and drop the station. * This is somewhat complicated due to reference counts * and locking. At this point a station will typically * have a reference count of 2. ieee80211_node_leave * will do a "free" of the node which will drop the * reference count. But in the meantime a reference * wil be held by the deauth frame. The actual reclaim * of the node will happen either after the tx is * completed or by ieee80211_node_leave. */ if (ni->ni_associd != 0) { IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_AUTH_EXPIRE); } ieee80211_node_leave(ni); vap->iv_stats.is_node_timeout++; } } /* * Timeout inactive stations and do related housekeeping. */ static void ieee80211_timeout_stations(struct ieee80211com *ic) { struct ieee80211_node_table *nt = &ic->ic_sta; ieee80211_iterate_nodes(nt, timeout_stations, NULL); } /* * Aggressively reclaim resources. This should be used * only in a critical situation to reclaim mbuf resources. */ void ieee80211_drain(struct ieee80211com *ic) { struct ieee80211_node_table *nt = &ic->ic_sta; struct ieee80211vap *vap; struct ieee80211_node *ni; IEEE80211_NODE_LOCK(nt); TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { /* * Ignore entries for which have yet to receive an * authentication frame. These are transient and * will be reclaimed when the last reference to them * goes away (when frame xmits complete). */ vap = ni->ni_vap; /* * Only process stations when in RUN state. This * insures, for example, that we don't timeout an * inactive station during CAC. Note that CSA state * is actually handled in ieee80211_node_timeout as * it applies to more than timeout processing. */ if (vap->iv_state != IEEE80211_S_RUN) continue; /* XXX can vap be NULL? */ if ((vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_STA) && (ni->ni_flags & IEEE80211_NODE_AREF) == 0) continue; /* * Free fragments. * XXX doesn't belong here, move to node_drain */ if (ni->ni_rxfrag[0] != NULL) { m_freem(ni->ni_rxfrag[0]); ni->ni_rxfrag[0] = NULL; } /* * Drain resources held by the station. */ ic->ic_node_drain(ni); } IEEE80211_NODE_UNLOCK(nt); } /* * Per-ieee80211vap inactivity timer callback. */ static void ieee80211_vap_timeout(struct ieee80211vap *vap) { IEEE80211_LOCK_ASSERT(vap->iv_ic); ieee80211_vap_erp_timeout(vap); ieee80211_ht_timeout(vap); ieee80211_vht_timeout(vap); } /* * Per-ieee80211com inactivity timer callback. */ void ieee80211_node_timeout(void *arg) { struct ieee80211com *ic = arg; struct ieee80211vap *vap; /* * Defer timeout processing if a channel switch is pending. * We typically need to be mute so not doing things that * might generate frames is good to handle in one place. * Suppressing the station timeout processing may extend the * lifetime of inactive stations (by not decrementing their * idle counters) but this should be ok unless the CSA is * active for an unusually long time. */ if ((ic->ic_flags & IEEE80211_F_CSAPENDING) == 0) { ieee80211_scan_timeout(ic); ieee80211_timeout_stations(ic); ieee80211_ageq_age(&ic->ic_stageq, IEEE80211_INACT_WAIT); IEEE80211_LOCK(ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) ieee80211_vap_timeout(vap); IEEE80211_UNLOCK(ic); } callout_reset(&ic->ic_inact, IEEE80211_INACT_WAIT*hz, ieee80211_node_timeout, ic); } /* * The same as ieee80211_iterate_nodes(), but for one vap only. */ int ieee80211_iterate_nodes_vap(struct ieee80211_node_table *nt, struct ieee80211vap *vap, ieee80211_iter_func *f, void *arg) { struct ieee80211_node **ni_arr; struct ieee80211_node *ni; size_t size; int count, i; /* * Iterate over the node table and save an array of ref'ed nodes. * * This is separated out from calling the actual node function so that * no LORs will occur. */ IEEE80211_NODE_LOCK(nt); count = nt->nt_count; size = count * sizeof(struct ieee80211_node *); ni_arr = (struct ieee80211_node **) IEEE80211_MALLOC(size, M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (ni_arr == NULL) { IEEE80211_NODE_UNLOCK(nt); return (ENOMEM); } i = 0; TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { if (vap != NULL && ni->ni_vap != vap) continue; KASSERT(i < count, ("node array overflow (vap %p, i %d, count %d)\n", vap, i, count)); ni_arr[i] = ieee80211_ref_node(ni); i++; } IEEE80211_NODE_UNLOCK(nt); for (i = 0; i < count; i++) { if (ni_arr[i] == NULL) /* end of the list */ break; (*f)(arg, ni_arr[i]); /* ieee80211_free_node() locks by itself */ ieee80211_free_node(ni_arr[i]); } IEEE80211_FREE(ni_arr, M_80211_NODE); return (0); } /* * Just a wrapper, so we don't have to change every ieee80211_iterate_nodes() * reference in the source. */ void ieee80211_iterate_nodes(struct ieee80211_node_table *nt, ieee80211_iter_func *f, void *arg) { /* XXX no way to pass error to the caller. */ (void) ieee80211_iterate_nodes_vap(nt, NULL, f, arg); } void ieee80211_dump_node(struct ieee80211_node_table *nt __unused, struct ieee80211_node *ni) { - printf("%p: mac %s refcnt %d\n", ni, + net80211_printf("%p: mac %s refcnt %d\n", ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); - printf("\tauthmode %u flags 0x%x\n", + net80211_printf("\tauthmode %u flags 0x%x\n", ni->ni_authmode, ni->ni_flags); - printf("\tassocid 0x%x txpower %u vlan %u\n", + net80211_printf("\tassocid 0x%x txpower %u vlan %u\n", ni->ni_associd, ni->ni_txpower, ni->ni_vlan); - printf("\ttxseq %u rxseq %u fragno %u rxfragstamp %u\n", + net80211_printf("\ttxseq %u rxseq %u fragno %u rxfragstamp %u\n", ni->ni_txseqs[IEEE80211_NONQOS_TID], ni->ni_rxseqs[IEEE80211_NONQOS_TID] >> IEEE80211_SEQ_SEQ_SHIFT, ni->ni_rxseqs[IEEE80211_NONQOS_TID] & IEEE80211_SEQ_FRAG_MASK, ni->ni_rxfragstamp); - printf("\trssi %d noise %d intval %u capinfo 0x%x\n", + net80211_printf("\trssi %d noise %d intval %u capinfo 0x%x\n", node_getrssi(ni), ni->ni_noise, ni->ni_intval, ni->ni_capinfo); - printf("\tbssid %s essid \"%.*s\" channel %u:0x%x\n", + net80211_printf("\tbssid %s essid \"%.*s\" channel %u:0x%x\n", ether_sprintf(ni->ni_bssid), ni->ni_esslen, ni->ni_essid, (ni->ni_chan != IEEE80211_CHAN_ANYC) ? ni->ni_chan->ic_freq : 0, (ni->ni_chan != IEEE80211_CHAN_ANYC) ? ni->ni_chan->ic_flags : 0); - printf("\tinact %u inact_reload %u txrate type %d dot11rate %u\n", + net80211_printf("\tinact %u inact_reload %u txrate type %d dot11rate %u\n", ni->ni_inact, ni->ni_inact_reload, ni->ni_txrate.type, ni->ni_txrate.dot11rate); - printf("\thtcap %x htparam %x htctlchan %u ht2ndchan %u\n", + net80211_printf("\thtcap %x htparam %x htctlchan %u ht2ndchan %u\n", ni->ni_htcap, ni->ni_htparam, ni->ni_htctlchan, ni->ni_ht2ndchan); - printf("\thtopmode %x htstbc %x htchw %d (%s)\n", + net80211_printf("\thtopmode %x htstbc %x htchw %d (%s)\n", ni->ni_htopmode, ni->ni_htstbc, ni->ni_chw, ieee80211_ni_chw_to_str(ni->ni_chw)); - printf("\tvhtcap %x freq1 %d freq2 %d vhtbasicmcs %x\n", + net80211_printf("\tvhtcap %x freq1 %d freq2 %d vhtbasicmcs %x\n", ni->ni_vhtcap, (int) ni->ni_vht_chan1, (int) ni->ni_vht_chan2, (int) ni->ni_vht_basicmcs); /* XXX VHT state */ } void ieee80211_dump_nodes(struct ieee80211_node_table *nt) { ieee80211_iterate_nodes(nt, (ieee80211_iter_func *) ieee80211_dump_node, nt); } /* * Iterate over the VAPs and update their ERP beacon IEs. * * Note this must be called from the deferred ERP update task paths. */ void ieee80211_notify_erp_locked(struct ieee80211com *ic) { struct ieee80211vap *vap; IEEE80211_LOCK_ASSERT(ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_opmode == IEEE80211_M_HOSTAP) ieee80211_beacon_notify(vap, IEEE80211_BEACON_ERP); } /* * Handle a station joining an 11g network. */ static void ieee80211_node_join_11g(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; IEEE80211_LOCK_ASSERT(ic); /* * Station isn't capable of short slot time. Bump * the count of long slot time stations and disable * use of short slot time. Note that the actual switch * over to long slot time use may not occur until the * next beacon transmission (per sec. 7.3.1.4 of 11g). */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) == 0) { vap->iv_longslotsta++; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "station needs long slot time, count %d", vap->iv_longslotsta); /* * XXX TODO: this may need all VAPs checked! */ if (!IEEE80211_IS_CHAN_108G(ic->ic_bsschan)) { /* * Don't force slot time when switched to turbo * mode as non-ERP stations won't be present; this * need only be done when on the normal G channel. */ ieee80211_vap_set_shortslottime(vap, 0); } } /* * If the new station is not an ERP station * then bump the counter and enable protection * if configured. */ if (!ieee80211_iserp_rateset(&ni->ni_rates)) { vap->iv_nonerpsta++; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "station is !ERP, %d non-ERP stations associated", vap->iv_nonerpsta); /* * If station does not support short preamble * then we must enable use of Barker preamble. */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE) == 0) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "%s", "station needs long preamble"); vap->iv_flags |= IEEE80211_F_USEBARKER; vap->iv_flags &= ~IEEE80211_F_SHPREAMBLE; ieee80211_vap_update_preamble(vap); } /* * If protection is configured and this is the first * indication we should use protection, enable it. */ if (vap->iv_protmode != IEEE80211_PROT_NONE && vap->iv_nonerpsta == 1 && (vap->iv_flags_ext & IEEE80211_FEXT_NONERP_PR) == 0) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: enable use of protection\n", __func__); vap->iv_flags |= IEEE80211_F_USEPROT; ieee80211_vap_update_erp_protmode(vap); } } else ni->ni_flags |= IEEE80211_NODE_ERP; } void ieee80211_node_join(struct ieee80211_node *ni, int resp) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; int newassoc; if (ni->ni_associd == 0) { uint16_t aid; KASSERT(vap->iv_aid_bitmap != NULL, ("no aid bitmap")); /* * It would be good to search the bitmap * more efficiently, but this will do for now. */ for (aid = 1; aid < vap->iv_max_aid; aid++) { if (!IEEE80211_AID_ISSET(vap, aid)) break; } if (aid >= vap->iv_max_aid) { IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_TOOMANY); ieee80211_node_leave(ni); return; } ni->ni_associd = aid | 0xc000; ni->ni_jointime = time_uptime; IEEE80211_LOCK(ic); IEEE80211_AID_SET(vap, ni->ni_associd); vap->iv_sta_assoc++; if (IEEE80211_IS_CHAN_HT(ic->ic_bsschan)) ieee80211_ht_node_join(ni); if (IEEE80211_IS_CHAN_VHT(ic->ic_bsschan)) ieee80211_vht_node_join(ni); if (IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && IEEE80211_IS_CHAN_FULL(ic->ic_bsschan)) ieee80211_node_join_11g(ni); IEEE80211_UNLOCK(ic); newassoc = 1; } else newassoc = 0; /* * XXX VHT - should log VHT channel width, etc */ IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, ni, "station associated at aid %d: %s preamble, %s slot time%s%s%s%s%s%s%s%s%s", IEEE80211_NODE_AID(ni), vap->iv_flags & IEEE80211_F_SHPREAMBLE ? "short" : "long", vap->iv_flags & IEEE80211_F_SHSLOT ? "short" : "long", vap->iv_flags & IEEE80211_F_USEPROT ? ", protection" : "", ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : "", /* XXX update for VHT string */ ni->ni_flags & IEEE80211_NODE_HT ? (ni->ni_chw == IEEE80211_STA_RX_BW_40 ? ", HT40" : ", HT20") : "", ni->ni_flags & IEEE80211_NODE_AMPDU ? " (+AMPDU)" : "", ni->ni_flags & IEEE80211_NODE_AMSDU ? " (+AMSDU)" : "", ni->ni_flags & IEEE80211_NODE_MIMO_RTS ? " (+SMPS-DYN)" : ni->ni_flags & IEEE80211_NODE_MIMO_PS ? " (+SMPS)" : "", ni->ni_flags & IEEE80211_NODE_RIFS ? " (+RIFS)" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF) ? ", fast-frames" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_TURBOP) ? ", turbo" : "" ); ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); /* give driver a chance to setup state like ni_txrate */ if (ic->ic_newassoc != NULL) ic->ic_newassoc(ni, newassoc); IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_SUCCESS); /* tell the authenticator about new station */ if (vap->iv_auth->ia_node_join != NULL) vap->iv_auth->ia_node_join(ni); ieee80211_notify_node_join(ni, resp == IEEE80211_FC0_SUBTYPE_ASSOC_RESP); } static void disable_protection(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; KASSERT(vap->iv_nonerpsta == 0 && (vap->iv_flags_ext & IEEE80211_FEXT_NONERP_PR) == 0, ("%d non ERP stations, flags 0x%x", vap->iv_nonerpsta, vap->iv_flags_ext)); vap->iv_flags &= ~IEEE80211_F_USEPROT; /* XXX verify mode? */ if (ic->ic_caps & IEEE80211_C_SHPREAMBLE) { vap->iv_flags |= IEEE80211_F_SHPREAMBLE; vap->iv_flags &= ~IEEE80211_F_USEBARKER; } ieee80211_vap_update_erp_protmode(vap); ieee80211_vap_update_preamble(vap); } /* * Handle a station leaving an 11g network. */ static void ieee80211_node_leave_11g(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; IEEE80211_LOCK_ASSERT(ic); KASSERT(IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan), ("not in 11g, bss %u:0x%x", ic->ic_bsschan->ic_freq, ic->ic_bsschan->ic_flags)); /* * If a long slot station do the slot time bookkeeping. */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) == 0) { KASSERT(vap->iv_longslotsta > 0, ("bogus long slot station count %d", vap->iv_longslotsta)); vap->iv_longslotsta--; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "long slot time station leaves, count now %d", vap->iv_longslotsta); /* * XXX TODO: this may need all VAPs checked! */ if (vap->iv_longslotsta == 0) { /* * Re-enable use of short slot time if supported * and not operating in IBSS mode (per spec). */ if ((ic->ic_caps & IEEE80211_C_SHSLOT) && ic->ic_opmode != IEEE80211_M_IBSS) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: re-enable use of short slot time\n", __func__); ieee80211_vap_set_shortslottime(vap, 1); } } } /* * If a non-ERP station do the protection-related bookkeeping. */ if ((ni->ni_flags & IEEE80211_NODE_ERP) == 0) { KASSERT(vap->iv_nonerpsta > 0, ("bogus non-ERP station count %d", vap->iv_nonerpsta)); vap->iv_nonerpsta--; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "non-ERP station leaves, count now %d%s", vap->iv_nonerpsta, (vap->iv_flags_ext & IEEE80211_FEXT_NONERP_PR) ? " (non-ERP sta present)" : ""); if (vap->iv_nonerpsta == 0 && (vap->iv_flags_ext & IEEE80211_FEXT_NONERP_PR) == 0) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: disable use of protection\n", __func__); disable_protection(vap); } } } /* * Time out presence of an overlapping bss with non-ERP * stations. When operating in hostap mode we listen for * beacons from other stations and if we identify a non-ERP * station is present we enable protection. To identify * when all non-ERP stations are gone we time out this * condition. */ static void ieee80211_vap_erp_timeout(struct ieee80211vap *vap) { IEEE80211_LOCK_ASSERT(vap->iv_ic); if ((vap->iv_flags_ext & IEEE80211_FEXT_NONERP_PR) && ieee80211_time_after(ticks, vap->iv_lastnonerp + IEEE80211_NONERP_PRESENT_AGE)) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s", "age out non-ERP sta present on channel"); vap->iv_flags_ext &= ~IEEE80211_FEXT_NONERP_PR; if (vap->iv_nonerpsta == 0) disable_protection(vap); } } /* * Handle bookkeeping for station deauthentication/disassociation * when operating as an ap. */ void ieee80211_node_leave(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_node_table *nt = ni->ni_table; IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, ni, "station with aid %d leaves", IEEE80211_NODE_AID(ni)); KASSERT(vap->iv_opmode != IEEE80211_M_STA, ("unexpected operating mode %u", vap->iv_opmode)); /* * If node wasn't previously associated all * we need to do is reclaim the reference. */ /* XXX ibss mode bypasses 11g and notification */ if (ni->ni_associd == 0) goto done; /* * Tell the authenticator the station is leaving. * Note that we must do this before yanking the * association id as the authenticator uses the * associd to locate it's state block. */ if (vap->iv_auth->ia_node_leave != NULL) vap->iv_auth->ia_node_leave(ni); IEEE80211_LOCK(ic); IEEE80211_AID_CLR(vap, ni->ni_associd); vap->iv_sta_assoc--; if (IEEE80211_IS_CHAN_VHT(ic->ic_bsschan)) ieee80211_vht_node_leave(ni); if (IEEE80211_IS_CHAN_HT(ic->ic_bsschan)) ieee80211_ht_node_leave(ni); if (IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && IEEE80211_IS_CHAN_FULL(ic->ic_bsschan)) ieee80211_node_leave_11g(ni); IEEE80211_UNLOCK(ic); /* * Cleanup station state. In particular clear various * state that might otherwise be reused if the node * is reused before the reference count goes to zero * (and memory is reclaimed). */ ieee80211_sta_leave(ni); done: /* * Remove the node from any table it's recorded in and * drop the caller's reference. Removal from the table * is important to insure the node is not reprocessed * for inactivity. */ if (nt != NULL) { IEEE80211_NODE_LOCK(nt); node_reclaim(nt, ni); IEEE80211_NODE_UNLOCK(nt); } else ieee80211_free_node(ni); } struct rssiinfo { int rssi_samples; uint32_t rssi_total; }; static void get_hostap_rssi(void *arg, struct ieee80211_node *ni) { struct rssiinfo *info = arg; struct ieee80211vap *vap = ni->ni_vap; int8_t rssi; /* only associated stations */ if (ni->ni_associd == 0) return; rssi = vap->iv_ic->ic_node_getrssi(ni); if (rssi != 0) { info->rssi_samples++; info->rssi_total += rssi; } } static void get_adhoc_rssi(void *arg, struct ieee80211_node *ni) { struct rssiinfo *info = arg; struct ieee80211vap *vap = ni->ni_vap; int8_t rssi; /* only neighbors */ /* XXX check bssid */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) return; rssi = vap->iv_ic->ic_node_getrssi(ni); if (rssi != 0) { info->rssi_samples++; info->rssi_total += rssi; } } #ifdef IEEE80211_SUPPORT_MESH static void get_mesh_rssi(void *arg, struct ieee80211_node *ni) { struct rssiinfo *info = arg; struct ieee80211vap *vap = ni->ni_vap; int8_t rssi; /* only neighbors that peered successfully */ if (ni->ni_mlstate != IEEE80211_NODE_MESH_ESTABLISHED) return; rssi = vap->iv_ic->ic_node_getrssi(ni); if (rssi != 0) { info->rssi_samples++; info->rssi_total += rssi; } } #endif /* IEEE80211_SUPPORT_MESH */ int8_t ieee80211_getrssi(struct ieee80211vap *vap) { #define NZ(x) ((x) == 0 ? 1 : (x)) struct ieee80211com *ic = vap->iv_ic; struct rssiinfo info; info.rssi_total = 0; info.rssi_samples = 0; switch (vap->iv_opmode) { case IEEE80211_M_IBSS: /* average of all ibss neighbors */ case IEEE80211_M_AHDEMO: /* average of all neighbors */ ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, get_adhoc_rssi, &info); break; case IEEE80211_M_HOSTAP: /* average of all associated stations */ ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, get_hostap_rssi, &info); break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_M_MBSS: /* average of all mesh neighbors */ ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, get_mesh_rssi, &info); break; #endif case IEEE80211_M_MONITOR: /* XXX */ case IEEE80211_M_STA: /* use stats from associated ap */ default: if (vap->iv_bss != NULL) info.rssi_total = ic->ic_node_getrssi(vap->iv_bss); info.rssi_samples = 1; break; } return info.rssi_total / NZ(info.rssi_samples); #undef NZ } void ieee80211_getsignal(struct ieee80211vap *vap, int8_t *rssi, int8_t *noise) { if (vap->iv_bss == NULL) /* NB: shouldn't happen */ return; vap->iv_ic->ic_node_getsignal(vap->iv_bss, rssi, noise); /* for non-station mode return avg'd rssi accounting */ if (vap->iv_opmode != IEEE80211_M_STA) *rssi = ieee80211_getrssi(vap); } /** * @brief return a dot11rate / ratecode representing the current transmit rate * * This is the API call for legacy / 802.11n drivers and rate control APIs * which expect a dot11rate / ratecode representation for legacy and HT MCS * rates. * * Drivers which support VHT should not use this API, as it will log an error * and return a low rate if a VHT rate is selected. * * @param ni the ieee80211_node to return the transmit rate for * @returns the dot11rate / ratecode for legacy/MCS, or the * lowest available dot11rate if it's VHT (and shouldn't * have been called.) */ uint8_t ieee80211_node_get_txrate_dot11rate(struct ieee80211_node *ni) { switch (ni->ni_txrate.type) { case IEEE80211_NODE_TXRATE_LEGACY: case IEEE80211_NODE_TXRATE_HT: return (ni->ni_txrate.dot11rate); break; case IEEE80211_NODE_TXRATE_VHT: default: - printf("%s: called for VHT / unknown rate (type %d)!\n", + net80211_vap_printf(ni->ni_vap, + "%s: called for VHT / unknown rate (type %d)!\n", __func__, ni->ni_txrate.type); return (12); /* OFDM6 for now */ } } /** * @brief return the txrate representing the current transmit rate * * This is the API call for drivers and rate control APIs to fetch * rates. It will populate a struct ieee80211_node_txrate with the * current rate configuration to use. * * @param ni the ieee80211_node to return the transmit rate for * @param txrate the struct ieee80211_node_txrate to populate */ void ieee80211_node_get_txrate(struct ieee80211_node *ni, struct ieee80211_node_txrate *txr) { MPASS(ni != NULL); MPASS(txr != NULL); *txr = ni->ni_txrate; } /** * @brief Set the txrate representing the current transmit rate * * This is the API call for drivers and rate control APIs to set * rates. It will copy a struct ieee80211_node_txrate with the * current rate configuration to use. * * @param ni the ieee80211_node to return the transmit rate for * @param txrate the struct ieee80211_node_txrate to copy to the node */ void ieee80211_node_set_txrate(struct ieee80211_node *ni, const struct ieee80211_node_txrate *txr) { MPASS(ni != NULL); MPASS(txr != NULL); ni->ni_txrate = *txr; } /** * @brief set the dot11rate / ratecode representing the current transmit rate * * This is the API call for legacy / 802.11n drivers and rate control APIs * which expect a dot11rate / ratecode representation for legacy and HT MCS * rates. * * @param ni the ieee80211_node to return the transmit rate for * @param dot11rate the dot11rate rate code to use */ void ieee80211_node_set_txrate_dot11rate(struct ieee80211_node *ni, uint8_t dot11Rate) { if (dot11Rate & IEEE80211_RATE_MCS) { ni->ni_txrate.type = IEEE80211_NODE_TXRATE_HT; ni->ni_txrate.mcs = dot11Rate & IEEE80211_RATE_VAL; ni->ni_txrate.nss = 0; ni->ni_txrate.dot11rate = dot11Rate; } else { ni->ni_txrate.type = IEEE80211_NODE_TXRATE_LEGACY; ni->ni_txrate.mcs = ni->ni_txrate.nss = 0; ni->ni_txrate.dot11rate = dot11Rate; } } /** * @brief set the dot11rate / ratecode representing the current HT transmit rate * * This is the API call for 802.11n drivers and rate control APIs * which expect a dot11rate / ratecode representation for legacy and HT MCS * rates. It expects an MCS rate code from 0 .. 76. * * @param ni the ieee80211_node to return the transmit rate for * @param mcs the MCS rate to select */ void ieee80211_node_set_txrate_ht_mcsrate(struct ieee80211_node *ni, uint8_t mcs) { KASSERT(mcs <= 76, ("%s: MCS is not 0..76 (%d)", __func__, mcs)); if (mcs > 76) { ic_printf(ni->ni_ic, "%s: invalid MCS (%d)\n", __func__, mcs); return; } ni->ni_txrate.type = IEEE80211_NODE_TXRATE_HT; ni->ni_txrate.mcs = mcs; ni->ni_txrate.nss = 0; ni->ni_txrate.dot11rate = IEEE80211_RATE_MCS | mcs; } /** * @brief set the rate to the given VHT transmission rate. * * This sets the current transmit rate to the given VHT NSS/MCS. * * @param ni the ieee80211_node to set the transmit rate for * @param nss the number of spatial streams * @param mcs the MCS rate to select */ void ieee80211_node_set_txrate_vht_rate(struct ieee80211_node *ni, uint8_t nss, uint8_t mcs) { MPASS(ni != NULL); ni->ni_txrate.type = IEEE80211_NODE_TXRATE_VHT; ni->ni_txrate.mcs = mcs; ni->ni_txrate.nss = nss; ni->ni_txrate.dot11rate = 0; } /* * @brief Fetch the transmit rate for the given node in kbit/s. * * This currently only works for CCK, OFDM and HT rates. * * @param ni struct ieee80211_node * to lookup * @returns current transmit rate in kbit/s */ uint32_t ieee80211_node_get_txrate_kbit(struct ieee80211_node *ni) { uint32_t kbps; switch (ni->ni_txrate.type) { case IEEE80211_NODE_TXRATE_LEGACY: kbps = ni->ni_txrate.dot11rate * 500; break; case IEEE80211_NODE_TXRATE_HT: /* Note: Valid for MCS 0..76 */ { const struct ieee80211_mcs_rates *mcs = &ieee80211_htrates[ni->ni_txrate.dot11rate & ~IEEE80211_RATE_MCS]; if (IEEE80211_IS_CHAN_HT40(ni->ni_chan)) { if (ni->ni_flags & IEEE80211_NODE_SGI40) kbps = mcs->ht40_rate_800ns * 500; else kbps = mcs->ht40_rate_400ns * 500; } else { if (ni->ni_flags & IEEE80211_NODE_SGI20) kbps = mcs->ht20_rate_800ns * 500; else kbps = mcs->ht20_rate_400ns * 500; } } break; case IEEE80211_NODE_TXRATE_VHT: /* Note: valid for VHT rates, assumes long-GI for now */ kbps = ieee80211_phy_vht_get_mcs_kbit(ni->ni_chw, ni->ni_txrate.nss, ni->ni_txrate.mcs, false); break; default: - printf("%s: called for unknown rate (type %d)!\n", - __func__, ni->ni_txrate.type); + net80211_vap_printf(ni->ni_vap, + "%s: called for unknown rate (type %d)!\n", __func__, + ni->ni_txrate.type); return (0); } return (kbps); } diff --git a/sys/net80211/ieee80211_output.c b/sys/net80211/ieee80211_output.c index 9466b4851881..a4151f807882 100644 --- a/sys/net80211/ieee80211_output.c +++ b/sys/net80211/ieee80211_output.c @@ -1,4282 +1,4283 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 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 #include "opt_inet.h" #include "opt_inet6.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #ifdef IEEE80211_SUPPORT_TDMA #include #endif #include #include #include #if defined(INET) || defined(INET6) #include #endif #ifdef INET #include #include #include #endif #ifdef INET6 #include #endif #include #define ETHER_HEADER_COPY(dst, src) \ memcpy(dst, src, sizeof(struct ether_header)) static int ieee80211_fragment(struct ieee80211vap *, struct mbuf *, u_int hdrsize, u_int ciphdrsize, u_int mtu); static void ieee80211_tx_mgt_cb(struct ieee80211_node *, void *, int); #ifdef IEEE80211_DEBUG /* * Decide if an outbound 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_PROBE_RESP: return (vap->iv_opmode == IEEE80211_M_IBSS); } return 1; } #endif /* * Transmit a frame to the given destination on the given VAP. * * It's up to the caller to figure out the details of who this * is going to and resolving the node. * * This routine takes care of queuing it for power save, * A-MPDU state stuff, fast-frames state stuff, encapsulation * if required, then passing it up to the driver layer. * * This routine (for now) consumes the mbuf and frees the node * reference; it ideally will return a TX status which reflects * whether the mbuf was consumed or not, so the caller can * free the mbuf (if appropriate) and the node reference (again, * if appropriate.) */ int ieee80211_vap_pkt_send_dest(struct ieee80211vap *vap, struct mbuf *m, struct ieee80211_node *ni) { struct ieee80211com *ic = vap->iv_ic; struct ifnet *ifp = vap->iv_ifp; int mcast; int do_ampdu = 0; #ifdef IEEE80211_SUPPORT_SUPERG int do_amsdu = 0; int do_ampdu_amsdu = 0; int no_ampdu = 1; /* Will be set to 0 if ampdu is active */ int do_ff = 0; #endif if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) && (m->m_flags & M_PWR_SAV) == 0) { /* * Station in power save mode; pass the frame * to the 802.11 layer and continue. We'll get * the frame back when the time is right. * XXX lose WDS vap linkage? */ if (ieee80211_pwrsave(ni, m) != 0) if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); /* * We queued it fine, so tell the upper layer * that we consumed it. */ return (0); } /* calculate priority so drivers can find the tx queue */ if (ieee80211_classify(ni, m)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_OUTPUT, ni->ni_macaddr, NULL, "%s", "classification failure"); vap->iv_stats.is_tx_classify++; if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(m); ieee80211_free_node(ni); /* XXX better status? */ return (0); } /* * Stash the node pointer. Note that we do this after * any call to ieee80211_dwds_mcast because that code * uses any existing value for rcvif to identify the * interface it (might have been) received on. */ MPASS((m->m_pkthdr.csum_flags & CSUM_SND_TAG) == 0); m->m_pkthdr.rcvif = (void *)ni; mcast = (m->m_flags & (M_MCAST | M_BCAST)) ? 1: 0; BPF_MTAP(ifp, m); /* 802.3 tx */ /* * Figure out if we can do A-MPDU, A-MSDU or FF. * * A-MPDU depends upon vap/node config. * A-MSDU depends upon vap/node config. * FF depends upon vap config, IE and whether * it's 11abg (and not 11n/11ac/etc.) * * Note that these flags indiciate whether we can do * it at all, rather than the situation (eg traffic type.) */ do_ampdu = ((ni->ni_flags & IEEE80211_NODE_AMPDU_TX) && (vap->iv_flags_ht & IEEE80211_FHT_AMPDU_TX)); #ifdef IEEE80211_SUPPORT_SUPERG do_amsdu = ((ni->ni_flags & IEEE80211_NODE_AMSDU_TX) && (vap->iv_flags_ht & IEEE80211_FHT_AMSDU_TX)); do_ff = ((ni->ni_flags & IEEE80211_NODE_HT) == 0) && ((ni->ni_flags & IEEE80211_NODE_VHT) == 0) && (IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF)); #endif /* * Check if A-MPDU tx aggregation is setup or if we * should try to enable it. The sta must be associated * with HT and A-MPDU enabled for use. When the policy * routine decides we should enable A-MPDU we issue an * ADDBA request and wait for a reply. The frame being * encapsulated will go out w/o using A-MPDU, or possibly * it might be collected by the driver and held/retransmit. * The default ic_ampdu_enable routine handles staggering * ADDBA requests in case the receiver NAK's us or we are * otherwise unable to establish a BA stream. * * Don't treat group-addressed frames as candidates for aggregation; * net80211 doesn't support 802.11aa-2012 and so group addressed * frames will always have sequence numbers allocated from the NON_QOS * TID. */ if (!IEEE80211_CONF_AMPDU_OFFLOAD(ic) && do_ampdu) { if ((m->m_flags & M_EAPOL) == 0 && (! mcast)) { int tid = WME_AC_TO_TID(M_WME_GETAC(m)); struct ieee80211_tx_ampdu *tap = &ni->ni_tx_ampdu[tid]; ieee80211_txampdu_count_packet(tap); if (IEEE80211_AMPDU_RUNNING(tap)) { /* * Operational, mark frame for aggregation. * * XXX do tx aggregation here */ m->m_flags |= M_AMPDU_MPDU; } else if (!IEEE80211_AMPDU_REQUESTED(tap) && ic->ic_ampdu_enable(ni, tap)) { /* * Not negotiated yet, request service. */ ieee80211_ampdu_request(ni, tap); /* XXX hold frame for reply? */ } /* * Now update the no-ampdu flag. A-MPDU may have been * started or administratively disabled above; so now we * know whether we're running yet or not. * * This will let us know whether we should be doing A-MSDU * at this point. We only do A-MSDU if we're either not * doing A-MPDU, or A-MPDU is NACKed, or A-MPDU + A-MSDU * is available. * * Whilst here, update the amsdu-ampdu flag. The above may * have also set or cleared the amsdu-in-ampdu txa_flags * combination so we can correctly do A-MPDU + A-MSDU. */ #ifdef IEEE80211_SUPPORT_SUPERG no_ampdu = (! IEEE80211_AMPDU_RUNNING(tap) || (IEEE80211_AMPDU_NACKED(tap))); do_ampdu_amsdu = IEEE80211_AMPDU_RUNNING_AMSDU(tap); #endif } } #ifdef IEEE80211_SUPPORT_SUPERG /* * Check for AMSDU/FF; queue for aggregation * * Note: we don't bother trying to do fast frames or * A-MSDU encapsulation for 802.3 drivers. Now, we * likely could do it for FF (because it's a magic * atheros tunnel LLC type) but I don't think we're going * to really need to. For A-MSDU we'd have to set the * A-MSDU QoS bit in the wifi header, so we just plain * can't do it. */ if (__predict_true((vap->iv_caps & IEEE80211_C_8023ENCAP) == 0)) { if ((! mcast) && (do_ampdu_amsdu || (no_ampdu && do_amsdu)) && ieee80211_amsdu_tx_ok(ni)) { m = ieee80211_amsdu_check(ni, m); if (m == NULL) { /* NB: any ni ref held on stageq */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: amsdu_check queued frame\n", __func__); return (0); } } else if ((! mcast) && do_ff) { m = ieee80211_ff_check(ni, m); if (m == NULL) { /* NB: any ni ref held on stageq */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: ff_check queued frame\n", __func__); return (0); } } } #endif /* IEEE80211_SUPPORT_SUPERG */ /* * Grab the TX lock - serialise the TX process from this * point (where TX state is being checked/modified) * through to driver queue. */ IEEE80211_TX_LOCK(ic); /* * XXX make the encap and transmit code a separate function * so things like the FF (and later A-MSDU) path can just call * it for flushed frames. */ if (__predict_true((vap->iv_caps & IEEE80211_C_8023ENCAP) == 0)) { /* * Encapsulate the packet in prep for transmission. */ m = ieee80211_encap(vap, ni, m); if (m == NULL) { /* NB: stat+msg handled in ieee80211_encap */ IEEE80211_TX_UNLOCK(ic); ieee80211_free_node(ni); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return (ENOBUFS); } } (void) ieee80211_parent_xmitpkt(ic, m); /* * Unlock at this point - no need to hold it across * ieee80211_free_node() (ie, the comlock) */ IEEE80211_TX_UNLOCK(ic); ic->ic_lastdata = ticks; return (0); } /* * Send the given mbuf through the given vap. * * This consumes the mbuf regardless of whether the transmit * was successful or not. * * This does none of the initial checks that ieee80211_start() * does (eg CAC timeout, interface wakeup) - the caller must * do this first. */ static int ieee80211_start_pkt(struct ieee80211vap *vap, struct mbuf *m) { #define IS_DWDS(vap) \ (vap->iv_opmode == IEEE80211_M_WDS && \ (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY) == 0) struct ieee80211com *ic = vap->iv_ic; struct ifnet *ifp = vap->iv_ifp; struct ieee80211_node *ni; struct ether_header *eh; /* * Cancel any background scan. */ if (ic->ic_flags & IEEE80211_F_SCAN) ieee80211_cancel_anyscan(vap); /* * Find the node for the destination so we can do * things like power save and fast frames aggregation. * * NB: past this point various code assumes the first * mbuf has the 802.3 header present (and contiguous). */ ni = NULL; if (m->m_len < sizeof(struct ether_header) && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "discard frame, %s\n", "m_pullup failed"); vap->iv_stats.is_tx_nobuf++; /* XXX */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return (ENOBUFS); } eh = mtod(m, struct ether_header *); if (ETHER_IS_MULTICAST(eh->ether_dhost)) { if (IS_DWDS(vap)) { /* * Only unicast frames from the above go out * DWDS vaps; multicast frames are handled by * dispatching the frame as it comes through * the AP vap (see below). */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_WDS, eh->ether_dhost, "mcast", "%s", "on DWDS"); vap->iv_stats.is_dwds_mcast++; m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); /* XXX better status? */ return (ENOBUFS); } if (vap->iv_opmode == IEEE80211_M_HOSTAP) { /* * Spam DWDS vap's w/ multicast traffic. */ /* XXX only if dwds in use? */ ieee80211_dwds_mcast(vap, m); } } #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode != IEEE80211_M_MBSS) { #endif ni = ieee80211_find_txnode(vap, eh->ether_dhost); if (ni == NULL) { /* NB: ieee80211_find_txnode does stat+msg */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(m); /* XXX better status? */ return (ENOBUFS); } if (ni->ni_associd == 0 && (ni->ni_flags & IEEE80211_NODE_ASSOCID)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_OUTPUT, eh->ether_dhost, NULL, "sta not associated (type 0x%04x)", htons(eh->ether_type)); vap->iv_stats.is_tx_notassoc++; if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(m); ieee80211_free_node(ni); /* XXX better status? */ return (ENOBUFS); } #ifdef IEEE80211_SUPPORT_MESH } else { if (!IEEE80211_ADDR_EQ(eh->ether_shost, vap->iv_myaddr)) { /* * Proxy station only if configured. */ if (!ieee80211_mesh_isproxyena(vap)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_OUTPUT | IEEE80211_MSG_MESH, eh->ether_dhost, NULL, "%s", "proxy not enabled"); vap->iv_stats.is_mesh_notproxy++; if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(m); /* XXX better status? */ return (ENOBUFS); } IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "forward frame from DS SA(%6D), DA(%6D)\n", eh->ether_shost, ":", eh->ether_dhost, ":"); ieee80211_mesh_proxy_check(vap, eh->ether_shost); } ni = ieee80211_mesh_discover(vap, eh->ether_dhost, m); if (ni == NULL) { /* * NB: ieee80211_mesh_discover holds/disposes * frame (e.g. queueing on path discovery). */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); /* XXX better status? */ return (ENOBUFS); } } #endif /* * We've resolved the sender, so attempt to transmit it. */ if (vap->iv_state == IEEE80211_S_SLEEP) { /* * In power save; queue frame and then wakeup device * for transmit. */ ic->ic_lastdata = ticks; if (ieee80211_pwrsave(ni, m) != 0) if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); ieee80211_new_state(vap, IEEE80211_S_RUN, 0); return (0); } if (ieee80211_vap_pkt_send_dest(vap, m, ni) != 0) return (ENOBUFS); return (0); #undef IS_DWDS } /* * Start method for vap's. All packets from the stack come * through here. We handle common processing of the packets * before dispatching them to the underlying device. * * if_transmit() requires that the mbuf be consumed by this call * regardless of the return condition. */ int ieee80211_vap_transmit(struct ifnet *ifp, struct mbuf *m) { struct ieee80211vap *vap = ifp->if_softc; struct ieee80211com *ic = vap->iv_ic; /* * No data frames go out unless we're running. * Note in particular this covers CAC and CSA * states (though maybe we should check muting * for CSA). */ if (vap->iv_state != IEEE80211_S_RUN && vap->iv_state != IEEE80211_S_SLEEP) { IEEE80211_LOCK(ic); /* re-check under the com lock to avoid races */ if (vap->iv_state != IEEE80211_S_RUN && vap->iv_state != IEEE80211_S_SLEEP) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "%s: ignore queue, in %s state\n", __func__, ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_tx_badstate++; IEEE80211_UNLOCK(ic); ifp->if_drv_flags |= IFF_DRV_OACTIVE; m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return (ENETDOWN); } IEEE80211_UNLOCK(ic); } /* * Sanitize mbuf flags for net80211 use. We cannot * clear M_PWR_SAV or M_MORE_DATA because these may * be set for frames that are re-submitted from the * power save queue. * * NB: This must be done before ieee80211_classify as * it marks EAPOL in frames with M_EAPOL. */ m->m_flags &= ~(M_80211_TX - M_PWR_SAV - M_MORE_DATA); /* * Bump to the packet transmission path. * The mbuf will be consumed here. */ return (ieee80211_start_pkt(vap, m)); } void ieee80211_vap_qflush(struct ifnet *ifp) { /* Empty for now */ } /* * 802.11 raw output routine. * * XXX TODO: this (and other send routines) should correctly * XXX keep the pwr mgmt bit set if it decides to call into the * XXX driver to send a frame whilst the state is SLEEP. * * Otherwise the peer may decide that we're awake and flood us * with traffic we are still too asleep to receive! */ int ieee80211_raw_output(struct ieee80211vap *vap, struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = vap->iv_ic; int error; /* * Set node - the caller has taken a reference, so ensure * that the mbuf has the same node value that * it would if it were going via the normal path. */ MPASS((m->m_pkthdr.csum_flags & CSUM_SND_TAG) == 0); m->m_pkthdr.rcvif = (void *)ni; /* * Attempt to add bpf transmit parameters. * * For now it's ok to fail; the raw_xmit api still takes * them as an option. * * Later on when ic_raw_xmit() has params removed, * they'll have to be added - so fail the transmit if * they can't be. */ if (params) (void) ieee80211_add_xmit_params(m, params); error = ic->ic_raw_xmit(ni, m, params); if (error) { if_inc_counter(vap->iv_ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); } return (error); } static int ieee80211_validate_frame(struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211_frame *wh; int type; if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_ack)) return (EINVAL); wh = mtod(m, struct ieee80211_frame *); if (!IEEE80211_IS_FC0_CHECK_VER(wh, IEEE80211_FC0_VERSION_0)) return (EINVAL); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; if (type != IEEE80211_FC0_TYPE_DATA) { if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) != IEEE80211_FC1_DIR_NODS) return (EINVAL); if (type != IEEE80211_FC0_TYPE_MGT && (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG) != 0) return (EINVAL); /* XXX skip other field checks? */ } if ((params && (params->ibp_flags & IEEE80211_BPF_CRYPTO) != 0) || (IEEE80211_IS_PROTECTED(wh))) { int subtype; subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; /* * See IEEE Std 802.11-2012, * 8.2.4.1.9 'Protected Frame field' */ /* XXX no support for robust management frames yet. */ if (!(type == IEEE80211_FC0_TYPE_DATA || (type == IEEE80211_FC0_TYPE_MGT && subtype == IEEE80211_FC0_SUBTYPE_AUTH))) return (EINVAL); wh->i_fc[1] |= IEEE80211_FC1_PROTECTED; } if (m->m_pkthdr.len < ieee80211_anyhdrsize(wh)) return (EINVAL); return (0); } static int ieee80211_validate_rate(struct ieee80211_node *ni, uint8_t rate) { struct ieee80211com *ic = ni->ni_ic; if (IEEE80211_IS_HT_RATE(rate)) { if ((ic->ic_htcaps & IEEE80211_HTC_HT) == 0) return (EINVAL); rate = IEEE80211_RV(rate); if (rate <= 31) { if (rate > ic->ic_txstream * 8 - 1) return (EINVAL); return (0); } if (rate == 32) { if ((ic->ic_htcaps & IEEE80211_HTC_TXMCS32) == 0) return (EINVAL); return (0); } if ((ic->ic_htcaps & IEEE80211_HTC_TXUNEQUAL) == 0) return (EINVAL); switch (ic->ic_txstream) { case 0: case 1: return (EINVAL); case 2: if (rate > 38) return (EINVAL); return (0); case 3: if (rate > 52) return (EINVAL); return (0); case 4: default: if (rate > 76) return (EINVAL); return (0); } } if (!ieee80211_isratevalid(ic->ic_rt, rate)) return (EINVAL); return (0); } static int ieee80211_sanitize_rates(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { int error; if (!params) return (0); /* nothing to do */ /* NB: most drivers assume that ibp_rate0 is set (!= 0). */ if (params->ibp_rate0 != 0) { error = ieee80211_validate_rate(ni, params->ibp_rate0); if (error != 0) return (error); } else { /* XXX pre-setup some default (e.g., mgmt / mcast) rate */ /* XXX __DECONST? */ (void) m; } if (params->ibp_rate1 != 0 && (error = ieee80211_validate_rate(ni, params->ibp_rate1)) != 0) return (error); if (params->ibp_rate2 != 0 && (error = ieee80211_validate_rate(ni, params->ibp_rate2)) != 0) return (error); if (params->ibp_rate3 != 0 && (error = ieee80211_validate_rate(ni, params->ibp_rate3)) != 0) return (error); return (0); } /* * 802.11 output routine. This is (currently) used only to * connect bpf write calls to the 802.11 layer for injecting * raw 802.11 frames. */ int ieee80211_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *dst, struct route *ro) { #define senderr(e) do { error = (e); goto bad;} while (0) const struct ieee80211_bpf_params *params = NULL; struct ieee80211_node *ni = NULL; struct ieee80211vap *vap; struct ieee80211_frame *wh; struct ieee80211com *ic = NULL; int error; int ret; if (ifp->if_drv_flags & IFF_DRV_OACTIVE) { /* * Short-circuit requests if the vap is marked OACTIVE * as this can happen because a packet came down through * ieee80211_start before the vap entered RUN state in * which case it's ok to just drop the frame. This * should not be necessary but callers of if_output don't * check OACTIVE. */ senderr(ENETDOWN); } vap = ifp->if_softc; ic = vap->iv_ic; /* * Hand to the 802.3 code if not tagged as * a raw 802.11 frame. */ if (dst->sa_family != AF_IEEE80211) return vap->iv_output(ifp, m, dst, ro); #ifdef MAC error = mac_ifnet_check_transmit(ifp, m); if (error) senderr(error); #endif if (ieee80211_vap_ifp_check_is_monitor(vap)) senderr(ENETDOWN); if (!IFNET_IS_UP_RUNNING(ifp)) senderr(ENETDOWN); if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT | IEEE80211_MSG_DOTH, "block %s frame in CAC state\n", "raw data"); vap->iv_stats.is_tx_badstate++; senderr(EIO); /* XXX */ } else if (vap->iv_state == IEEE80211_S_SCAN) senderr(EIO); /* XXX bypass bridge, pfil, carp, etc. */ /* * NB: DLT_IEEE802_11_RADIO identifies the parameters are * present by setting the sa_len field of the sockaddr (yes, * this is a hack). * NB: we assume sa_data is suitably aligned to cast. */ if (dst->sa_len != 0) params = (const struct ieee80211_bpf_params *)dst->sa_data; error = ieee80211_validate_frame(m, params); if (error != 0) senderr(error); wh = mtod(m, struct ieee80211_frame *); /* locate destination node */ switch (wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) { case IEEE80211_FC1_DIR_NODS: case IEEE80211_FC1_DIR_FROMDS: ni = ieee80211_find_txnode(vap, wh->i_addr1); break; case IEEE80211_FC1_DIR_TODS: case IEEE80211_FC1_DIR_DSTODS: ni = ieee80211_find_txnode(vap, wh->i_addr3); break; default: senderr(EDOOFUS); } if (ni == NULL) { /* * Permit packets w/ bpf params through regardless * (see below about sa_len). */ if (dst->sa_len == 0) senderr(EHOSTUNREACH); ni = ieee80211_ref_node(vap->iv_bss); } /* * Sanitize mbuf for net80211 flags leaked from above. * * NB: This must be done before ieee80211_classify as * it marks EAPOL in frames with M_EAPOL. */ m->m_flags &= ~M_80211_TX; m->m_flags |= M_ENCAP; /* mark encapsulated */ if (IEEE80211_IS_DATA(wh)) { /* calculate priority so drivers can find the tx queue */ if (ieee80211_classify(ni, m)) senderr(EIO); /* XXX */ /* NB: ieee80211_encap does not include 802.11 header */ IEEE80211_NODE_STAT_ADD(ni, tx_bytes, m->m_pkthdr.len - ieee80211_hdrsize(wh)); } else M_WME_SETAC(m, WME_AC_BE); error = ieee80211_sanitize_rates(ni, m, params); if (error != 0) senderr(error); IEEE80211_NODE_STAT(ni, tx_data); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_NODE_STAT(ni, tx_mcast); m->m_flags |= M_MCAST; } else IEEE80211_NODE_STAT(ni, tx_ucast); IEEE80211_TX_LOCK(ic); ret = ieee80211_raw_output(vap, ni, m, params); IEEE80211_TX_UNLOCK(ic); return (ret); bad: if (m != NULL) m_freem(m); if (ni != NULL) ieee80211_free_node(ni); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return error; #undef senderr } /* * Set the direction field and address fields of an outgoing * frame. Note this should be called early on in constructing * a frame as it sets i_fc[1]; other bits can then be or'd in. */ void ieee80211_send_setup( struct ieee80211_node *ni, struct mbuf *m, int type, int tid, const uint8_t sa[IEEE80211_ADDR_LEN], const uint8_t da[IEEE80211_ADDR_LEN], const uint8_t bssid[IEEE80211_ADDR_LEN]) { #define WH4(wh) ((struct ieee80211_frame_addr4 *)wh) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_tx_ampdu *tap; struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); IEEE80211_TX_LOCK_ASSERT(ni->ni_ic); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | type; if ((type & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_DATA) { switch (vap->iv_opmode) { case IEEE80211_M_STA: wh->i_fc[1] = IEEE80211_FC1_DIR_TODS; IEEE80211_ADDR_COPY(wh->i_addr1, bssid); IEEE80211_ADDR_COPY(wh->i_addr2, sa); IEEE80211_ADDR_COPY(wh->i_addr3, da); break; case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, sa); IEEE80211_ADDR_COPY(wh->i_addr3, bssid); break; case IEEE80211_M_HOSTAP: wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, bssid); IEEE80211_ADDR_COPY(wh->i_addr3, sa); break; case IEEE80211_M_WDS: wh->i_fc[1] = IEEE80211_FC1_DIR_DSTODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, da); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, sa); break; case IEEE80211_M_MBSS: #ifdef IEEE80211_SUPPORT_MESH if (IEEE80211_IS_MULTICAST(da)) { wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; /* XXX next hop */ IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); } else { wh->i_fc[1] = IEEE80211_FC1_DIR_DSTODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, da); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, sa); } #endif break; case IEEE80211_M_MONITOR: /* NB: to quiet compiler */ break; } } else { wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, sa); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) IEEE80211_ADDR_COPY(wh->i_addr3, sa); else #endif IEEE80211_ADDR_COPY(wh->i_addr3, bssid); } *(uint16_t *)&wh->i_dur[0] = 0; /* * XXX TODO: this is what the TX lock is for. * Here we're incrementing sequence numbers, and they * need to be in lock-step with what the driver is doing * both in TX ordering and crypto encap (IV increment.) * * If the driver does seqno itself, then we can skip * assigning sequence numbers here, and we can avoid * requiring the TX lock. */ tap = &ni->ni_tx_ampdu[tid]; if (tid != IEEE80211_NONQOS_TID && IEEE80211_AMPDU_RUNNING(tap)) { m->m_flags |= M_AMPDU_MPDU; /* NB: zero out i_seq field (for s/w encryption etc) */ *(uint16_t *)&wh->i_seq[0] = 0; } else ieee80211_output_seqno_assign(ni, tid, m); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) m->m_flags |= M_MCAST; #undef WH4 } /* * Send a management frame to the specified node. The node pointer * must have a reference as the pointer will be passed to the driver * and potentially held for a long time. If the frame is successfully * dispatched to the driver, then it is responsible for freeing the * reference (and potentially free'ing up any associated storage); * otherwise deal with reclaiming any reference (on error). */ int ieee80211_mgmt_output(struct ieee80211_node *ni, struct mbuf *m, int type, struct ieee80211_bpf_params *params) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_frame *wh; int ret; KASSERT(ni != NULL, ("null node")); if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT | IEEE80211_MSG_DOTH, ni, "block %s frame in CAC state", ieee80211_mgt_subtype_name(type)); vap->iv_stats.is_tx_badstate++; ieee80211_free_node(ni); m_freem(m); return EIO; /* XXX */ } M_PREPEND(m, sizeof(struct ieee80211_frame), IEEE80211_M_NOWAIT); if (m == NULL) { ieee80211_free_node(ni); return ENOMEM; } IEEE80211_TX_LOCK(ic); wh = mtod(m, struct ieee80211_frame *); ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_MGT | type, IEEE80211_NONQOS_TID, vap->iv_myaddr, ni->ni_macaddr, ni->ni_bssid); if (params->ibp_flags & IEEE80211_BPF_CRYPTO) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH, wh->i_addr1, "encrypting frame (%s)", __func__); wh->i_fc[1] |= IEEE80211_FC1_PROTECTED; } m->m_flags |= M_ENCAP; /* mark encapsulated */ KASSERT(type != IEEE80211_FC0_SUBTYPE_PROBE_RESP, ("probe response?")); M_WME_SETAC(m, params->ibp_pri); #ifdef IEEE80211_DEBUG /* avoid printing too many frames */ if ((ieee80211_msg_debug(vap) && doprint(vap, type)) || ieee80211_msg_dumppkts(vap)) { ieee80211_note(vap, "[%s] send %s on channel %u\n", ether_sprintf(wh->i_addr1), ieee80211_mgt_subtype_name(type), ieee80211_chan2ieee(ic, ic->ic_curchan)); } #endif IEEE80211_NODE_STAT(ni, tx_mgmt); ret = ieee80211_raw_output(vap, ni, m, params); IEEE80211_TX_UNLOCK(ic); return (ret); } static void ieee80211_nulldata_transmitted(struct ieee80211_node *ni, void *arg, int status) { struct ieee80211vap *vap = ni->ni_vap; wakeup(vap); } /* * Send a null data frame to the specified node. If the station * is setup for QoS then a QoS Null Data frame is constructed. * If this is a WDS station then a 4-address frame is constructed. * * NB: the caller is assumed to have setup a node reference * for use; this is necessary to deal with a race condition * when probing for inactive stations. Like ieee80211_mgmt_output * we must cleanup any node reference on error; however we * can safely just unref it as we know it will never be the * last reference to the node. */ int ieee80211_send_nulldata(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct mbuf *m; struct ieee80211_frame *wh; int hdrlen; uint8_t *frm; int ret; if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT | IEEE80211_MSG_DOTH, ni, "block %s frame in CAC state", "null data"); ieee80211_node_decref(ni); vap->iv_stats.is_tx_badstate++; return EIO; /* XXX */ } if (ni->ni_flags & (IEEE80211_NODE_QOS|IEEE80211_NODE_HT)) hdrlen = sizeof(struct ieee80211_qosframe); else hdrlen = sizeof(struct ieee80211_frame); /* NB: only WDS vap's get 4-address frames */ if (vap->iv_opmode == IEEE80211_M_WDS) hdrlen += IEEE80211_ADDR_LEN; if (ic->ic_flags & IEEE80211_F_DATAPAD) hdrlen = roundup(hdrlen, sizeof(uint32_t)); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + hdrlen, 0); if (m == NULL) { /* XXX debug msg */ ieee80211_node_decref(ni); vap->iv_stats.is_tx_nobuf++; return ENOMEM; } KASSERT(M_LEADINGSPACE(m) >= hdrlen, ("leading space %zd", M_LEADINGSPACE(m))); M_PREPEND(m, hdrlen, IEEE80211_M_NOWAIT); if (m == NULL) { /* NB: cannot happen */ ieee80211_free_node(ni); return ENOMEM; } IEEE80211_TX_LOCK(ic); wh = mtod(m, struct ieee80211_frame *); /* NB: a little lie */ if (ni->ni_flags & IEEE80211_NODE_QOS) { const int tid = WME_AC_TO_TID(WME_AC_BE); uint8_t *qos; ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_DATA | IEEE80211_FC0_SUBTYPE_QOS_NULL, tid, vap->iv_myaddr, ni->ni_macaddr, ni->ni_bssid); if (vap->iv_opmode == IEEE80211_M_WDS) qos = ((struct ieee80211_qosframe_addr4 *) wh)->i_qos; else qos = ((struct ieee80211_qosframe *) wh)->i_qos; qos[0] = tid & IEEE80211_QOS_TID; if (ic->ic_wme.wme_wmeChanParams.cap_wmeParams[WME_AC_BE].wmep_noackPolicy) qos[0] |= IEEE80211_QOS_ACKPOLICY_NOACK; qos[1] = 0; } else { ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_DATA | IEEE80211_FC0_SUBTYPE_NODATA, IEEE80211_NONQOS_TID, vap->iv_myaddr, ni->ni_macaddr, ni->ni_bssid); } if (vap->iv_opmode != IEEE80211_M_WDS) { /* NB: power management bit is never sent by an AP */ if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) && vap->iv_opmode != IEEE80211_M_HOSTAP) wh->i_fc[1] |= IEEE80211_FC1_PWR_MGT; } if ((ic->ic_flags & IEEE80211_F_SCAN) && (ni->ni_flags & IEEE80211_NODE_PWR_MGT)) { ieee80211_add_callback(m, ieee80211_nulldata_transmitted, NULL); } m->m_len = m->m_pkthdr.len = hdrlen; m->m_flags |= M_ENCAP; /* mark encapsulated */ M_WME_SETAC(m, WME_AC_BE); IEEE80211_NODE_STAT(ni, tx_data); IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DUMPPKTS, ni, "send %snull data frame on channel %u, pwr mgt %s", ni->ni_flags & IEEE80211_NODE_QOS ? "QoS " : "", ieee80211_chan2ieee(ic, ic->ic_curchan), wh->i_fc[1] & IEEE80211_FC1_PWR_MGT ? "ena" : "dis"); ret = ieee80211_raw_output(vap, ni, m, NULL); IEEE80211_TX_UNLOCK(ic); return (ret); } /* * Assign priority to a frame based on any vlan tag assigned * to the station and/or any Diffserv setting in an IP header. * Finally, if an ACM policy is setup (in station mode) it's * applied. */ int ieee80211_classify(struct ieee80211_node *ni, struct mbuf *m) { const struct ether_header *eh = NULL; uint16_t ether_type; int v_wme_ac, d_wme_ac, ac; if (__predict_false(m->m_flags & M_ENCAP)) { struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); struct llc *llc; int hdrlen, subtype; subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; if (subtype & IEEE80211_FC0_SUBTYPE_NODATA) { ac = WME_AC_BE; goto done; } hdrlen = ieee80211_hdrsize(wh); if (m->m_pkthdr.len < hdrlen + sizeof(*llc)) return 1; llc = (struct llc *)mtodo(m, hdrlen); if (llc->llc_dsap != LLC_SNAP_LSAP || llc->llc_ssap != LLC_SNAP_LSAP || llc->llc_control != LLC_UI || llc->llc_snap.org_code[0] != 0 || llc->llc_snap.org_code[1] != 0 || llc->llc_snap.org_code[2] != 0) return 1; ether_type = llc->llc_snap.ether_type; } else { eh = mtod(m, struct ether_header *); ether_type = eh->ether_type; } /* * Always promote PAE/EAPOL frames to high priority. */ if (ether_type == htons(ETHERTYPE_PAE)) { /* NB: mark so others don't need to check header */ m->m_flags |= M_EAPOL; ac = WME_AC_VO; goto done; } /* * Non-qos traffic goes to BE. */ if ((ni->ni_flags & IEEE80211_NODE_QOS) == 0) { ac = WME_AC_BE; goto done; } /* * If node has a vlan tag then all traffic * to it must have a matching tag. */ v_wme_ac = 0; if (ni->ni_vlan != 0) { if ((m->m_flags & M_VLANTAG) == 0) { IEEE80211_NODE_STAT(ni, tx_novlantag); return 1; } if (EVL_VLANOFTAG(m->m_pkthdr.ether_vtag) != EVL_VLANOFTAG(ni->ni_vlan)) { IEEE80211_NODE_STAT(ni, tx_vlanmismatch); return 1; } /* map vlan priority to AC */ v_wme_ac = TID_TO_WME_AC(EVL_PRIOFTAG(ni->ni_vlan)); } if (eh == NULL) goto no_eh; /* XXX m_copydata may be too slow for fast path */ switch (ntohs(eh->ether_type)) { #ifdef INET case ETHERTYPE_IP: { uint8_t tos; /* * IP frame, map the DSCP bits from the TOS field. */ /* NB: ip header may not be in first mbuf */ m_copydata(m, sizeof(struct ether_header) + offsetof(struct ip, ip_tos), sizeof(tos), &tos); tos >>= 5; /* NB: ECN + low 3 bits of DSCP */ d_wme_ac = TID_TO_WME_AC(tos); break; } #endif #ifdef INET6 case ETHERTYPE_IPV6: { uint32_t flow; uint8_t tos; /* * IPv6 frame, map the DSCP bits from the traffic class field. */ m_copydata(m, sizeof(struct ether_header) + offsetof(struct ip6_hdr, ip6_flow), sizeof(flow), (caddr_t) &flow); tos = (uint8_t)(ntohl(flow) >> 20); tos >>= 5; /* NB: ECN + low 3 bits of DSCP */ d_wme_ac = TID_TO_WME_AC(tos); break; } #endif default: no_eh: d_wme_ac = WME_AC_BE; break; } /* * Use highest priority AC. */ if (v_wme_ac > d_wme_ac) ac = v_wme_ac; else ac = d_wme_ac; /* * Apply ACM policy. */ if (ni->ni_vap->iv_opmode == IEEE80211_M_STA) { static const int acmap[4] = { WME_AC_BK, /* WME_AC_BE */ WME_AC_BK, /* WME_AC_BK */ WME_AC_BE, /* WME_AC_VI */ WME_AC_VI, /* WME_AC_VO */ }; struct ieee80211com *ic = ni->ni_ic; while (ac != WME_AC_BK && ic->ic_wme.wme_wmeBssChanParams.cap_wmeParams[ac].wmep_acm) ac = acmap[ac]; } done: M_WME_SETAC(m, ac); return 0; } /* * Insure there is sufficient contiguous space to encapsulate the * 802.11 data frame. If room isn't already there, arrange for it. * Drivers and cipher modules assume we have done the necessary work * and fail rudely if they don't find the space they need. */ struct mbuf * ieee80211_mbuf_adjust(struct ieee80211vap *vap, int hdrsize, struct ieee80211_key *key, struct mbuf *m) { #define TO_BE_RECLAIMED (sizeof(struct ether_header) - sizeof(struct llc)) int needed_space = vap->iv_ic->ic_headroom + hdrsize; if (key != NULL) { /* XXX belongs in crypto code? */ needed_space += key->wk_cipher->ic_header; /* XXX frags */ /* * When crypto is being done in the host we must insure * the data are writable for the cipher routines; clone * a writable mbuf chain. * XXX handle SWMIC specially */ if (key->wk_flags & (IEEE80211_KEY_SWENCRYPT|IEEE80211_KEY_SWENMIC)) { m = m_unshare(m, IEEE80211_M_NOWAIT); if (m == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "%s: cannot get writable mbuf\n", __func__); vap->iv_stats.is_tx_nobuf++; /* XXX new stat */ return NULL; } } } /* * We know we are called just before stripping an Ethernet * header and prepending an LLC header. This means we know * there will be * sizeof(struct ether_header) - sizeof(struct llc) * bytes recovered to which we need additional space for the * 802.11 header and any crypto header. */ /* XXX check trailing space and copy instead? */ if (M_LEADINGSPACE(m) < needed_space - TO_BE_RECLAIMED) { struct mbuf *n = m_gethdr(IEEE80211_M_NOWAIT, m->m_type); if (n == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "%s: cannot expand storage\n", __func__); vap->iv_stats.is_tx_nobuf++; m_freem(m); return NULL; } KASSERT(needed_space <= MHLEN, ("not enough room, need %u got %d\n", needed_space, MHLEN)); /* * Setup new mbuf to have leading space to prepend the * 802.11 header and any crypto header bits that are * required (the latter are added when the driver calls * back to ieee80211_crypto_encap to do crypto encapsulation). */ /* NB: must be first 'cuz it clobbers m_data */ m_move_pkthdr(n, m); n->m_len = 0; /* NB: m_gethdr does not set */ n->m_data += needed_space; /* * Pull up Ethernet header to create the expected layout. * We could use m_pullup but that's overkill (i.e. we don't * need the actual data) and it cannot fail so do it inline * for speed. */ /* NB: struct ether_header is known to be contiguous */ n->m_len += sizeof(struct ether_header); m->m_len -= sizeof(struct ether_header); m->m_data += sizeof(struct ether_header); /* * Replace the head of the chain. */ n->m_next = m; m = n; } return m; #undef TO_BE_RECLAIMED } /* * Return the transmit key to use in sending a unicast frame. * If a unicast key is set we use that. When no unicast key is set * we fall back to the default transmit key. */ static __inline struct ieee80211_key * ieee80211_crypto_getucastkey(struct ieee80211vap *vap, struct ieee80211_node *ni) { if (IEEE80211_KEY_UNDEFINED(&ni->ni_ucastkey)) { if (vap->iv_def_txkey == IEEE80211_KEYIX_NONE || IEEE80211_KEY_UNDEFINED(&vap->iv_nw_keys[vap->iv_def_txkey])) return NULL; return &vap->iv_nw_keys[vap->iv_def_txkey]; } else { return &ni->ni_ucastkey; } } /* * Return the transmit key to use in sending a multicast frame. * Multicast traffic always uses the group key which is installed as * the default tx key. */ static __inline struct ieee80211_key * ieee80211_crypto_getmcastkey(struct ieee80211vap *vap, struct ieee80211_node *ni) { if (vap->iv_def_txkey == IEEE80211_KEYIX_NONE || IEEE80211_KEY_UNDEFINED(&vap->iv_nw_keys[vap->iv_def_txkey])) return NULL; return &vap->iv_nw_keys[vap->iv_def_txkey]; } /* * Encapsulate an outbound data frame. The mbuf chain is updated. * If an error is encountered NULL is returned. The caller is required * to provide a node reference and pullup the ethernet header in the * first mbuf. * * NB: Packet is assumed to be processed by ieee80211_classify which * marked EAPOL frames w/ M_EAPOL. */ struct mbuf * ieee80211_encap(struct ieee80211vap *vap, struct ieee80211_node *ni, struct mbuf *m) { #define WH4(wh) ((struct ieee80211_frame_addr4 *)(wh)) #define MC01(mc) ((struct ieee80211_meshcntl_ae01 *)mc) struct ieee80211com *ic = ni->ni_ic; #ifdef IEEE80211_SUPPORT_MESH struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_meshcntl_ae10 *mc; struct ieee80211_mesh_route *rt = NULL; int dir = -1; #endif struct ether_header eh; struct ieee80211_frame *wh; struct ieee80211_key *key; struct llc *llc; int hdrsize, hdrspace, datalen, addqos, txfrag, is4addr, is_mcast; int meshhdrsize, meshae; uint8_t *qos; int is_amsdu = 0; IEEE80211_TX_LOCK_ASSERT(ic); is_mcast = !! (m->m_flags & (M_MCAST | M_BCAST)); /* * Copy existing Ethernet header to a safe place. The * rest of the code assumes it's ok to strip it when * reorganizing state for the final encapsulation. */ KASSERT(m->m_len >= sizeof(eh), ("no ethernet header!")); ETHER_HEADER_COPY(&eh, mtod(m, caddr_t)); /* * Insure space for additional headers. First identify * transmit key to use in calculating any buffer adjustments * required. This is also used below to do privacy * encapsulation work. Then calculate the 802.11 header * size and any padding required by the driver. * * Note key may be NULL if we fall back to the default * transmit key and that is not set. In that case the * buffer may not be expanded as needed by the cipher * routines, but they will/should discard it. */ if (vap->iv_flags & IEEE80211_F_PRIVACY) { if (vap->iv_opmode == IEEE80211_M_STA || !IEEE80211_IS_MULTICAST(eh.ether_dhost) || (vap->iv_opmode == IEEE80211_M_WDS && (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY))) { key = ieee80211_crypto_getucastkey(vap, ni); } else if ((vap->iv_opmode == IEEE80211_M_WDS) && (! (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY))) { /* * Use ucastkey for DWDS transmit nodes, multicast * or otherwise. * * This is required to ensure that multicast frames * from a DWDS AP to a DWDS STA is encrypted with * a key that can actually work. * * There's no default key for multicast traffic * on a DWDS WDS VAP node (note NOT the DWDS enabled * AP VAP, the dynamically created per-STA WDS node) * so encap fails and transmit fails. */ key = ieee80211_crypto_getucastkey(vap, ni); } else { key = ieee80211_crypto_getmcastkey(vap, ni); } if (key == NULL && (m->m_flags & M_EAPOL) == 0) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, eh.ether_dhost, "no default transmit key (%s) deftxkey %u", __func__, vap->iv_def_txkey); vap->iv_stats.is_tx_nodefkey++; goto bad; } } else key = NULL; /* * XXX Some ap's don't handle QoS-encapsulated EAPOL * frames so suppress use. This may be an issue if other * ap's require all data frames to be QoS-encapsulated * once negotiated in which case we'll need to make this * configurable. * * Don't send multicast QoS frames. * Technically multicast frames can be QoS if all stations in the * BSS are also QoS. * * NB: mesh data frames are QoS, including multicast frames. */ addqos = (((is_mcast == 0) && (ni->ni_flags & (IEEE80211_NODE_QOS|IEEE80211_NODE_HT))) || (vap->iv_opmode == IEEE80211_M_MBSS)) && (m->m_flags & M_EAPOL) == 0; if (addqos) hdrsize = sizeof(struct ieee80211_qosframe); else hdrsize = sizeof(struct ieee80211_frame); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) { /* * Mesh data frames are encapsulated according to the * rules of Section 11B.8.5 (p.139 of D3.0 spec). * o Group Addressed data (aka multicast) originating * at the local sta are sent w/ 3-address format and * address extension mode 00 * o Individually Addressed data (aka unicast) originating * at the local sta are sent w/ 4-address format and * address extension mode 00 * o Group Addressed data forwarded from a non-mesh sta are * sent w/ 3-address format and address extension mode 01 * o Individually Address data from another sta are sent * w/ 4-address format and address extension mode 10 */ is4addr = 0; /* NB: don't use, disable */ if (!IEEE80211_IS_MULTICAST(eh.ether_dhost)) { rt = ieee80211_mesh_rt_find(vap, eh.ether_dhost); KASSERT(rt != NULL, ("route is NULL")); dir = IEEE80211_FC1_DIR_DSTODS; hdrsize += IEEE80211_ADDR_LEN; if (rt->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY) { if (IEEE80211_ADDR_EQ(rt->rt_mesh_gate, vap->iv_myaddr)) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, eh.ether_dhost, "%s", "trying to send to ourself"); goto bad; } meshae = IEEE80211_MESH_AE_10; meshhdrsize = sizeof(struct ieee80211_meshcntl_ae10); } else { meshae = IEEE80211_MESH_AE_00; meshhdrsize = sizeof(struct ieee80211_meshcntl); } } else { dir = IEEE80211_FC1_DIR_FROMDS; if (!IEEE80211_ADDR_EQ(eh.ether_shost, vap->iv_myaddr)) { /* proxy group */ meshae = IEEE80211_MESH_AE_01; meshhdrsize = sizeof(struct ieee80211_meshcntl_ae01); } else { /* group */ meshae = IEEE80211_MESH_AE_00; meshhdrsize = sizeof(struct ieee80211_meshcntl); } } } else { #endif /* * 4-address frames need to be generated for: * o packets sent through a WDS vap (IEEE80211_M_WDS) * o packets sent through a vap marked for relaying * (e.g. a station operating with dynamic WDS) */ is4addr = vap->iv_opmode == IEEE80211_M_WDS || ((vap->iv_flags_ext & IEEE80211_FEXT_4ADDR) && !IEEE80211_ADDR_EQ(eh.ether_shost, vap->iv_myaddr)); if (is4addr) hdrsize += IEEE80211_ADDR_LEN; meshhdrsize = meshae = 0; #ifdef IEEE80211_SUPPORT_MESH } #endif /* * Honor driver DATAPAD requirement. */ if (ic->ic_flags & IEEE80211_F_DATAPAD) hdrspace = roundup(hdrsize, sizeof(uint32_t)); else hdrspace = hdrsize; if (__predict_true((m->m_flags & M_FF) == 0)) { /* * Normal frame. */ m = ieee80211_mbuf_adjust(vap, hdrspace + meshhdrsize, key, m); if (m == NULL) { /* NB: ieee80211_mbuf_adjust handles msgs+statistics */ goto bad; } /* NB: this could be optimized 'cuz of ieee80211_mbuf_adjust */ m_adj(m, sizeof(struct ether_header) - sizeof(struct llc)); llc = mtod(m, struct llc *); llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP; llc->llc_control = LLC_UI; llc->llc_snap.org_code[0] = 0; llc->llc_snap.org_code[1] = 0; llc->llc_snap.org_code[2] = 0; llc->llc_snap.ether_type = eh.ether_type; } else { #ifdef IEEE80211_SUPPORT_SUPERG /* * Aggregated frame. Check if it's for AMSDU or FF. * * XXX TODO: IEEE80211_NODE_AMSDU* isn't implemented * anywhere for some reason. But, since 11n requires * AMSDU RX, we can just assume "11n" == "AMSDU". */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: called; M_FF\n", __func__); if (ieee80211_amsdu_tx_ok(ni)) { m = ieee80211_amsdu_encap(vap, m, hdrspace + meshhdrsize, key); is_amsdu = 1; } else { m = ieee80211_ff_encap(vap, m, hdrspace + meshhdrsize, key); } if (m == NULL) #endif goto bad; } datalen = m->m_pkthdr.len; /* NB: w/o 802.11 header */ M_PREPEND(m, hdrspace + meshhdrsize, IEEE80211_M_NOWAIT); if (m == NULL) { vap->iv_stats.is_tx_nobuf++; goto bad; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_DATA; *(uint16_t *)wh->i_dur = 0; qos = NULL; /* NB: quiet compiler */ if (is4addr) { wh->i_fc[1] = IEEE80211_FC1_DIR_DSTODS; IEEE80211_ADDR_COPY(wh->i_addr1, ni->ni_macaddr); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_dhost); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, eh.ether_shost); } else switch (vap->iv_opmode) { case IEEE80211_M_STA: wh->i_fc[1] = IEEE80211_FC1_DIR_TODS; IEEE80211_ADDR_COPY(wh->i_addr1, ni->ni_bssid); IEEE80211_ADDR_COPY(wh->i_addr2, eh.ether_shost); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_dhost); break; case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, eh.ether_shost); /* * NB: always use the bssid from iv_bss as the * neighbor's may be stale after an ibss merge */ IEEE80211_ADDR_COPY(wh->i_addr3, vap->iv_bss->ni_bssid); break; case IEEE80211_M_HOSTAP: wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, ni->ni_bssid); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_shost); break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_M_MBSS: /* NB: offset by hdrspace to deal with DATAPAD */ mc = (struct ieee80211_meshcntl_ae10 *) (mtod(m, uint8_t *) + hdrspace); wh->i_fc[1] = dir; switch (meshae) { case IEEE80211_MESH_AE_00: /* no proxy */ mc->mc_flags = 0; if (dir == IEEE80211_FC1_DIR_DSTODS) { /* ucast */ IEEE80211_ADDR_COPY(wh->i_addr1, ni->ni_macaddr); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_dhost); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, eh.ether_shost); qos =((struct ieee80211_qosframe_addr4 *) wh)->i_qos; } else if (dir == IEEE80211_FC1_DIR_FROMDS) { /* mcast */ IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_shost); qos = ((struct ieee80211_qosframe *) wh)->i_qos; } break; case IEEE80211_MESH_AE_01: /* mcast, proxy */ wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, vap->iv_myaddr); mc->mc_flags = 1; IEEE80211_ADDR_COPY(MC01(mc)->mc_addr4, eh.ether_shost); qos = ((struct ieee80211_qosframe *) wh)->i_qos; break; case IEEE80211_MESH_AE_10: /* ucast, proxy */ KASSERT(rt != NULL, ("route is NULL")); IEEE80211_ADDR_COPY(wh->i_addr1, rt->rt_nexthop); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, rt->rt_mesh_gate); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, vap->iv_myaddr); mc->mc_flags = IEEE80211_MESH_AE_10; IEEE80211_ADDR_COPY(mc->mc_addr5, eh.ether_dhost); IEEE80211_ADDR_COPY(mc->mc_addr6, eh.ether_shost); qos = ((struct ieee80211_qosframe_addr4 *) wh)->i_qos; break; default: KASSERT(0, ("meshae %d", meshae)); break; } mc->mc_ttl = ms->ms_ttl; ms->ms_seq++; le32enc(mc->mc_seq, ms->ms_seq); break; #endif case IEEE80211_M_WDS: /* NB: is4addr should always be true */ default: goto bad; } if (m->m_flags & M_MORE_DATA) wh->i_fc[1] |= IEEE80211_FC1_MORE_DATA; if (addqos) { int ac, tid; if (is4addr) { qos = ((struct ieee80211_qosframe_addr4 *) wh)->i_qos; /* NB: mesh case handled earlier */ } else if (vap->iv_opmode != IEEE80211_M_MBSS) qos = ((struct ieee80211_qosframe *) wh)->i_qos; ac = M_WME_GETAC(m); /* map from access class/queue to 11e header priorty value */ tid = WME_AC_TO_TID(ac); qos[0] = tid & IEEE80211_QOS_TID; if (ic->ic_wme.wme_wmeChanParams.cap_wmeParams[ac].wmep_noackPolicy) qos[0] |= IEEE80211_QOS_ACKPOLICY_NOACK; #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) qos[1] = IEEE80211_QOS_MC; else #endif qos[1] = 0; wh->i_fc[0] |= IEEE80211_FC0_SUBTYPE_QOS_DATA; /* * If this is an A-MSDU then ensure we set the * relevant field. */ if (is_amsdu) qos[0] |= IEEE80211_QOS_AMSDU; /* * XXX TODO TX lock is needed for atomic updates of sequence * numbers. If the driver does it, then don't do it here; * and we don't need the TX lock held. */ if ((m->m_flags & M_AMPDU_MPDU) == 0) { ieee80211_output_seqno_assign(ni, tid, m); } else { /* * NB: don't assign a sequence # to potential * aggregates; we expect this happens at the * point the frame comes off any aggregation q * as otherwise we may introduce holes in the * BA sequence space and/or make window accouting * more difficult. * * XXX may want to control this with a driver * capability; this may also change when we pull * aggregation up into net80211 */ /* NB: zero out i_seq field (for s/w encryption etc) */ *(uint16_t *)wh->i_seq = 0; } } else { ieee80211_output_seqno_assign(ni, IEEE80211_NONQOS_TID, m); /* * XXX TODO: we shouldn't allow EAPOL, etc that would * be forced to be non-QoS traffic to be A-MSDU encapsulated. */ if (is_amsdu) - printf("%s: XXX ERROR: is_amsdu set; not QoS!\n", + net80211_vap_printf(vap, + "%s: XXX ERROR: is_amsdu set; not QoS!\n", __func__); } /* * Check if xmit fragmentation is required. * * If the hardware does fragmentation offload, then don't bother * doing it here. * * Don't send AMPDU/FF/AMSDU through fragmentation. * * 802.11-2016 10.2.7 (Fragmentation/defragmentation overview) */ if (IEEE80211_CONF_FRAG_OFFLOAD(ic)) txfrag = 0; else txfrag = (m->m_pkthdr.len > vap->iv_fragthreshold && !IEEE80211_IS_MULTICAST(wh->i_addr1) && (vap->iv_caps & IEEE80211_C_TXFRAG) && (m->m_flags & (M_FF | M_AMPDU_MPDU)) == 0); if (key != NULL) { /* * IEEE 802.1X: send EAPOL frames always in the clear. * WPA/WPA2: encrypt EAPOL keys when pairwise keys are set. */ if ((m->m_flags & M_EAPOL) == 0 || ((vap->iv_flags & IEEE80211_F_WPA) && (vap->iv_opmode == IEEE80211_M_STA ? !IEEE80211_KEY_UNDEFINED(key) : !IEEE80211_KEY_UNDEFINED(&ni->ni_ucastkey)))) { wh->i_fc[1] |= IEEE80211_FC1_PROTECTED; if (!ieee80211_crypto_enmic(vap, key, m, txfrag)) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_OUTPUT, eh.ether_dhost, "%s", "enmic failed, discard frame"); vap->iv_stats.is_crypto_enmicfail++; goto bad; } } } if (txfrag && !ieee80211_fragment(vap, m, hdrsize, key != NULL ? key->wk_cipher->ic_header : 0, vap->iv_fragthreshold)) goto bad; m->m_flags |= M_ENCAP; /* mark encapsulated */ IEEE80211_NODE_STAT(ni, tx_data); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_NODE_STAT(ni, tx_mcast); m->m_flags |= M_MCAST; } else IEEE80211_NODE_STAT(ni, tx_ucast); IEEE80211_NODE_STAT_ADD(ni, tx_bytes, datalen); return m; bad: if (m != NULL) m_freem(m); return NULL; #undef WH4 #undef MC01 } /** * @brief Free an 802.11 frame mbuf. * * Note that since a "frame" may consist of an mbuf packet * list containing the 802.11 fragments that make up said * frame, it will free everything in the mbuf packet list. * * @param m mbuf packet list to free */ void ieee80211_free_mbuf(struct mbuf *m) { struct mbuf *next; if (m == NULL) return; do { next = m->m_nextpkt; m->m_nextpkt = NULL; m_freem(m); } while ((m = next) != NULL); } /** * @brief Fragment the frame according to the specified mtu. * * This implements the fragmentation part of 802.11-2016 10.2.7 * (Fragmentation/defragmentation overview.) * * The size of the 802.11 header (w/o padding) is provided * so we don't need to recalculate it. We create a new * mbuf for each fragment and chain it through m_nextpkt; * we might be able to optimize this by reusing the original * packet's mbufs but that is significantly more complicated. * * A node reference is NOT acquired for each fragment in * the list - the caller is assumed to have taken a node * reference for the whole list. The fragment mbufs do not * have a node pointer. * * Fragments will have the sequence number and fragment numbers * assigned. However, Fragments will NOT have a sequence number * assigned via M_SEQNO_SET. * * This must be called after assigning sequence numbers; it * modifies the i_seq field in the 802.11 header to include * the fragment number. * * @param vap ieee80211vap interface * @param m0 pointer to mbuf list to fragment * @param hdrsize header size to reserver * @param ciphdrsize crypto cipher header size to reserve * @param mtu maximum fragment size * @retval 1 if successful, with the mbuf pointed at by m0 * turned into an mbuf list of fragments (with the original * mbuf being truncated.) * @retval 0 if failure, the mbuf needs to be freed by the caller */ static int ieee80211_fragment(struct ieee80211vap *vap, struct mbuf *m0, u_int hdrsize, u_int ciphdrsize, u_int mtu) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_frame *wh, *whf; struct mbuf *m, *prev; u_int totalhdrsize, fragno, fragsize, off, remainder, payload; u_int hdrspace; KASSERT(m0->m_nextpkt == NULL, ("mbuf already chained?")); KASSERT(m0->m_pkthdr.len > mtu, ("pktlen %u mtu %u", m0->m_pkthdr.len, mtu)); /* * Honor driver DATAPAD requirement. */ if (ic->ic_flags & IEEE80211_F_DATAPAD) hdrspace = roundup(hdrsize, sizeof(uint32_t)); else hdrspace = hdrsize; wh = mtod(m0, struct ieee80211_frame *); /* NB: mark the first frag; it will be propagated below */ wh->i_fc[1] |= IEEE80211_FC1_MORE_FRAG; totalhdrsize = hdrspace + ciphdrsize; fragno = 1; off = mtu - ciphdrsize; remainder = m0->m_pkthdr.len - off; prev = m0; do { fragsize = MIN(totalhdrsize + remainder, mtu); m = m_get2(fragsize, IEEE80211_M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) goto bad; /* leave room to prepend any cipher header */ m_align(m, fragsize - ciphdrsize); /* * Form the header in the fragment. Note that since * we mark the first fragment with the MORE_FRAG bit * it automatically is propagated to each fragment; we * need only clear it on the last fragment (done below). * NB: frag 1+ dont have Mesh Control field present. */ whf = mtod(m, struct ieee80211_frame *); memcpy(whf, wh, hdrsize); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_getqos(wh)[1] &= ~IEEE80211_QOS_MC; #endif *(uint16_t *)&whf->i_seq[0] |= htole16( (fragno & IEEE80211_SEQ_FRAG_MASK) << IEEE80211_SEQ_FRAG_SHIFT); fragno++; payload = fragsize - totalhdrsize; /* NB: destination is known to be contiguous */ m_copydata(m0, off, payload, mtod(m, uint8_t *) + hdrspace); m->m_len = hdrspace + payload; m->m_pkthdr.len = hdrspace + payload; m->m_flags |= M_FRAG; /* chain up the fragment */ prev->m_nextpkt = m; prev = m; /* deduct fragment just formed */ remainder -= payload; off += payload; } while (remainder != 0); /* set the last fragment */ m->m_flags |= M_LASTFRAG; whf->i_fc[1] &= ~IEEE80211_FC1_MORE_FRAG; /* strip first mbuf now that everything has been copied */ m_adj(m0, -(m0->m_pkthdr.len - (mtu - ciphdrsize))); m0->m_flags |= M_FIRSTFRAG | M_FRAG; vap->iv_stats.is_tx_fragframes++; vap->iv_stats.is_tx_frags += fragno-1; return 1; bad: /* reclaim fragments but leave original frame for caller to free */ ieee80211_free_mbuf(m0->m_nextpkt); m0->m_nextpkt = NULL; return 0; } /* * Add a supported rates element id to a frame. */ uint8_t * ieee80211_add_rates(uint8_t *frm, const struct ieee80211_rateset *rs) { int nrates; *frm++ = IEEE80211_ELEMID_RATES; nrates = rs->rs_nrates; if (nrates > IEEE80211_RATE_SIZE) nrates = IEEE80211_RATE_SIZE; *frm++ = nrates; memcpy(frm, rs->rs_rates, nrates); return frm + nrates; } /* * Add an extended supported rates element id to a frame. */ uint8_t * ieee80211_add_xrates(uint8_t *frm, const struct ieee80211_rateset *rs) { /* * Add an extended supported rates element if operating in 11g mode. */ if (rs->rs_nrates > IEEE80211_RATE_SIZE) { int nrates = rs->rs_nrates - IEEE80211_RATE_SIZE; *frm++ = IEEE80211_ELEMID_XRATES; *frm++ = nrates; memcpy(frm, rs->rs_rates + IEEE80211_RATE_SIZE, nrates); frm += nrates; } return frm; } /* * Add an ssid element to a frame. */ uint8_t * ieee80211_add_ssid(uint8_t *frm, const uint8_t *ssid, u_int len) { *frm++ = IEEE80211_ELEMID_SSID; *frm++ = len; memcpy(frm, ssid, len); return frm + len; } /* * Add an erp element to a frame. */ static uint8_t * ieee80211_add_erp(uint8_t *frm, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; uint8_t erp; *frm++ = IEEE80211_ELEMID_ERP; *frm++ = 1; erp = 0; /* * TODO: This uses the global flags for now because * the per-VAP flags are fine for per-VAP, but don't * take into account which VAPs share the same channel * and which are on different channels. * * ERP and HT/VHT protection mode is a function of * how many stations are on a channel, not specifically * the VAP or global. But, until we grow that status, * the global flag will have to do. */ if (ic->ic_flags_ext & IEEE80211_FEXT_NONERP_PR) erp |= IEEE80211_ERP_NON_ERP_PRESENT; /* * TODO: same as above; these should be based not * on the vap or ic flags, but instead on a combination * of per-VAP and channels. */ if (ic->ic_flags & IEEE80211_F_USEPROT) erp |= IEEE80211_ERP_USE_PROTECTION; if (ic->ic_flags & IEEE80211_F_USEBARKER) erp |= IEEE80211_ERP_LONG_PREAMBLE; *frm++ = erp; return frm; } /* * Add a CFParams element to a frame. */ static uint8_t * ieee80211_add_cfparms(uint8_t *frm, struct ieee80211com *ic) { #define ADDSHORT(frm, v) do { \ le16enc(frm, v); \ frm += 2; \ } while (0) *frm++ = IEEE80211_ELEMID_CFPARMS; *frm++ = 6; *frm++ = 0; /* CFP count */ *frm++ = 2; /* CFP period */ ADDSHORT(frm, 0); /* CFP MaxDuration (TU) */ ADDSHORT(frm, 0); /* CFP CurRemaining (TU) */ return frm; #undef ADDSHORT } static __inline uint8_t * add_appie(uint8_t *frm, const struct ieee80211_appie *ie) { memcpy(frm, ie->ie_data, ie->ie_len); return frm + ie->ie_len; } static __inline uint8_t * add_ie(uint8_t *frm, const uint8_t *ie) { memcpy(frm, ie, 2 + ie[1]); return frm + 2 + ie[1]; } #define WME_OUI_BYTES 0x00, 0x50, 0xf2 /* * Add a WME information element to a frame. */ uint8_t * ieee80211_add_wme_info(uint8_t *frm, struct ieee80211_wme_state *wme, struct ieee80211_node *ni) { static const uint8_t oui[4] = { WME_OUI_BYTES, WME_OUI_TYPE }; struct ieee80211vap *vap = ni->ni_vap; *frm++ = IEEE80211_ELEMID_VENDOR; *frm++ = sizeof(struct ieee80211_wme_info) - 2; memcpy(frm, oui, sizeof(oui)); frm += sizeof(oui); *frm++ = WME_INFO_OUI_SUBTYPE; *frm++ = WME_VERSION; /* QoS info field depends upon operating mode */ switch (vap->iv_opmode) { case IEEE80211_M_HOSTAP: *frm = wme->wme_bssChanParams.cap_info; if (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD) *frm |= WME_CAPINFO_UAPSD_EN; frm++; break; case IEEE80211_M_STA: /* * NB: UAPSD drivers must set this up in their * VAP creation method. */ *frm++ = vap->iv_uapsdinfo; break; default: *frm++ = 0; break; } return frm; } /* * Add a WME parameters element to a frame. */ static uint8_t * ieee80211_add_wme_param(uint8_t *frm, struct ieee80211_wme_state *wme, int uapsd_enable) { #define ADDSHORT(frm, v) do { \ le16enc(frm, v); \ frm += 2; \ } while (0) /* NB: this works 'cuz a param has an info at the front */ static const struct ieee80211_wme_info param = { .wme_id = IEEE80211_ELEMID_VENDOR, .wme_len = sizeof(struct ieee80211_wme_param) - 2, .wme_oui = { WME_OUI_BYTES }, .wme_type = WME_OUI_TYPE, .wme_subtype = WME_PARAM_OUI_SUBTYPE, .wme_version = WME_VERSION, }; int i; memcpy(frm, ¶m, sizeof(param)); frm += __offsetof(struct ieee80211_wme_info, wme_info); *frm = wme->wme_bssChanParams.cap_info; /* AC info */ if (uapsd_enable) *frm |= WME_CAPINFO_UAPSD_EN; frm++; *frm++ = 0; /* reserved field */ /* XXX TODO - U-APSD bits - SP, flags below */ for (i = 0; i < WME_NUM_AC; i++) { const struct wmeParams *ac = &wme->wme_bssChanParams.cap_wmeParams[i]; *frm++ = _IEEE80211_SHIFTMASK(i, WME_PARAM_ACI) | _IEEE80211_SHIFTMASK(ac->wmep_acm, WME_PARAM_ACM) | _IEEE80211_SHIFTMASK(ac->wmep_aifsn, WME_PARAM_AIFSN) ; *frm++ = _IEEE80211_SHIFTMASK(ac->wmep_logcwmax, WME_PARAM_LOGCWMAX) | _IEEE80211_SHIFTMASK(ac->wmep_logcwmin, WME_PARAM_LOGCWMIN) ; ADDSHORT(frm, ac->wmep_txopLimit); } return frm; #undef ADDSHORT } #undef WME_OUI_BYTES /* * Add an 11h Power Constraint element to a frame. */ static uint8_t * ieee80211_add_powerconstraint(uint8_t *frm, struct ieee80211vap *vap) { const struct ieee80211_channel *c = vap->iv_bss->ni_chan; /* XXX per-vap tx power limit? */ int8_t limit = vap->iv_ic->ic_txpowlimit / 2; frm[0] = IEEE80211_ELEMID_PWRCNSTR; frm[1] = 1; frm[2] = c->ic_maxregpower > limit ? c->ic_maxregpower - limit : 0; return frm + 3; } /* * Add an 11h Power Capability element to a frame. */ static uint8_t * ieee80211_add_powercapability(uint8_t *frm, const struct ieee80211_channel *c) { frm[0] = IEEE80211_ELEMID_PWRCAP; frm[1] = 2; frm[2] = c->ic_minpower; frm[3] = c->ic_maxpower; return frm + 4; } /* * Add an 11h Supported Channels element to a frame. */ static uint8_t * ieee80211_add_supportedchannels(uint8_t *frm, struct ieee80211com *ic) { static const int ielen = 26; frm[0] = IEEE80211_ELEMID_SUPPCHAN; frm[1] = ielen; /* XXX not correct */ memcpy(frm+2, ic->ic_chan_avail, ielen); return frm + 2 + ielen; } /* * Add an 11h Quiet time element to a frame. */ static uint8_t * ieee80211_add_quiet(uint8_t *frm, struct ieee80211vap *vap, int update) { struct ieee80211_quiet_ie *quiet = (struct ieee80211_quiet_ie *) frm; quiet->quiet_ie = IEEE80211_ELEMID_QUIET; quiet->len = 6; /* * Only update every beacon interval - otherwise probe responses * would update the quiet count value. */ if (update) { if (vap->iv_quiet_count_value == 1) vap->iv_quiet_count_value = vap->iv_quiet_count; else if (vap->iv_quiet_count_value > 1) vap->iv_quiet_count_value--; } if (vap->iv_quiet_count_value == 0) { /* value 0 is reserved as per 802.11h standerd */ vap->iv_quiet_count_value = 1; } quiet->tbttcount = vap->iv_quiet_count_value; quiet->period = vap->iv_quiet_period; quiet->duration = htole16(vap->iv_quiet_duration); quiet->offset = htole16(vap->iv_quiet_offset); return frm + sizeof(*quiet); } /* * Add an 11h Channel Switch Announcement element to a frame. * Note that we use the per-vap CSA count to adjust the global * counter so we can use this routine to form probe response * frames and get the current count. */ static uint8_t * ieee80211_add_csa(uint8_t *frm, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_csa_ie *csa = (struct ieee80211_csa_ie *) frm; csa->csa_ie = IEEE80211_ELEMID_CSA; csa->csa_len = 3; csa->csa_mode = 1; /* XXX force quiet on channel */ csa->csa_newchan = ieee80211_chan2ieee(ic, ic->ic_csa_newchan); csa->csa_count = ic->ic_csa_count - vap->iv_csa_count; return frm + sizeof(*csa); } /* * Add an 11h country information element to a frame. */ static uint8_t * ieee80211_add_countryie(uint8_t *frm, struct ieee80211com *ic) { if (ic->ic_countryie == NULL || ic->ic_countryie_chan != ic->ic_bsschan) { /* * Handle lazy construction of ie. This is done on * first use and after a channel change that requires * re-calculation. */ if (ic->ic_countryie != NULL) IEEE80211_FREE(ic->ic_countryie, M_80211_NODE_IE); ic->ic_countryie = ieee80211_alloc_countryie(ic); if (ic->ic_countryie == NULL) return frm; ic->ic_countryie_chan = ic->ic_bsschan; } return add_appie(frm, ic->ic_countryie); } uint8_t * ieee80211_add_wpa(uint8_t *frm, const struct ieee80211vap *vap) { if (vap->iv_flags & IEEE80211_F_WPA1 && vap->iv_wpa_ie != NULL) return (add_ie(frm, vap->iv_wpa_ie)); else { /* XXX else complain? */ return (frm); } } uint8_t * ieee80211_add_rsn(uint8_t *frm, const struct ieee80211vap *vap) { if (vap->iv_flags & IEEE80211_F_WPA2 && vap->iv_rsn_ie != NULL) return (add_ie(frm, vap->iv_rsn_ie)); else { /* XXX else complain? */ return (frm); } } uint8_t * ieee80211_add_qos(uint8_t *frm, const struct ieee80211_node *ni) { if (ni->ni_flags & IEEE80211_NODE_QOS) { *frm++ = IEEE80211_ELEMID_QOS; *frm++ = 1; *frm++ = 0; } return (frm); } /* * ieee80211_send_probereq(): send a probe request frame with the specified ssid * and any optional information element data; some helper functions as FW based * HW scans need some of that information passed too. */ static uint32_t ieee80211_probereq_ie_len(struct ieee80211vap *vap, struct ieee80211com *ic) { const struct ieee80211_rateset *rs; rs = ieee80211_get_suprates(ic, ic->ic_curchan); /* * prreq frame format * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates (if needed) * [tlv] HT cap (optional) * [tlv] VHT cap (optional) * [tlv] WPA (optional) * [tlv] user-specified ie's */ return ( 2 + IEEE80211_NWID_LEN + 2 + IEEE80211_RATE_SIZE + ((rs->rs_nrates > IEEE80211_RATE_SIZE) ? 2 + (rs->rs_nrates - IEEE80211_RATE_SIZE) : 0) + (((vap->iv_opmode == IEEE80211_M_IBSS) && (vap->iv_flags_ht & IEEE80211_FHT_HT)) ? sizeof(struct ieee80211_ie_htcap) : 0) #ifdef notyet + sizeof(struct ieee80211_ie_htinfo) /* XXX not needed? */ + 2 + sizeof(struct ieee80211_vht_cap) #endif + ((vap->iv_flags & IEEE80211_F_WPA1 && vap->iv_wpa_ie != NULL) ? vap->iv_wpa_ie[1] : 0) + (vap->iv_appie_probereq != NULL ? vap->iv_appie_probereq->ie_len : 0) ); } int ieee80211_probereq_ie(struct ieee80211vap *vap, struct ieee80211com *ic, uint8_t **frmp, uint32_t *frmlen, const uint8_t *ssid, size_t ssidlen, bool alloc) { const struct ieee80211_rateset *rs; uint8_t *frm; uint32_t len; if (!alloc && (frmp == NULL || frmlen == NULL)) return (EINVAL); len = ieee80211_probereq_ie_len(vap, ic); if (!alloc && len > *frmlen) return (ENOBUFS); /* For HW scans we usually do not pass in the SSID as IE. */ if (ssidlen == -1) len -= (2 + IEEE80211_NWID_LEN); if (alloc) { frm = IEEE80211_MALLOC(len, M_80211_VAP, IEEE80211_M_WAITOK | IEEE80211_M_ZERO); *frmp = frm; *frmlen = len; } else frm = *frmp; if (ssidlen != -1) frm = ieee80211_add_ssid(frm, ssid, ssidlen); rs = ieee80211_get_suprates(ic, ic->ic_curchan); frm = ieee80211_add_rates(frm, rs); frm = ieee80211_add_xrates(frm, rs); /* * Note: we can't use bss; we don't have one yet. * * So, we should announce our capabilities * in this channel mode (2g/5g), not the * channel details itself. */ if ((vap->iv_opmode == IEEE80211_M_IBSS) && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { struct ieee80211_channel *c; /* * Get the HT channel that we should try upgrading to. * If we can do 40MHz then this'll upgrade it appropriately. */ c = ieee80211_ht_adjust_channel(ic, ic->ic_curchan, vap->iv_flags_ht); frm = ieee80211_add_htcap_ch(frm, vap, c); } /* * XXX TODO: need to figure out what/how to update the * VHT channel. */ #ifdef notyet if (vap->iv_vht_flags & IEEE80211_FVHT_VHT) { struct ieee80211_channel *c; c = ieee80211_ht_adjust_channel(ic, ic->ic_curchan, vap->iv_flags_ht); c = ieee80211_vht_adjust_channel(ic, c, vap->iv_vht_flags); frm = ieee80211_add_vhtcap_ch(frm, vap, c); } #endif frm = ieee80211_add_wpa(frm, vap); if (vap->iv_appie_probereq != NULL) frm = add_appie(frm, vap->iv_appie_probereq); if (!alloc) { *frmp = frm; *frmlen = len; } return (0); } int ieee80211_send_probereq(struct ieee80211_node *ni, const uint8_t sa[IEEE80211_ADDR_LEN], const uint8_t da[IEEE80211_ADDR_LEN], const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t *ssid, size_t ssidlen) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_node *bss; const struct ieee80211_txparam *tp; struct ieee80211_bpf_params params; struct mbuf *m; uint8_t *frm; uint32_t frmlen; int ret; bss = ieee80211_ref_node(vap->iv_bss); if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT, ni, "block %s frame in CAC state", "probe request"); vap->iv_stats.is_tx_badstate++; ieee80211_free_node(bss); return EIO; /* XXX */ } /* * Hold a reference on the node so it doesn't go away until after * the xmit is complete all the way in the driver. On error we * will remove our reference. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); /* See comments above for entire frame format. */ frmlen = ieee80211_probereq_ie_len(vap, ic); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), frmlen); if (m == NULL) { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); ieee80211_free_node(bss); return ENOMEM; } ret = ieee80211_probereq_ie(vap, ic, &frm, &frmlen, ssid, ssidlen, false); KASSERT(ret == 0, ("%s: ieee80211_probereq_ie failed: %d\n", __func__, ret)); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); KASSERT(M_LEADINGSPACE(m) >= sizeof(struct ieee80211_frame), ("leading space %zd", M_LEADINGSPACE(m))); M_PREPEND(m, sizeof(struct ieee80211_frame), IEEE80211_M_NOWAIT); if (m == NULL) { /* NB: cannot happen */ ieee80211_free_node(ni); ieee80211_free_node(bss); return ENOMEM; } IEEE80211_TX_LOCK(ic); ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_REQ, IEEE80211_NONQOS_TID, sa, da, bssid); /* XXX power management? */ m->m_flags |= M_ENCAP; /* mark encapsulated */ M_WME_SETAC(m, WME_AC_BE); IEEE80211_NODE_STAT(ni, tx_probereq); IEEE80211_NODE_STAT(ni, tx_mgmt); IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DUMPPKTS, "send probe req on channel %u bssid %s sa %6D da %6D ssid \"%.*s\"\n", ieee80211_chan2ieee(ic, ic->ic_curchan), ether_sprintf(bssid), sa, ":", da, ":", ssidlen, ssid); memset(¶ms, 0, sizeof(params)); params.ibp_pri = M_WME_GETAC(m); tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; params.ibp_rate0 = tp->mgmtrate; if (IEEE80211_IS_MULTICAST(da)) { params.ibp_flags |= IEEE80211_BPF_NOACK; params.ibp_try0 = 1; } else params.ibp_try0 = tp->maxretry; params.ibp_power = ni->ni_txpower; ret = ieee80211_raw_output(vap, ni, m, ¶ms); IEEE80211_TX_UNLOCK(ic); ieee80211_free_node(bss); return (ret); } /* * Calculate capability information for mgt frames. * * This fills out the 16 bit capability field in various management * frames for non-DMG STAs. DMG STAs are not supported. * * See 802.11-2020 9.4.1.4 (Capability Information Field) for the * field definitions. */ uint16_t ieee80211_getcapinfo(struct ieee80211vap *vap, struct ieee80211_channel *chan) { uint16_t capinfo; KASSERT(vap->iv_opmode != IEEE80211_M_STA, ("station mode")); if (vap->iv_opmode == IEEE80211_M_HOSTAP) capinfo = IEEE80211_CAPINFO_ESS; else if (vap->iv_opmode == IEEE80211_M_IBSS) capinfo = IEEE80211_CAPINFO_IBSS; else capinfo = 0; if (vap->iv_flags & IEEE80211_F_PRIVACY) capinfo |= IEEE80211_CAPINFO_PRIVACY; if ((vap->iv_flags & IEEE80211_F_SHPREAMBLE) && IEEE80211_IS_CHAN_2GHZ(chan)) capinfo |= IEEE80211_CAPINFO_SHORT_PREAMBLE; if (vap->iv_flags & IEEE80211_F_SHSLOT) capinfo |= IEEE80211_CAPINFO_SHORT_SLOTTIME; if (IEEE80211_IS_CHAN_5GHZ(chan) && (vap->iv_flags & IEEE80211_F_DOTH)) capinfo |= IEEE80211_CAPINFO_SPECTRUM_MGMT; return capinfo; } /* * Send a management frame. The node is for the destination (or ic_bss * when in station mode). Nodes other than ic_bss have their reference * count bumped to reflect our use for an indeterminant time. */ int ieee80211_send_mgmt(struct ieee80211_node *ni, int type, int arg) { #define HTFLAGS (IEEE80211_NODE_HT | IEEE80211_NODE_HTCOMPAT) #define senderr(_x, _v) do { vap->iv_stats._v++; ret = _x; goto bad; } while (0) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_node *bss = vap->iv_bss; struct ieee80211_bpf_params params; struct mbuf *m; uint8_t *frm; uint16_t capinfo; int has_challenge, is_shared_key, ret, status; KASSERT(ni != NULL, ("null node")); /* * Hold a reference on the node so it doesn't go away until after * the xmit is complete all the way in the driver. On error we * will remove our reference. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); memset(¶ms, 0, sizeof(params)); switch (type) { case IEEE80211_FC0_SUBTYPE_AUTH: status = arg >> 16; arg &= 0xffff; has_challenge = ((arg == IEEE80211_AUTH_SHARED_CHALLENGE || arg == IEEE80211_AUTH_SHARED_RESPONSE) && ni->ni_challenge != NULL); /* * Deduce whether we're doing open authentication or * shared key authentication. We do the latter if * we're in the middle of a shared key authentication * handshake or if we're initiating an authentication * request and configured to use shared key. */ is_shared_key = has_challenge || arg >= IEEE80211_AUTH_SHARED_RESPONSE || (arg == IEEE80211_AUTH_SHARED_REQUEST && bss->ni_authmode == IEEE80211_AUTH_SHARED); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), 3 * sizeof(uint16_t) + (has_challenge && status == IEEE80211_STATUS_SUCCESS ? sizeof(uint16_t)+IEEE80211_CHALLENGE_LEN : 0)); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); ((uint16_t *)frm)[0] = (is_shared_key) ? htole16(IEEE80211_AUTH_ALG_SHARED) : htole16(IEEE80211_AUTH_ALG_OPEN); ((uint16_t *)frm)[1] = htole16(arg); /* sequence number */ ((uint16_t *)frm)[2] = htole16(status);/* status */ if (has_challenge && status == IEEE80211_STATUS_SUCCESS) { ((uint16_t *)frm)[3] = htole16((IEEE80211_CHALLENGE_LEN << 8) | IEEE80211_ELEMID_CHALLENGE); memcpy(&((uint16_t *)frm)[4], ni->ni_challenge, IEEE80211_CHALLENGE_LEN); m->m_pkthdr.len = m->m_len = 4 * sizeof(uint16_t) + IEEE80211_CHALLENGE_LEN; if (arg == IEEE80211_AUTH_SHARED_RESPONSE) { IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, "request encrypt frame (%s)", __func__); /* mark frame for encryption */ params.ibp_flags |= IEEE80211_BPF_CRYPTO; } } else m->m_pkthdr.len = m->m_len = 3 * sizeof(uint16_t); /* XXX not right for shared key */ if (status == IEEE80211_STATUS_SUCCESS) IEEE80211_NODE_STAT(ni, tx_auth); else IEEE80211_NODE_STAT(ni, tx_auth_fail); if (vap->iv_opmode == IEEE80211_M_STA) ieee80211_add_callback(m, ieee80211_tx_mgt_cb, (void *) vap->iv_state); break; case IEEE80211_FC0_SUBTYPE_DEAUTH: IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, "send station deauthenticate (reason: %d (%s))", arg, ieee80211_reason_to_string(arg)); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t)); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); *(uint16_t *)frm = htole16(arg); /* reason */ m->m_pkthdr.len = m->m_len = sizeof(uint16_t); IEEE80211_NODE_STAT(ni, tx_deauth); IEEE80211_NODE_STAT_SET(ni, tx_deauth_code, arg); ieee80211_node_unauthorize(ni); /* port closed */ break; case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: /* * asreq frame format * [2] capability information * [2] listen interval * [6*] current AP address (reassoc only) * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates * [4] power capability (optional) * [28] supported channels (optional) * [tlv] HT capabilities * [tlv] VHT capabilities * [tlv] WME (optional) * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Atheros capabilities (if negotiated) * [tlv] AppIE's (optional) */ m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) + sizeof(uint16_t) + IEEE80211_ADDR_LEN + 2 + IEEE80211_NWID_LEN + 2 + IEEE80211_RATE_SIZE + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + 4 + 2 + 26 + sizeof(struct ieee80211_wme_info) + sizeof(struct ieee80211_ie_htcap) + 2 + sizeof(struct ieee80211_vht_cap) + 4 + sizeof(struct ieee80211_ie_htcap) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) #endif + (vap->iv_appie_wpa != NULL ? vap->iv_appie_wpa->ie_len : 0) + (vap->iv_appie_assocreq != NULL ? vap->iv_appie_assocreq->ie_len : 0) ); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode %u", vap->iv_opmode)); capinfo = IEEE80211_CAPINFO_ESS; if (vap->iv_flags & IEEE80211_F_PRIVACY) capinfo |= IEEE80211_CAPINFO_PRIVACY; /* * NB: Some 11a AP's reject the request when * short preamble is set. */ if ((vap->iv_flags & IEEE80211_F_SHPREAMBLE) && IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) capinfo |= IEEE80211_CAPINFO_SHORT_PREAMBLE; if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && (ic->ic_caps & IEEE80211_C_SHSLOT)) capinfo |= IEEE80211_CAPINFO_SHORT_SLOTTIME; if ((ni->ni_capinfo & IEEE80211_CAPINFO_SPECTRUM_MGMT) && (vap->iv_flags & IEEE80211_F_DOTH)) capinfo |= IEEE80211_CAPINFO_SPECTRUM_MGMT; *(uint16_t *)frm = htole16(capinfo); frm += 2; KASSERT(bss->ni_intval != 0, ("beacon interval is zero!")); *(uint16_t *)frm = htole16(howmany(ic->ic_lintval, bss->ni_intval)); frm += 2; if (type == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) { IEEE80211_ADDR_COPY(frm, bss->ni_bssid); frm += IEEE80211_ADDR_LEN; } frm = ieee80211_add_ssid(frm, ni->ni_essid, ni->ni_esslen); frm = ieee80211_add_rates(frm, &ni->ni_rates); frm = ieee80211_add_rsn(frm, vap); frm = ieee80211_add_xrates(frm, &ni->ni_rates); if (capinfo & IEEE80211_CAPINFO_SPECTRUM_MGMT) { frm = ieee80211_add_powercapability(frm, ic->ic_curchan); frm = ieee80211_add_supportedchannels(frm, ic); } /* * Check the channel - we may be using an 11n NIC with an * 11n capable station, but we're configured to be an 11b * channel. */ if ((vap->iv_flags_ht & IEEE80211_FHT_HT) && IEEE80211_IS_CHAN_HT(ni->ni_chan) && ni->ni_ies.htcap_ie != NULL && ni->ni_ies.htcap_ie[0] == IEEE80211_ELEMID_HTCAP) { frm = ieee80211_add_htcap(frm, ni); } if ((vap->iv_vht_flags & IEEE80211_FVHT_VHT) && IEEE80211_IS_CHAN_VHT(ni->ni_chan) && ni->ni_ies.vhtcap_ie != NULL && ni->ni_ies.vhtcap_ie[0] == IEEE80211_ELEMID_VHT_CAP) { frm = ieee80211_add_vhtcap(frm, ni); } frm = ieee80211_add_wpa(frm, vap); if ((vap->iv_flags & IEEE80211_F_WME) && ni->ni_ies.wme_ie != NULL) frm = ieee80211_add_wme_info(frm, &ic->ic_wme, ni); /* * Same deal - only send HT info if we're on an 11n * capable channel. */ if ((vap->iv_flags_ht & IEEE80211_FHT_HT) && IEEE80211_IS_CHAN_HT(ni->ni_chan) && ni->ni_ies.htcap_ie != NULL && ni->ni_ies.htcap_ie[0] == IEEE80211_ELEMID_VENDOR) { frm = ieee80211_add_htcap_vendor(frm, ni); } #ifdef IEEE80211_SUPPORT_SUPERG if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_F_ATHEROS)) { frm = ieee80211_add_ath(frm, IEEE80211_ATH_CAP(vap, ni, IEEE80211_F_ATHEROS), ((vap->iv_flags & IEEE80211_F_WPA) == 0 && ni->ni_authmode != IEEE80211_AUTH_8021X) ? vap->iv_def_txkey : IEEE80211_KEYIX_NONE); } #endif /* IEEE80211_SUPPORT_SUPERG */ if (vap->iv_appie_assocreq != NULL) frm = add_appie(frm, vap->iv_appie_assocreq); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); ieee80211_add_callback(m, ieee80211_tx_mgt_cb, (void *) vap->iv_state); break; case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: /* * asresp frame format * [2] capability information * [2] status * [2] association ID * [tlv] supported rates * [tlv] extended supported rates * [tlv] HT capabilities (standard, if STA enabled) * [tlv] HT information (standard, if STA enabled) * [tlv] VHT capabilities (standard, if STA enabled) * [tlv] VHT information (standard, if STA enabled) * [tlv] WME (if configured and STA enabled) * [tlv] HT capabilities (vendor OUI, if STA enabled) * [tlv] HT information (vendor OUI, if STA enabled) * [tlv] Atheros capabilities (if STA enabled) * [tlv] AppIE's (optional) */ m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + 2 + IEEE80211_RATE_SIZE + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + sizeof(struct ieee80211_ie_htcap) + 4 + sizeof(struct ieee80211_ie_htinfo) + 4 + 2 + sizeof(struct ieee80211_vht_cap) + 2 + sizeof(struct ieee80211_vht_operation) + sizeof(struct ieee80211_wme_param) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) #endif + (vap->iv_appie_assocresp != NULL ? vap->iv_appie_assocresp->ie_len : 0) ); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); capinfo = ieee80211_getcapinfo(vap, bss->ni_chan); *(uint16_t *)frm = htole16(capinfo); frm += 2; *(uint16_t *)frm = htole16(arg); /* status */ frm += 2; if (arg == IEEE80211_STATUS_SUCCESS) { *(uint16_t *)frm = htole16(ni->ni_associd); IEEE80211_NODE_STAT(ni, tx_assoc); } else IEEE80211_NODE_STAT(ni, tx_assoc_fail); frm += 2; frm = ieee80211_add_rates(frm, &ni->ni_rates); frm = ieee80211_add_xrates(frm, &ni->ni_rates); /* NB: respond according to what we received */ if ((ni->ni_flags & HTFLAGS) == IEEE80211_NODE_HT) { frm = ieee80211_add_htcap(frm, ni); frm = ieee80211_add_htinfo(frm, ni); } if ((vap->iv_flags & IEEE80211_F_WME) && ni->ni_ies.wme_ie != NULL) frm = ieee80211_add_wme_param(frm, &ic->ic_wme, !! (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD)); if ((ni->ni_flags & HTFLAGS) == HTFLAGS) { frm = ieee80211_add_htcap_vendor(frm, ni); frm = ieee80211_add_htinfo_vendor(frm, ni); } if (ni->ni_flags & IEEE80211_NODE_VHT) { frm = ieee80211_add_vhtcap(frm, ni); frm = ieee80211_add_vhtinfo(frm, ni); } #ifdef IEEE80211_SUPPORT_SUPERG if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_F_ATHEROS)) frm = ieee80211_add_ath(frm, IEEE80211_ATH_CAP(vap, ni, IEEE80211_F_ATHEROS), ((vap->iv_flags & IEEE80211_F_WPA) == 0 && ni->ni_authmode != IEEE80211_AUTH_8021X) ? vap->iv_def_txkey : IEEE80211_KEYIX_NONE); #endif /* IEEE80211_SUPPORT_SUPERG */ if (vap->iv_appie_assocresp != NULL) frm = add_appie(frm, vap->iv_appie_assocresp); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); break; case IEEE80211_FC0_SUBTYPE_DISASSOC: IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, "send station disassociate (reason: %d (%s))", arg, ieee80211_reason_to_string(arg)); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t)); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); *(uint16_t *)frm = htole16(arg); /* reason */ m->m_pkthdr.len = m->m_len = sizeof(uint16_t); IEEE80211_NODE_STAT(ni, tx_disassoc); IEEE80211_NODE_STAT_SET(ni, tx_disassoc_code, arg); break; default: IEEE80211_NOTE(vap, IEEE80211_MSG_ANY, ni, "invalid mgmt frame type %u", type); senderr(EINVAL, is_tx_unknownmgt); /* NOTREACHED */ } /* NB: force non-ProbeResp frames to the highest queue */ params.ibp_pri = WME_AC_VO; params.ibp_rate0 = bss->ni_txparms->mgmtrate; /* NB: we know all frames are unicast */ params.ibp_try0 = bss->ni_txparms->maxretry; params.ibp_power = bss->ni_txpower; return ieee80211_mgmt_output(ni, m, type, ¶ms); bad: ieee80211_free_node(ni); return ret; #undef senderr #undef HTFLAGS } /* * Return an mbuf with a probe response frame in it. * Space is left to prepend and 802.11 header at the * front but it's left to the caller to fill in. */ struct mbuf * ieee80211_alloc_proberesp(struct ieee80211_node *bss, int legacy) { struct ieee80211vap *vap = bss->ni_vap; struct ieee80211com *ic = bss->ni_ic; const struct ieee80211_rateset *rs; struct mbuf *m; uint16_t capinfo; uint8_t *frm; /* * probe response frame format * [8] time stamp * [2] beacon interval * [2] cabability information * [tlv] ssid * [tlv] supported rates * [tlv] parameter set (FH/DS) * [tlv] parameter set (IBSS) * [tlv] country (optional) * [3] power control (optional) * [5] channel switch announcement (CSA) (optional) * [tlv] extended rate phy (ERP) * [tlv] extended supported rates * [tlv] RSN (optional) * [tlv] HT capabilities * [tlv] HT information * [tlv] VHT capabilities * [tlv] VHT information * [tlv] WPA (optional) * [tlv] WME (optional) * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Vendor OUI HT information (optional) * [tlv] Atheros capabilities * [tlv] AppIE's (optional) * [tlv] Mesh ID (MBSS) * [tlv] Mesh Conf (MBSS) */ m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), 8 + sizeof(uint16_t) + sizeof(uint16_t) + 2 + IEEE80211_NWID_LEN + 2 + IEEE80211_RATE_SIZE + 7 /* max(7,3) */ + IEEE80211_COUNTRY_MAX_SIZE + 3 + sizeof(struct ieee80211_csa_ie) + sizeof(struct ieee80211_quiet_ie) + 3 + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + sizeof(struct ieee80211_ie_wpa) + sizeof(struct ieee80211_ie_htcap) + sizeof(struct ieee80211_ie_htinfo) + sizeof(struct ieee80211_ie_wpa) + sizeof(struct ieee80211_wme_param) + 4 + sizeof(struct ieee80211_ie_htcap) + 4 + sizeof(struct ieee80211_ie_htinfo) + 2 + sizeof(struct ieee80211_vht_cap) + 2 + sizeof(struct ieee80211_vht_operation) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) #endif #ifdef IEEE80211_SUPPORT_MESH + 2 + IEEE80211_MESHID_LEN + sizeof(struct ieee80211_meshconf_ie) #endif + (vap->iv_appie_proberesp != NULL ? vap->iv_appie_proberesp->ie_len : 0) ); if (m == NULL) { vap->iv_stats.is_tx_nobuf++; return NULL; } memset(frm, 0, 8); /* timestamp should be filled later */ frm += 8; *(uint16_t *)frm = htole16(bss->ni_intval); frm += 2; capinfo = ieee80211_getcapinfo(vap, bss->ni_chan); *(uint16_t *)frm = htole16(capinfo); frm += 2; frm = ieee80211_add_ssid(frm, bss->ni_essid, bss->ni_esslen); rs = ieee80211_get_suprates(ic, bss->ni_chan); frm = ieee80211_add_rates(frm, rs); if (IEEE80211_IS_CHAN_FHSS(bss->ni_chan)) { *frm++ = IEEE80211_ELEMID_FHPARMS; *frm++ = 5; *frm++ = bss->ni_fhdwell & 0x00ff; *frm++ = (bss->ni_fhdwell >> 8) & 0x00ff; *frm++ = IEEE80211_FH_CHANSET( ieee80211_chan2ieee(ic, bss->ni_chan)); *frm++ = IEEE80211_FH_CHANPAT( ieee80211_chan2ieee(ic, bss->ni_chan)); *frm++ = bss->ni_fhindex; } else { *frm++ = IEEE80211_ELEMID_DSPARMS; *frm++ = 1; *frm++ = ieee80211_chan2ieee(ic, bss->ni_chan); } if (vap->iv_opmode == IEEE80211_M_IBSS) { *frm++ = IEEE80211_ELEMID_IBSSPARMS; *frm++ = 2; *frm++ = 0; *frm++ = 0; /* TODO: ATIM window */ } if ((vap->iv_flags & IEEE80211_F_DOTH) || (vap->iv_flags_ext & IEEE80211_FEXT_DOTD)) frm = ieee80211_add_countryie(frm, ic); if (vap->iv_flags & IEEE80211_F_DOTH) { if (IEEE80211_IS_CHAN_5GHZ(bss->ni_chan)) frm = ieee80211_add_powerconstraint(frm, vap); if (ic->ic_flags & IEEE80211_F_CSAPENDING) frm = ieee80211_add_csa(frm, vap); } if (vap->iv_flags & IEEE80211_F_DOTH) { if (IEEE80211_IS_CHAN_DFS(ic->ic_bsschan) && (vap->iv_flags_ext & IEEE80211_FEXT_DFS)) { if (vap->iv_quiet) frm = ieee80211_add_quiet(frm, vap, 0); } } if (IEEE80211_IS_CHAN_ANYG(bss->ni_chan)) frm = ieee80211_add_erp(frm, vap); frm = ieee80211_add_xrates(frm, rs); frm = ieee80211_add_rsn(frm, vap); /* * NB: legacy 11b clients do not get certain ie's. * The caller identifies such clients by passing * a token in legacy to us. Could expand this to be * any legacy client for stuff like HT ie's. */ if (IEEE80211_IS_CHAN_HT(bss->ni_chan) && legacy != IEEE80211_SEND_LEGACY_11B) { frm = ieee80211_add_htcap(frm, bss); frm = ieee80211_add_htinfo(frm, bss); } if (IEEE80211_IS_CHAN_VHT(bss->ni_chan) && legacy != IEEE80211_SEND_LEGACY_11B) { frm = ieee80211_add_vhtcap(frm, bss); frm = ieee80211_add_vhtinfo(frm, bss); } frm = ieee80211_add_wpa(frm, vap); if (vap->iv_flags & IEEE80211_F_WME) frm = ieee80211_add_wme_param(frm, &ic->ic_wme, !! (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD)); if (IEEE80211_IS_CHAN_HT(bss->ni_chan) && (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) && legacy != IEEE80211_SEND_LEGACY_11B) { frm = ieee80211_add_htcap_vendor(frm, bss); frm = ieee80211_add_htinfo_vendor(frm, bss); } #ifdef IEEE80211_SUPPORT_SUPERG if ((vap->iv_flags & IEEE80211_F_ATHEROS) && legacy != IEEE80211_SEND_LEGACY_11B) frm = ieee80211_add_athcaps(frm, bss); #endif if (vap->iv_appie_proberesp != NULL) frm = add_appie(frm, vap->iv_appie_proberesp); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) { frm = ieee80211_add_meshid(frm, vap); frm = ieee80211_add_meshconf(frm, vap); } #endif m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return m; } /* * Send a probe response frame to the specified mac address. * This does not go through the normal mgt frame api so we * can specify the destination address and re-use the bss node * for the sta reference. */ int ieee80211_send_proberesp(struct ieee80211vap *vap, const uint8_t da[IEEE80211_ADDR_LEN], int legacy) { struct ieee80211_node *bss = vap->iv_bss; struct ieee80211com *ic = vap->iv_ic; struct mbuf *m; int ret; if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT, bss, "block %s frame in CAC state", "probe response"); vap->iv_stats.is_tx_badstate++; return EIO; /* XXX */ } /* * Hold a reference on the node so it doesn't go away until after * the xmit is complete all the way in the driver. On error we * will remove our reference. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, bss, ether_sprintf(bss->ni_macaddr), ieee80211_node_refcnt(bss)+1); ieee80211_ref_node(bss); m = ieee80211_alloc_proberesp(bss, legacy); if (m == NULL) { ieee80211_free_node(bss); return ENOMEM; } M_PREPEND(m, sizeof(struct ieee80211_frame), IEEE80211_M_NOWAIT); KASSERT(m != NULL, ("no room for header")); IEEE80211_TX_LOCK(ic); ieee80211_send_setup(bss, m, IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_RESP, IEEE80211_NONQOS_TID, vap->iv_myaddr, da, bss->ni_bssid); /* XXX power management? */ m->m_flags |= M_ENCAP; /* mark encapsulated */ M_WME_SETAC(m, WME_AC_BE); IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DUMPPKTS, "send probe resp on channel %u to %s%s\n", ieee80211_chan2ieee(ic, ic->ic_curchan), ether_sprintf(da), legacy ? " " : ""); IEEE80211_NODE_STAT(bss, tx_mgmt); ret = ieee80211_raw_output(vap, bss, m, NULL); IEEE80211_TX_UNLOCK(ic); return (ret); } /* * Allocate and build a RTS (Request To Send) control frame. */ struct mbuf * ieee80211_alloc_rts(struct ieee80211com *ic, const uint8_t ra[IEEE80211_ADDR_LEN], const uint8_t ta[IEEE80211_ADDR_LEN], uint16_t dur) { struct ieee80211_frame_rts *rts; struct mbuf *m; /* XXX honor ic_headroom */ m = m_gethdr(IEEE80211_M_NOWAIT, MT_DATA); if (m != NULL) { rts = mtod(m, struct ieee80211_frame_rts *); rts->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_RTS; rts->i_fc[1] = IEEE80211_FC1_DIR_NODS; *(u_int16_t *)rts->i_dur = htole16(dur); IEEE80211_ADDR_COPY(rts->i_ra, ra); IEEE80211_ADDR_COPY(rts->i_ta, ta); m->m_pkthdr.len = m->m_len = sizeof(struct ieee80211_frame_rts); } return m; } /* * Allocate and build a CTS (Clear To Send) control frame. */ struct mbuf * ieee80211_alloc_cts(struct ieee80211com *ic, const uint8_t ra[IEEE80211_ADDR_LEN], uint16_t dur) { struct ieee80211_frame_cts *cts; struct mbuf *m; /* XXX honor ic_headroom */ m = m_gethdr(IEEE80211_M_NOWAIT, MT_DATA); if (m != NULL) { cts = mtod(m, struct ieee80211_frame_cts *); cts->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_CTS; cts->i_fc[1] = IEEE80211_FC1_DIR_NODS; *(u_int16_t *)cts->i_dur = htole16(dur); IEEE80211_ADDR_COPY(cts->i_ra, ra); m->m_pkthdr.len = m->m_len = sizeof(struct ieee80211_frame_cts); } return m; } /* * Wrapper for CTS/RTS frame allocation. */ struct mbuf * ieee80211_alloc_prot(struct ieee80211_node *ni, const struct mbuf *m, uint8_t rate, int prot) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_frame *wh; struct mbuf *mprot; uint16_t dur; int pktlen, isshort; KASSERT(prot == IEEE80211_PROT_RTSCTS || prot == IEEE80211_PROT_CTSONLY, ("wrong protection type %d", prot)); wh = mtod(m, const struct ieee80211_frame *); pktlen = m->m_pkthdr.len + IEEE80211_CRC_LEN; isshort = (vap->iv_flags & IEEE80211_F_SHPREAMBLE) != 0; dur = ieee80211_compute_duration(ic->ic_rt, pktlen, rate, isshort) + ieee80211_ack_duration(ic->ic_rt, rate, isshort); if (prot == IEEE80211_PROT_RTSCTS) { /* NB: CTS is the same size as an ACK */ dur += ieee80211_ack_duration(ic->ic_rt, rate, isshort); mprot = ieee80211_alloc_rts(ic, wh->i_addr1, wh->i_addr2, dur); } else mprot = ieee80211_alloc_cts(ic, vap->iv_myaddr, dur); return (mprot); } static void ieee80211_tx_mgt_timeout(void *arg) { struct ieee80211vap *vap = arg; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "vap %p mode %s state %s flags %#x & %#x\n", vap, ieee80211_opmode_name[vap->iv_opmode], ieee80211_state_name[vap->iv_state], vap->iv_ic->ic_flags, IEEE80211_F_SCAN); IEEE80211_LOCK(vap->iv_ic); if (vap->iv_state != IEEE80211_S_INIT && (vap->iv_ic->ic_flags & IEEE80211_F_SCAN) == 0) { /* * NB: it's safe to specify a timeout as the reason here; * it'll only be used in the right state. */ ieee80211_new_state_locked(vap, IEEE80211_S_SCAN, IEEE80211_SCAN_FAIL_TIMEOUT); } IEEE80211_UNLOCK(vap->iv_ic); } /* * This is the callback set on net80211-sourced transmitted * authentication request frames. * * This does a couple of things: * * + If the frame transmitted was a success, it schedules a future * event which will transition the interface to scan. * If a state transition _then_ occurs before that event occurs, * said state transition will cancel this callout. * * + If the frame transmit was a failure, it immediately schedules * the transition back to scan. */ static void ieee80211_tx_mgt_cb(struct ieee80211_node *ni, void *arg, int status) { struct ieee80211vap *vap = ni->ni_vap; enum ieee80211_state ostate = (enum ieee80211_state)(uintptr_t)arg; /* * Frame transmit completed; arrange timer callback. If * transmit was successfully we wait for response. Otherwise * we arrange an immediate callback instead of doing the * callback directly since we don't know what state the driver * is in (e.g. what locks it is holding). This work should * not be too time-critical and not happen too often so the * added overhead is acceptable. * * XXX what happens if !acked but response shows up before callback? */ if (vap->iv_state == ostate) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "ni %p mode %s state %s arg %p status %d\n", ni, ieee80211_opmode_name[vap->iv_opmode], ieee80211_state_name[vap->iv_state], arg, status); callout_reset(&vap->iv_mgtsend, status == 0 ? IEEE80211_TRANS_WAIT*hz : 0, ieee80211_tx_mgt_timeout, vap); } } static void ieee80211_beacon_construct(struct mbuf *m, uint8_t *frm, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_rateset *rs = &ni->ni_rates; uint16_t capinfo; /* * beacon frame format * * TODO: update to 802.11-2012; a lot of stuff has changed; * vendor extensions should be at the end, etc. * * [8] time stamp * [2] beacon interval * [2] cabability information * [tlv] ssid * [tlv] supported rates * [3] parameter set (DS) * [8] CF parameter set (optional) * [tlv] parameter set (IBSS/TIM) * [tlv] country (optional) * [3] power control (optional) * [5] channel switch announcement (CSA) (optional) * XXX TODO: Quiet * XXX TODO: IBSS DFS * XXX TODO: TPC report * [tlv] extended rate phy (ERP) * [tlv] extended supported rates * [tlv] RSN parameters * XXX TODO: BSSLOAD * (XXX EDCA parameter set, QoS capability?) * XXX TODO: AP channel report * * [tlv] HT capabilities * [tlv] HT information * XXX TODO: 20/40 BSS coexistence * Mesh: * XXX TODO: Meshid * XXX TODO: mesh config * XXX TODO: mesh awake window * XXX TODO: beacon timing (mesh, etc) * XXX TODO: MCCAOP Advertisement Overview * XXX TODO: MCCAOP Advertisement * XXX TODO: Mesh channel switch parameters * VHT: * XXX TODO: VHT capabilities * XXX TODO: VHT operation * XXX TODO: VHT transmit power envelope * XXX TODO: channel switch wrapper element * XXX TODO: extended BSS load element * * XXX Vendor-specific OIDs (e.g. Atheros) * [tlv] WPA parameters * [tlv] WME parameters * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Vendor OUI HT information (optional) * [tlv] Atheros capabilities (optional) * [tlv] TDMA parameters (optional) * [tlv] Mesh ID (MBSS) * [tlv] Mesh Conf (MBSS) * [tlv] application data (optional) */ memset(bo, 0, sizeof(*bo)); memset(frm, 0, 8); /* XXX timestamp is set by hardware/driver */ frm += 8; *(uint16_t *)frm = htole16(ni->ni_intval); frm += 2; capinfo = ieee80211_getcapinfo(vap, ni->ni_chan); bo->bo_caps = (uint16_t *)frm; *(uint16_t *)frm = htole16(capinfo); frm += 2; *frm++ = IEEE80211_ELEMID_SSID; if ((vap->iv_flags & IEEE80211_F_HIDESSID) == 0) { *frm++ = ni->ni_esslen; memcpy(frm, ni->ni_essid, ni->ni_esslen); frm += ni->ni_esslen; } else *frm++ = 0; frm = ieee80211_add_rates(frm, rs); if (!IEEE80211_IS_CHAN_FHSS(ni->ni_chan)) { *frm++ = IEEE80211_ELEMID_DSPARMS; *frm++ = 1; *frm++ = ieee80211_chan2ieee(ic, ni->ni_chan); } if (ic->ic_flags & IEEE80211_F_PCF) { bo->bo_cfp = frm; frm = ieee80211_add_cfparms(frm, ic); } bo->bo_tim = frm; if (vap->iv_opmode == IEEE80211_M_IBSS) { *frm++ = IEEE80211_ELEMID_IBSSPARMS; *frm++ = 2; *frm++ = 0; *frm++ = 0; /* TODO: ATIM window */ bo->bo_tim_len = 0; } else if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_MBSS) { /* TIM IE is the same for Mesh and Hostap */ struct ieee80211_tim_ie *tie = (struct ieee80211_tim_ie *) frm; tie->tim_ie = IEEE80211_ELEMID_TIM; tie->tim_len = 4; /* length */ tie->tim_count = 0; /* DTIM count */ tie->tim_period = vap->iv_dtim_period; /* DTIM period */ tie->tim_bitctl = 0; /* bitmap control */ tie->tim_bitmap[0] = 0; /* Partial Virtual Bitmap */ frm += sizeof(struct ieee80211_tim_ie); bo->bo_tim_len = 1; } bo->bo_tim_trailer = frm; if ((vap->iv_flags & IEEE80211_F_DOTH) || (vap->iv_flags_ext & IEEE80211_FEXT_DOTD)) frm = ieee80211_add_countryie(frm, ic); if (vap->iv_flags & IEEE80211_F_DOTH) { if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) frm = ieee80211_add_powerconstraint(frm, vap); bo->bo_csa = frm; if (ic->ic_flags & IEEE80211_F_CSAPENDING) frm = ieee80211_add_csa(frm, vap); } else bo->bo_csa = frm; bo->bo_quiet = NULL; if (vap->iv_flags & IEEE80211_F_DOTH) { if (IEEE80211_IS_CHAN_DFS(ic->ic_bsschan) && (vap->iv_flags_ext & IEEE80211_FEXT_DFS) && (vap->iv_quiet == 1)) { /* * We only insert the quiet IE offset if * the quiet IE is enabled. Otherwise don't * put it here or we'll just overwrite * some other beacon contents. */ if (vap->iv_quiet) { bo->bo_quiet = frm; frm = ieee80211_add_quiet(frm,vap, 0); } } } if (IEEE80211_IS_CHAN_ANYG(ni->ni_chan)) { bo->bo_erp = frm; frm = ieee80211_add_erp(frm, vap); } frm = ieee80211_add_xrates(frm, rs); frm = ieee80211_add_rsn(frm, vap); if (IEEE80211_IS_CHAN_HT(ni->ni_chan)) { frm = ieee80211_add_htcap(frm, ni); bo->bo_htinfo = frm; frm = ieee80211_add_htinfo(frm, ni); } if (IEEE80211_IS_CHAN_VHT(ni->ni_chan)) { frm = ieee80211_add_vhtcap(frm, ni); bo->bo_vhtinfo = frm; frm = ieee80211_add_vhtinfo(frm, ni); /* Transmit power envelope */ /* Channel switch wrapper element */ /* Extended bss load element */ } frm = ieee80211_add_wpa(frm, vap); if (vap->iv_flags & IEEE80211_F_WME) { bo->bo_wme = frm; frm = ieee80211_add_wme_param(frm, &ic->ic_wme, !! (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD)); } if (IEEE80211_IS_CHAN_HT(ni->ni_chan) && (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT)) { frm = ieee80211_add_htcap_vendor(frm, ni); frm = ieee80211_add_htinfo_vendor(frm, ni); } #ifdef IEEE80211_SUPPORT_SUPERG if (vap->iv_flags & IEEE80211_F_ATHEROS) { bo->bo_ath = frm; frm = ieee80211_add_athcaps(frm, ni); } #endif #ifdef IEEE80211_SUPPORT_TDMA if (vap->iv_caps & IEEE80211_C_TDMA) { bo->bo_tdma = frm; frm = ieee80211_add_tdma(frm, vap); } #endif if (vap->iv_appie_beacon != NULL) { bo->bo_appie = frm; bo->bo_appie_len = vap->iv_appie_beacon->ie_len; frm = add_appie(frm, vap->iv_appie_beacon); } /* XXX TODO: move meshid/meshconf up to before vendor extensions? */ #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) { frm = ieee80211_add_meshid(frm, vap); bo->bo_meshconf = frm; frm = ieee80211_add_meshconf(frm, vap); } #endif bo->bo_tim_trailer_len = frm - bo->bo_tim_trailer; bo->bo_csa_trailer_len = frm - bo->bo_csa; m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); } /* * Allocate a beacon frame and fillin the appropriate bits. */ struct mbuf * ieee80211_beacon_alloc(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_frame *wh; struct mbuf *m; int pktlen; uint8_t *frm; /* * Update the "We're putting the quiet IE in the beacon" state. */ if (vap->iv_quiet == 1) vap->iv_flags_ext |= IEEE80211_FEXT_QUIET_IE; else if (vap->iv_quiet == 0) vap->iv_flags_ext &= ~IEEE80211_FEXT_QUIET_IE; /* * beacon frame format * * Note: This needs updating for 802.11-2012. * * [8] time stamp * [2] beacon interval * [2] cabability information * [tlv] ssid * [tlv] supported rates * [3] parameter set (DS) * [8] CF parameter set (optional) * [tlv] parameter set (IBSS/TIM) * [tlv] country (optional) * [3] power control (optional) * [5] channel switch announcement (CSA) (optional) * [tlv] extended rate phy (ERP) * [tlv] extended supported rates * [tlv] RSN parameters * [tlv] HT capabilities * [tlv] HT information * [tlv] VHT capabilities * [tlv] VHT operation * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Vendor OUI HT information (optional) * XXX Vendor-specific OIDs (e.g. Atheros) * [tlv] WPA parameters * [tlv] WME parameters * [tlv] TDMA parameters (optional) * [tlv] Mesh ID (MBSS) * [tlv] Mesh Conf (MBSS) * [tlv] application data (optional) * NB: we allocate the max space required for the TIM bitmap. * XXX how big is this? */ pktlen = 8 /* time stamp */ + sizeof(uint16_t) /* beacon interval */ + sizeof(uint16_t) /* capabilities */ + 2 + ni->ni_esslen /* ssid */ + 2 + IEEE80211_RATE_SIZE /* supported rates */ + 2 + 1 /* DS parameters */ + 2 + 6 /* CF parameters */ + 2 + 4 + vap->iv_tim_len /* DTIM/IBSSPARMS */ + IEEE80211_COUNTRY_MAX_SIZE /* country */ + 2 + 1 /* power control */ + sizeof(struct ieee80211_csa_ie) /* CSA */ + sizeof(struct ieee80211_quiet_ie) /* Quiet */ + 2 + 1 /* ERP */ + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + (vap->iv_caps & IEEE80211_C_WPA ? /* WPA 1+2 */ 2*sizeof(struct ieee80211_ie_wpa) : 0) /* XXX conditional? */ + 4+2*sizeof(struct ieee80211_ie_htcap)/* HT caps */ + 4+2*sizeof(struct ieee80211_ie_htinfo)/* HT info */ + 2 + sizeof(struct ieee80211_vht_cap)/* VHT caps */ + 2 + sizeof(struct ieee80211_vht_operation)/* VHT info */ + (vap->iv_caps & IEEE80211_C_WME ? /* WME */ sizeof(struct ieee80211_wme_param) : 0) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) /* ATH */ #endif #ifdef IEEE80211_SUPPORT_TDMA + (vap->iv_caps & IEEE80211_C_TDMA ? /* TDMA */ sizeof(struct ieee80211_tdma_param) : 0) #endif #ifdef IEEE80211_SUPPORT_MESH + 2 + ni->ni_meshidlen + sizeof(struct ieee80211_meshconf_ie) #endif + IEEE80211_MAX_APPIE ; m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), pktlen); if (m == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_ANY, "%s: cannot get buf; size %u\n", __func__, pktlen); vap->iv_stats.is_tx_nobuf++; return NULL; } ieee80211_beacon_construct(m, frm, ni); M_PREPEND(m, sizeof(struct ieee80211_frame), IEEE80211_M_NOWAIT); KASSERT(m != NULL, ("no space for 802.11 header?")); wh = mtod(m, struct ieee80211_frame *); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_BEACON; wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; *(uint16_t *)wh->i_dur = 0; IEEE80211_ADDR_COPY(wh->i_addr1, ieee80211_vap_get_broadcast_address(vap)); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, ni->ni_bssid); *(uint16_t *)wh->i_seq = 0; return m; } /* * Update the dynamic parts of a beacon frame based on the current state. */ int ieee80211_beacon_update(struct ieee80211_node *ni, struct mbuf *m, int mcast) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off; struct ieee80211com *ic = ni->ni_ic; int len_changed = 0; uint16_t capinfo; IEEE80211_LOCK(ic); /* * Handle 11h channel change when we've reached the count. * We must recalculate the beacon frame contents to account * for the new channel. Note we do this only for the first * vap that reaches this point; subsequent vaps just update * their beacon state to reflect the recalculated channel. */ if (isset(bo->bo_flags, IEEE80211_BEACON_CSA) && vap->iv_csa_count == ic->ic_csa_count) { vap->iv_csa_count = 0; /* * Effect channel change before reconstructing the beacon * frame contents as many places reference ni_chan. */ if (ic->ic_csa_newchan != NULL) ieee80211_csa_completeswitch(ic); /* * NB: ieee80211_beacon_construct clears all pending * updates in bo_flags so we don't need to explicitly * clear IEEE80211_BEACON_CSA. */ ieee80211_beacon_construct(m, mtod(m, uint8_t*) + sizeof(struct ieee80211_frame), ni); /* XXX do WME aggressive mode processing? */ IEEE80211_UNLOCK(ic); return 1; /* just assume length changed */ } /* * Handle the quiet time element being added and removed. * Again, for now we just cheat and reconstruct the whole * beacon - that way the gap is provided as appropriate. * * So, track whether we have already added the IE versus * whether we want to be adding the IE. */ if ((vap->iv_flags_ext & IEEE80211_FEXT_QUIET_IE) && (vap->iv_quiet == 0)) { /* * Quiet time beacon IE enabled, but it's disabled; * recalc */ vap->iv_flags_ext &= ~IEEE80211_FEXT_QUIET_IE; ieee80211_beacon_construct(m, mtod(m, uint8_t*) + sizeof(struct ieee80211_frame), ni); /* XXX do WME aggressive mode processing? */ IEEE80211_UNLOCK(ic); return 1; /* just assume length changed */ } if (((vap->iv_flags_ext & IEEE80211_FEXT_QUIET_IE) == 0) && (vap->iv_quiet == 1)) { /* * Quiet time beacon IE disabled, but it's now enabled; * recalc */ vap->iv_flags_ext |= IEEE80211_FEXT_QUIET_IE; ieee80211_beacon_construct(m, mtod(m, uint8_t*) + sizeof(struct ieee80211_frame), ni); /* XXX do WME aggressive mode processing? */ IEEE80211_UNLOCK(ic); return 1; /* just assume length changed */ } /* * XXX TODO Strictly speaking this should be incremented with the TX * lock held so as to serialise access to the non-qos TID sequence * number space. * * If the driver identifies it does its own TX seqno management then * we can skip this (and still not do the TX seqno.) */ ieee80211_output_beacon_seqno_assign(ni, m); /* XXX faster to recalculate entirely or just changes? */ capinfo = ieee80211_getcapinfo(vap, ni->ni_chan); *bo->bo_caps = htole16(capinfo); if (vap->iv_flags & IEEE80211_F_WME) { struct ieee80211_wme_state *wme = &ic->ic_wme; /* * Check for aggressive mode change. When there is * significant high priority traffic in the BSS * throttle back BE traffic by using conservative * parameters. Otherwise BE uses aggressive params * to optimize performance of legacy/non-QoS traffic. */ if (wme->wme_flags & WME_F_AGGRMODE) { if (wme->wme_hipri_traffic > wme->wme_hipri_switch_thresh) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_WME, "%s: traffic %u, disable aggressive mode\n", __func__, wme->wme_hipri_traffic); wme->wme_flags &= ~WME_F_AGGRMODE; ieee80211_wme_updateparams_locked(vap); wme->wme_hipri_traffic = wme->wme_hipri_switch_hysteresis; } else wme->wme_hipri_traffic = 0; } else { if (wme->wme_hipri_traffic <= wme->wme_hipri_switch_thresh) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_WME, "%s: traffic %u, enable aggressive mode\n", __func__, wme->wme_hipri_traffic); wme->wme_flags |= WME_F_AGGRMODE; ieee80211_wme_updateparams_locked(vap); wme->wme_hipri_traffic = 0; } else wme->wme_hipri_traffic = wme->wme_hipri_switch_hysteresis; } if (isset(bo->bo_flags, IEEE80211_BEACON_WME)) { (void) ieee80211_add_wme_param(bo->bo_wme, wme, vap->iv_flags_ext & IEEE80211_FEXT_UAPSD); clrbit(bo->bo_flags, IEEE80211_BEACON_WME); } } if (isset(bo->bo_flags, IEEE80211_BEACON_HTINFO)) { ieee80211_ht_update_beacon(vap, bo); clrbit(bo->bo_flags, IEEE80211_BEACON_HTINFO); } #ifdef IEEE80211_SUPPORT_TDMA if (vap->iv_caps & IEEE80211_C_TDMA) { /* * NB: the beacon is potentially updated every TBTT. */ ieee80211_tdma_update_beacon(vap, bo); } #endif #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_update_beacon(vap, bo); #endif if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_MBSS) { /* NB: no IBSS support*/ struct ieee80211_tim_ie *tie = (struct ieee80211_tim_ie *) bo->bo_tim; if (isset(bo->bo_flags, IEEE80211_BEACON_TIM)) { u_int timlen, timoff, i; /* * ATIM/DTIM needs updating. If it fits in the * current space allocated then just copy in the * new bits. Otherwise we need to move any trailing * data to make room. Note that we know there is * contiguous space because ieee80211_beacon_allocate * insures there is space in the mbuf to write a * maximal-size virtual bitmap (based on iv_max_aid). */ /* * Calculate the bitmap size and offset, copy any * trailer out of the way, and then copy in the * new bitmap and update the information element. * Note that the tim bitmap must contain at least * one byte and any offset must be even. */ if (vap->iv_ps_pending != 0) { timoff = 128; /* impossibly large */ for (i = 0; i < vap->iv_tim_len; i++) if (vap->iv_tim_bitmap[i]) { timoff = i &~ 1; break; } KASSERT(timoff != 128, ("tim bitmap empty!")); for (i = vap->iv_tim_len-1; i >= timoff; i--) if (vap->iv_tim_bitmap[i]) break; timlen = 1 + (i - timoff); } else { timoff = 0; timlen = 1; } /* * TODO: validate this! */ if (timlen != bo->bo_tim_len) { /* copy up/down trailer */ int adjust = tie->tim_bitmap+timlen - bo->bo_tim_trailer; ovbcopy(bo->bo_tim_trailer, bo->bo_tim_trailer+adjust, bo->bo_tim_trailer_len); bo->bo_tim_trailer += adjust; bo->bo_erp += adjust; bo->bo_htinfo += adjust; bo->bo_vhtinfo += adjust; #ifdef IEEE80211_SUPPORT_SUPERG bo->bo_ath += adjust; #endif #ifdef IEEE80211_SUPPORT_TDMA bo->bo_tdma += adjust; #endif #ifdef IEEE80211_SUPPORT_MESH bo->bo_meshconf += adjust; #endif bo->bo_appie += adjust; bo->bo_wme += adjust; bo->bo_csa += adjust; bo->bo_quiet += adjust; bo->bo_tim_len = timlen; /* update information element */ tie->tim_len = 3 + timlen; tie->tim_bitctl = timoff; len_changed = 1; } memcpy(tie->tim_bitmap, vap->iv_tim_bitmap + timoff, bo->bo_tim_len); clrbit(bo->bo_flags, IEEE80211_BEACON_TIM); IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, "%s: TIM updated, pending %u, off %u, len %u\n", __func__, vap->iv_ps_pending, timoff, timlen); } /* count down DTIM period */ if (tie->tim_count == 0) tie->tim_count = tie->tim_period - 1; else tie->tim_count--; /* update state for buffered multicast frames on DTIM */ if (mcast && tie->tim_count == 0) tie->tim_bitctl |= 1; else tie->tim_bitctl &= ~1; if (isset(bo->bo_flags, IEEE80211_BEACON_CSA)) { struct ieee80211_csa_ie *csa = (struct ieee80211_csa_ie *) bo->bo_csa; /* * Insert or update CSA ie. If we're just starting * to count down to the channel switch then we need * to insert the CSA ie. Otherwise we just need to * drop the count. The actual change happens above * when the vap's count reaches the target count. */ if (vap->iv_csa_count == 0) { memmove(&csa[1], csa, bo->bo_csa_trailer_len); bo->bo_erp += sizeof(*csa); bo->bo_htinfo += sizeof(*csa); bo->bo_vhtinfo += sizeof(*csa); bo->bo_wme += sizeof(*csa); #ifdef IEEE80211_SUPPORT_SUPERG bo->bo_ath += sizeof(*csa); #endif #ifdef IEEE80211_SUPPORT_TDMA bo->bo_tdma += sizeof(*csa); #endif #ifdef IEEE80211_SUPPORT_MESH bo->bo_meshconf += sizeof(*csa); #endif bo->bo_appie += sizeof(*csa); bo->bo_csa_trailer_len += sizeof(*csa); bo->bo_quiet += sizeof(*csa); bo->bo_tim_trailer_len += sizeof(*csa); m->m_len += sizeof(*csa); m->m_pkthdr.len += sizeof(*csa); ieee80211_add_csa(bo->bo_csa, vap); } else csa->csa_count--; vap->iv_csa_count++; /* NB: don't clear IEEE80211_BEACON_CSA */ } /* * Only add the quiet time IE if we've enabled it * as appropriate. */ if (IEEE80211_IS_CHAN_DFS(ic->ic_bsschan) && (vap->iv_flags_ext & IEEE80211_FEXT_DFS)) { if (vap->iv_quiet && (vap->iv_flags_ext & IEEE80211_FEXT_QUIET_IE)) { ieee80211_add_quiet(bo->bo_quiet, vap, 1); } } if (isset(bo->bo_flags, IEEE80211_BEACON_ERP)) { /* * ERP element needs updating. */ (void) ieee80211_add_erp(bo->bo_erp, vap); clrbit(bo->bo_flags, IEEE80211_BEACON_ERP); } #ifdef IEEE80211_SUPPORT_SUPERG if (isset(bo->bo_flags, IEEE80211_BEACON_ATH)) { ieee80211_add_athcaps(bo->bo_ath, ni); clrbit(bo->bo_flags, IEEE80211_BEACON_ATH); } #endif } if (isset(bo->bo_flags, IEEE80211_BEACON_APPIE)) { const struct ieee80211_appie *aie = vap->iv_appie_beacon; int aielen; uint8_t *frm; aielen = 0; if (aie != NULL) aielen += aie->ie_len; if (aielen != bo->bo_appie_len) { /* copy up/down trailer */ int adjust = aielen - bo->bo_appie_len; ovbcopy(bo->bo_tim_trailer, bo->bo_tim_trailer+adjust, bo->bo_tim_trailer_len); bo->bo_tim_trailer += adjust; bo->bo_appie += adjust; bo->bo_appie_len = aielen; len_changed = 1; } frm = bo->bo_appie; if (aie != NULL) frm = add_appie(frm, aie); clrbit(bo->bo_flags, IEEE80211_BEACON_APPIE); } IEEE80211_UNLOCK(ic); return len_changed; } /* * Do Ethernet-LLC encapsulation for each payload in a fast frame * tunnel encapsulation. The frame is assumed to have an Ethernet * header at the front that must be stripped before prepending the * LLC followed by the Ethernet header passed in (with an Ethernet * type that specifies the payload size). */ struct mbuf * ieee80211_ff_encap1(struct ieee80211vap *vap, struct mbuf *m, const struct ether_header *eh) { struct llc *llc; uint16_t payload; /* XXX optimize by combining m_adj+M_PREPEND */ m_adj(m, sizeof(struct ether_header) - sizeof(struct llc)); llc = mtod(m, struct llc *); llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP; llc->llc_control = LLC_UI; llc->llc_snap.org_code[0] = 0; llc->llc_snap.org_code[1] = 0; llc->llc_snap.org_code[2] = 0; llc->llc_snap.ether_type = eh->ether_type; payload = m->m_pkthdr.len; /* NB: w/o Ethernet header */ M_PREPEND(m, sizeof(struct ether_header), IEEE80211_M_NOWAIT); if (m == NULL) { /* XXX cannot happen */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: no space for ether_header\n", __func__); vap->iv_stats.is_tx_nobuf++; return NULL; } ETHER_HEADER_COPY(mtod(m, void *), eh); mtod(m, struct ether_header *)->ether_type = htons(payload); return m; } /* * Complete an mbuf transmission. * * For now, this simply processes a completed frame after the * driver has completed it's transmission and/or retransmission. * It assumes the frame is an 802.11 encapsulated frame. * * Later on it will grow to become the exit path for a given frame * from the driver and, depending upon how it's been encapsulated * and already transmitted, it may end up doing A-MPDU retransmission, * power save requeuing, etc. * * In order for the above to work, the driver entry point to this * must not hold any driver locks. Thus, the driver needs to delay * any actual mbuf completion until it can release said locks. * * This frees the mbuf and if the mbuf has a node reference, * the node reference will be freed. */ void ieee80211_tx_complete(struct ieee80211_node *ni, struct mbuf *m, int status) { if (ni != NULL) { struct ifnet *ifp = ni->ni_vap->iv_ifp; if (status == 0) { if_inc_counter(ifp, IFCOUNTER_OBYTES, m->m_pkthdr.len); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); if (m->m_flags & M_MCAST) if_inc_counter(ifp, IFCOUNTER_OMCASTS, 1); } else if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (m->m_flags & M_TXCB) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "ni %p vap %p mode %s state %s m %p status %d\n", ni, ni->ni_vap, ieee80211_opmode_name[ni->ni_vap->iv_opmode], ieee80211_state_name[ni->ni_vap->iv_state], m, status); ieee80211_process_callback(ni, m, status); } ieee80211_free_node(ni); } m_freem(m); } /** * @brief Assign a sequence number to the given frame. * * Check the frame type and TID and assign a suitable sequence number * from the correct sequence number space. * * It assumes the mbuf has been encapsulated, and has the TID assigned * if it is a QoS frame. * * Note this also clears any existing fragment ID in the header, so it * must be called first before assigning fragment IDs. * * For now this implements parts of 802.11-2012; it doesn't do all of * the needed checks for full compliance (notably QoS-Data NULL frames). * * TODO: update to 802.11-2020 10.3.2.14.2 (Transmitter Requirements) * * @param ni ieee80211_node this frame will be transmitted to * @param arg_tid A temporary check, existing callers may set * this to a TID variable they were using, and this routine * will verify it against what's in the frame and complain if * they don't match. For new callers, use -1. * @param m mbuf to populate the sequence number into */ void ieee80211_output_seqno_assign(struct ieee80211_node *ni, int arg_tid, struct mbuf *m) { struct ieee80211_frame *wh; ieee80211_seq seqno; uint8_t tid, type, subtype; wh = mtod(m, struct ieee80211_frame *); tid = ieee80211_gettid(wh); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; /* * Find places where the passed in TID doesn't match gettid() * and log. I'll have to then go and chase those down. * * If the caller knows its already setup the TID in the frame * correctly then it can pass in -1 and this check will be * skipped. */ if (arg_tid != -1 && tid != arg_tid) ic_printf(ni->ni_vap->iv_ic, "%s: called; TID mismatch; tid=%u, arg_tid=%d\n", __func__, tid, arg_tid); if (IEEE80211_HAS_SEQ(type, subtype)) { /* * 802.11-2012 9.3.2.10 - QoS multicast frames * come out of a different seqno space. */ if (IEEE80211_IS_MULTICAST(wh->i_addr1)) seqno = ni->ni_txseqs[IEEE80211_NONQOS_TID]++; else seqno = ni->ni_txseqs[tid]++; } else seqno = 0; /* * Assign the sequence number, clearing out any existing * sequence and fragment numbers. */ *(uint16_t *)&wh->i_seq[0] = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT); M_SEQNO_SET(m, seqno); } /** * @brief Assign a sequence number to the given beacon frame. * * TODO: update to 802.11-2020 10.3.2.14.2 (Transmitter Requirements) * * @param ni ieee80211_node this frame will be transmitted to * @param m mbuf to populate the sequence number into */ void ieee80211_output_beacon_seqno_assign(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211_frame *wh; ieee80211_seq seqno; wh = mtod(m, struct ieee80211_frame *); seqno = ni->ni_txseqs[IEEE80211_NONQOS_TID]++; *(uint16_t *)&wh->i_seq[0] = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT); M_SEQNO_SET(m, seqno); } diff --git a/sys/net80211/ieee80211_power.c b/sys/net80211/ieee80211_power.c index 13a49940f6f0..f39c6418710d 100644 --- a/sys/net80211/ieee80211_power.c +++ b/sys/net80211/ieee80211_power.c @@ -1,650 +1,651 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2002-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 /* * IEEE 802.11 power save support. */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include static void ieee80211_update_ps(struct ieee80211vap *, int); static int ieee80211_set_tim(struct ieee80211_node *, int); static MALLOC_DEFINE(M_80211_POWER, "80211power", "802.11 power save state"); void ieee80211_power_attach(struct ieee80211com *ic) { } void ieee80211_power_detach(struct ieee80211com *ic) { } void ieee80211_power_vattach(struct ieee80211vap *vap) { if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_IBSS) { /* NB: driver should override */ vap->iv_update_ps = ieee80211_update_ps; vap->iv_set_tim = ieee80211_set_tim; } vap->iv_node_ps = ieee80211_node_pwrsave; vap->iv_sta_ps = ieee80211_sta_pwrsave; } void ieee80211_power_latevattach(struct ieee80211vap *vap) { /* * Allocate these only if needed. Beware that we * know adhoc mode doesn't support ATIM yet... */ if (vap->iv_opmode == IEEE80211_M_HOSTAP) { vap->iv_tim_len = howmany(vap->iv_max_aid,8) * sizeof(uint8_t); vap->iv_tim_bitmap = (uint8_t *) IEEE80211_MALLOC(vap->iv_tim_len, M_80211_POWER, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (vap->iv_tim_bitmap == NULL) { - printf("%s: no memory for TIM bitmap!\n", __func__); + net80211_vap_printf(vap, + "%s: no memory for TIM bitmap!\n", __func__); /* XXX good enough to keep from crashing? */ vap->iv_tim_len = 0; } } } void ieee80211_power_vdetach(struct ieee80211vap *vap) { if (vap->iv_tim_bitmap != NULL) { IEEE80211_FREE(vap->iv_tim_bitmap, M_80211_POWER); vap->iv_tim_bitmap = NULL; } } void ieee80211_psq_init(struct ieee80211_psq *psq, const char *name) { memset(psq, 0, sizeof(*psq)); psq->psq_maxlen = IEEE80211_PS_MAX_QUEUE; IEEE80211_PSQ_INIT(psq, name); /* OS-dependent setup */ } void ieee80211_psq_cleanup(struct ieee80211_psq *psq) { #if 0 psq_drain(psq); /* XXX should not be needed? */ #else KASSERT(psq->psq_len == 0, ("%d frames on ps q", psq->psq_len)); #endif IEEE80211_PSQ_DESTROY(psq); /* OS-dependent cleanup */ } /* * Return the highest priority frame in the ps queue. */ struct mbuf * ieee80211_node_psq_dequeue(struct ieee80211_node *ni, int *qlen) { struct ieee80211_psq *psq = &ni->ni_psq; struct ieee80211_psq_head *qhead; struct mbuf *m; IEEE80211_PSQ_LOCK(psq); qhead = &psq->psq_head[0]; again: if ((m = qhead->head) != NULL) { if ((qhead->head = m->m_nextpkt) == NULL) qhead->tail = NULL; KASSERT(qhead->len > 0, ("qhead len %d", qhead->len)); qhead->len--; KASSERT(psq->psq_len > 0, ("psq len %d", psq->psq_len)); psq->psq_len--; m->m_nextpkt = NULL; } if (m == NULL && qhead == &psq->psq_head[0]) { /* Algol-68 style for loop */ qhead = &psq->psq_head[1]; goto again; } if (qlen != NULL) *qlen = psq->psq_len; IEEE80211_PSQ_UNLOCK(psq); return m; } /* * Reclaim an mbuf from the ps q. If marked with M_ENCAP * we assume there is a node reference that must be relcaimed. */ static void psq_mfree(struct mbuf *m) { if (m->m_flags & M_ENCAP) { struct ieee80211_node *ni = (void *) m->m_pkthdr.rcvif; ieee80211_free_node(ni); } m->m_nextpkt = NULL; m_freem(m); } /* * Clear any frames queued in the power save queue. * The number of frames that were present is returned. */ static int psq_drain(struct ieee80211_psq *psq) { struct ieee80211_psq_head *qhead; struct mbuf *m; int qlen; IEEE80211_PSQ_LOCK(psq); qlen = psq->psq_len; qhead = &psq->psq_head[0]; again: while ((m = qhead->head) != NULL) { qhead->head = m->m_nextpkt; psq_mfree(m); } qhead->tail = NULL; qhead->len = 0; if (qhead == &psq->psq_head[0]) { /* Algol-68 style for loop */ qhead = &psq->psq_head[1]; goto again; } psq->psq_len = 0; IEEE80211_PSQ_UNLOCK(psq); return qlen; } /* * Clear any frames queued in the power save queue. * The number of frames that were present is returned. */ int ieee80211_node_psq_drain(struct ieee80211_node *ni) { return psq_drain(&ni->ni_psq); } /* * Age frames on the power save 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 so the caller * can check if it needs to adjust the tim. */ int ieee80211_node_psq_age(struct ieee80211_node *ni) { struct ieee80211_psq *psq = &ni->ni_psq; int discard = 0; if (psq->psq_len != 0) { #ifdef IEEE80211_DEBUG struct ieee80211vap *vap = ni->ni_vap; #endif struct ieee80211_psq_head *qhead; struct mbuf *m; IEEE80211_PSQ_LOCK(psq); qhead = &psq->psq_head[0]; again: while ((m = qhead->head) != NULL && M_AGE_GET(m) < IEEE80211_INACT_WAIT) { IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "discard frame, age %u", M_AGE_GET(m)); if ((qhead->head = m->m_nextpkt) == NULL) qhead->tail = NULL; KASSERT(qhead->len > 0, ("qhead len %d", qhead->len)); qhead->len--; KASSERT(psq->psq_len > 0, ("psq len %d", psq->psq_len)); psq->psq_len--; psq_mfree(m); discard++; } if (qhead == &psq->psq_head[0]) { /* Algol-68 style for loop */ qhead = &psq->psq_head[1]; goto again; } if (m != NULL) M_AGE_SUB(m, IEEE80211_INACT_WAIT); IEEE80211_PSQ_UNLOCK(psq); IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "discard %u frames for age", discard); IEEE80211_NODE_STAT_ADD(ni, ps_discard, discard); } return discard; } /* * Handle a change in the PS station occupancy. */ static void ieee80211_update_ps(struct ieee80211vap *vap, int nsta) { KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_IBSS, ("operating mode %u", vap->iv_opmode)); } /* * Indicate whether there are frames queued for a station in power-save mode. */ static int ieee80211_set_tim(struct ieee80211_node *ni, int set) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; uint16_t aid; int changed; KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_IBSS, ("operating mode %u", vap->iv_opmode)); aid = IEEE80211_AID(ni->ni_associd); KASSERT(aid < vap->iv_max_aid, ("bogus aid %u, max %u", aid, vap->iv_max_aid)); IEEE80211_LOCK(ic); changed = (set != (isset(vap->iv_tim_bitmap, aid) != 0)); if (changed) { if (set) { setbit(vap->iv_tim_bitmap, aid); vap->iv_ps_pending++; } else { clrbit(vap->iv_tim_bitmap, aid); vap->iv_ps_pending--; } /* NB: we know vap is in RUN state so no need to check */ vap->iv_update_beacon(vap, IEEE80211_BEACON_TIM); } IEEE80211_UNLOCK(ic); return changed; } /* * Save an outbound packet for a node in power-save sleep state. * The new packet is placed on the node's saved queue, and the TIM * is changed, if necessary. */ int ieee80211_pwrsave(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211_psq *psq = &ni->ni_psq; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_psq_head *qhead; int qlen, age; IEEE80211_PSQ_LOCK(psq); if (psq->psq_len >= psq->psq_maxlen) { psq->psq_drops++; IEEE80211_PSQ_UNLOCK(psq); IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "pwr save q overflow, drops %d (size %d)", psq->psq_drops, psq->psq_len); #ifdef IEEE80211_DEBUG if (ieee80211_msg_dumppkts(vap)) ieee80211_dump_pkt(ni->ni_ic, mtod(m, caddr_t), m->m_len, -1, -1); #endif psq_mfree(m); return ENOSPC; } /* * Tag the frame with it's expiry time and insert it in * the appropriate 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. */ /* TU -> secs. XXX handle overflow? */ age = IEEE80211_TU_TO_MS((ni->ni_intval * ic->ic_bintval) << 2) / 1000; /* * Encapsulated frames go on the high priority queue, * other stuff goes on the low priority queue. We use * this to order frames returned out of the driver * ahead of frames we collect in ieee80211_start. */ if (m->m_flags & M_ENCAP) qhead = &psq->psq_head[0]; else qhead = &psq->psq_head[1]; if (qhead->tail == NULL) { struct mbuf *mh; qhead->head = m; /* * Take care to adjust age when inserting the first * frame of a queue and the other queue already has * frames. We need to preserve the age difference * relationship so ieee80211_node_psq_age works. */ if (qhead == &psq->psq_head[1]) { mh = psq->psq_head[0].head; if (mh != NULL) age-= M_AGE_GET(mh); } else { mh = psq->psq_head[1].head; if (mh != NULL) { int nage = M_AGE_GET(mh) - age; /* XXX is clamping to zero good 'nuf? */ M_AGE_SET(mh, nage < 0 ? 0 : nage); } } } else { qhead->tail->m_nextpkt = m; age -= M_AGE_GET(qhead->head); } KASSERT(age >= 0, ("age %d", age)); M_AGE_SET(m, age); m->m_nextpkt = NULL; qhead->tail = m; qhead->len++; qlen = ++(psq->psq_len); IEEE80211_PSQ_UNLOCK(psq); IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "save frame with age %d, %u now queued", age, qlen); if (qlen == 1 && vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 1); return 0; } /* * Move frames from the ps q to the vap's send queue * and/or the driver's send queue; and kick the start * method for each, as appropriate. Note we're careful * to preserve packet ordering here. */ static void pwrsave_flushq(struct ieee80211_node *ni) { struct ieee80211_psq *psq = &ni->ni_psq; struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_psq_head *qhead; struct mbuf *parent_q = NULL, *ifp_q = NULL; struct mbuf *m; IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "flush ps queue, %u packets queued", psq->psq_len); IEEE80211_PSQ_LOCK(psq); qhead = &psq->psq_head[0]; /* 802.11 frames */ if (qhead->head != NULL) { /* XXX could dispatch through vap and check M_ENCAP */ /* XXX need different driver interface */ /* XXX bypasses q max and OACTIVE */ parent_q = qhead->head; qhead->head = qhead->tail = NULL; qhead->len = 0; } qhead = &psq->psq_head[1]; /* 802.3 frames */ if (qhead->head != NULL) { /* XXX need different driver interface */ /* XXX bypasses q max and OACTIVE */ ifp_q = qhead->head; qhead->head = qhead->tail = NULL; qhead->len = 0; } psq->psq_len = 0; IEEE80211_PSQ_UNLOCK(psq); /* NB: do this outside the psq lock */ /* XXX packets might get reordered if parent is OACTIVE */ /* parent frames, should be encapsulated */ while (parent_q != NULL) { m = parent_q; parent_q = m->m_nextpkt; m->m_nextpkt = NULL; /* must be encapsulated */ KASSERT((m->m_flags & M_ENCAP), ("%s: parentq with non-M_ENCAP frame!\n", __func__)); (void) ieee80211_parent_xmitpkt(ic, m); } /* VAP frames, aren't encapsulated */ while (ifp_q != NULL) { m = ifp_q; ifp_q = m->m_nextpkt; m->m_nextpkt = NULL; KASSERT((!(m->m_flags & M_ENCAP)), ("%s: vapq with M_ENCAP frame!\n", __func__)); (void) ieee80211_vap_xmitpkt(vap, m); } } /* * Handle station power-save state change. */ void ieee80211_node_pwrsave(struct ieee80211_node *ni, int enable) { struct ieee80211vap *vap = ni->ni_vap; int update; update = 0; if (enable) { if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) { vap->iv_ps_sta++; update = 1; } ni->ni_flags |= IEEE80211_NODE_PWR_MGT; IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "power save mode on, %u sta's in ps mode", vap->iv_ps_sta); if (update) vap->iv_update_ps(vap, vap->iv_ps_sta); } else { if (ni->ni_flags & IEEE80211_NODE_PWR_MGT) { vap->iv_ps_sta--; update = 1; } ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "power save mode off, %u sta's in ps mode", vap->iv_ps_sta); /* NB: order here is intentional so TIM is clear before flush */ if (vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); if (update) { /* NB if no sta's in ps, driver should flush mc q */ vap->iv_update_ps(vap, vap->iv_ps_sta); } if (ni->ni_psq.psq_len != 0) pwrsave_flushq(ni); } } /* * Handle power-save state change in station mode. */ void ieee80211_sta_pwrsave(struct ieee80211vap *vap, int enable) { struct ieee80211_node *ni = vap->iv_bss; if (!((enable != 0) ^ ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) != 0))) return; IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "sta power save mode %s", enable ? "on" : "off"); if (!enable) { ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; ieee80211_send_nulldata(ieee80211_ref_node(ni)); /* * Flush any queued frames; we can do this immediately * because we know they'll be queued behind the null * data frame we send the ap. * XXX can we use a data frame to take us out of ps? */ if (ni->ni_psq.psq_len != 0) pwrsave_flushq(ni); } else { ni->ni_flags |= IEEE80211_NODE_PWR_MGT; ieee80211_send_nulldata(ieee80211_ref_node(ni)); } } /* * Handle being notified that we have data available for us in a TIM/ATIM. * * This may schedule a transition from _SLEEP -> _RUN if it's appropriate. * * In STA mode, we may have put to sleep during scan and need to be dragged * back out of powersave mode. */ void ieee80211_sta_tim_notify(struct ieee80211vap *vap, int set) { struct ieee80211com *ic = vap->iv_ic; /* * Schedule the driver state change. It'll happen at some point soon. * Since the hardware shouldn't know that we're running just yet * (and thus tell the peer that we're awake before we actually wake * up said hardware), we leave the actual node state transition * up to the transition to RUN. * * XXX TODO: verify that the transition to RUN will wake up the * BSS node! */ IEEE80211_LOCK(vap->iv_ic); if (set == 1 && vap->iv_state == IEEE80211_S_SLEEP) { ieee80211_new_state_locked(vap, IEEE80211_S_RUN, 0); IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, "%s: TIM=%d; wakeup\n", __func__, set); } else if ((set == 1) && (ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN)) { /* * XXX only do this if we're in RUN state? */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, "%s: wake up from bgscan vap sleep\n", __func__); /* * We may be in BGSCAN mode - this means the VAP is in STA * mode powersave. If it is, we need to wake it up so we * can process outbound traffic. */ vap->iv_sta_ps(vap, 0); } IEEE80211_UNLOCK(vap->iv_ic); } /* * Timer check on whether the VAP has had any transmit activity. * * This may schedule a transition from _RUN -> _SLEEP if it's appropriate. */ void ieee80211_sta_ps_timer_check(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; /* XXX lock assert */ /* For no, only do this in STA mode */ if (! (vap->iv_caps & IEEE80211_C_SWSLEEP)) goto out; if (vap->iv_opmode != IEEE80211_M_STA) goto out; /* If we're not at run state, bail */ if (vap->iv_state != IEEE80211_S_RUN) goto out; IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, "%s: lastdata=%llu, ticks=%llu\n", __func__, (unsigned long long) ic->ic_lastdata, (unsigned long long) ticks); /* If powersave is disabled on the VAP, don't bother */ if (! (vap->iv_flags & IEEE80211_F_PMGTON)) goto out; /* If we've done any data within our idle interval, bail */ /* XXX hard-coded to one second for now, ew! */ if (ieee80211_time_after(ic->ic_lastdata + 500, ticks)) goto out; /* * Signify we're going into power save and transition the * node to powersave. */ if ((vap->iv_bss->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) vap->iv_sta_ps(vap, 1); /* * XXX The driver has to handle the fact that we're going * to sleep but frames may still be transmitted; * hopefully it and/or us will do the right thing and mark any * transmitted frames with PWRMGT set to 1. */ ieee80211_new_state_locked(vap, IEEE80211_S_SLEEP, 0); IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, "%s: time delta=%d msec\n", __func__, (int) ticks_to_msecs(ticks - ic->ic_lastdata)); out: return; } diff --git a/sys/net80211/ieee80211_proto.c b/sys/net80211/ieee80211_proto.c index 14c0d2beaad5..0c161d98a55a 100644 --- a/sys/net80211/ieee80211_proto.c +++ b/sys/net80211/ieee80211_proto.c @@ -1,2915 +1,2915 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2008 Sam Leffler, Errno Consulting * Copyright (c) 2012 IEEE * 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 /* * IEEE 802.11 protocol support. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include /* XXX for ether_sprintf */ #include #include #include #include #include #ifdef IEEE80211_SUPPORT_MESH #include #endif #include #include /* XXX tunables */ #define AGGRESSIVE_MODE_SWITCH_HYSTERESIS 3 /* pkts / 100ms */ #define HIGH_PRI_SWITCH_THRESH 10 /* pkts / 100ms */ const char *mgt_subtype_name[] = { "assoc_req", "assoc_resp", "reassoc_req", "reassoc_resp", "probe_req", "probe_resp", "timing_adv", "reserved#7", "beacon", "atim", "disassoc", "auth", "deauth", "action", "action_noack", "reserved#15" }; const char *ctl_subtype_name[] = { "reserved#0", "reserved#1", "reserved#2", "reserved#3", "reserved#4", "reserved#5", "reserved#6", "control_wrap", "bar", "ba", "ps_poll", "rts", "cts", "ack", "cf_end", "cf_end_ack" }; const char *ieee80211_opmode_name[IEEE80211_OPMODE_MAX] = { "IBSS", /* IEEE80211_M_IBSS */ "STA", /* IEEE80211_M_STA */ "WDS", /* IEEE80211_M_WDS */ "AHDEMO", /* IEEE80211_M_AHDEMO */ "HOSTAP", /* IEEE80211_M_HOSTAP */ "MONITOR", /* IEEE80211_M_MONITOR */ "MBSS" /* IEEE80211_M_MBSS */ }; const char *ieee80211_state_name[IEEE80211_S_MAX] = { "INIT", /* IEEE80211_S_INIT */ "SCAN", /* IEEE80211_S_SCAN */ "AUTH", /* IEEE80211_S_AUTH */ "ASSOC", /* IEEE80211_S_ASSOC */ "CAC", /* IEEE80211_S_CAC */ "RUN", /* IEEE80211_S_RUN */ "CSA", /* IEEE80211_S_CSA */ "SLEEP", /* IEEE80211_S_SLEEP */ }; const char *ieee80211_wme_acnames[] = { "WME_AC_BE", "WME_AC_BK", "WME_AC_VI", "WME_AC_VO", "WME_UPSD", }; /* * Reason code descriptions were (mostly) obtained from * IEEE Std 802.11-2012, pp. 442-445 Table 8-36. */ const char * ieee80211_reason_to_string(uint16_t reason) { switch (reason) { case IEEE80211_REASON_UNSPECIFIED: return ("unspecified"); case IEEE80211_REASON_AUTH_EXPIRE: return ("previous authentication is expired"); case IEEE80211_REASON_AUTH_LEAVE: return ("sending STA is leaving/has left IBSS or ESS"); case IEEE80211_REASON_ASSOC_EXPIRE: return ("disassociated due to inactivity"); case IEEE80211_REASON_ASSOC_TOOMANY: return ("too many associated STAs"); case IEEE80211_REASON_NOT_AUTHED: return ("class 2 frame received from nonauthenticated STA"); case IEEE80211_REASON_NOT_ASSOCED: return ("class 3 frame received from nonassociated STA"); case IEEE80211_REASON_ASSOC_LEAVE: return ("sending STA is leaving/has left BSS"); case IEEE80211_REASON_ASSOC_NOT_AUTHED: return ("STA requesting (re)association is not authenticated"); case IEEE80211_REASON_DISASSOC_PWRCAP_BAD: return ("information in the Power Capability element is " "unacceptable"); case IEEE80211_REASON_DISASSOC_SUPCHAN_BAD: return ("information in the Supported Channels element is " "unacceptable"); case IEEE80211_REASON_IE_INVALID: return ("invalid element"); case IEEE80211_REASON_MIC_FAILURE: return ("MIC failure"); case IEEE80211_REASON_4WAY_HANDSHAKE_TIMEOUT: return ("4-Way handshake timeout"); case IEEE80211_REASON_GROUP_KEY_UPDATE_TIMEOUT: return ("group key update timeout"); case IEEE80211_REASON_IE_IN_4WAY_DIFFERS: return ("element in 4-Way handshake different from " "(re)association request/probe response/beacon frame"); case IEEE80211_REASON_GROUP_CIPHER_INVALID: return ("invalid group cipher"); case IEEE80211_REASON_PAIRWISE_CIPHER_INVALID: return ("invalid pairwise cipher"); case IEEE80211_REASON_AKMP_INVALID: return ("invalid AKMP"); case IEEE80211_REASON_UNSUPP_RSN_IE_VERSION: return ("unsupported version in RSN IE"); case IEEE80211_REASON_INVALID_RSN_IE_CAP: return ("invalid capabilities in RSN IE"); case IEEE80211_REASON_802_1X_AUTH_FAILED: return ("IEEE 802.1X authentication failed"); case IEEE80211_REASON_CIPHER_SUITE_REJECTED: return ("cipher suite rejected because of the security " "policy"); case IEEE80211_REASON_UNSPECIFIED_QOS: return ("unspecified (QoS-related)"); case IEEE80211_REASON_INSUFFICIENT_BW: return ("QoS AP lacks sufficient bandwidth for this QoS STA"); case IEEE80211_REASON_TOOMANY_FRAMES: return ("too many frames need to be acknowledged"); case IEEE80211_REASON_OUTSIDE_TXOP: return ("STA is transmitting outside the limits of its TXOPs"); case IEEE80211_REASON_LEAVING_QBSS: return ("requested from peer STA (the STA is " "resetting/leaving the BSS)"); case IEEE80211_REASON_BAD_MECHANISM: return ("requested from peer STA (it does not want to use " "the mechanism)"); case IEEE80211_REASON_SETUP_NEEDED: return ("requested from peer STA (setup is required for the " "used mechanism)"); case IEEE80211_REASON_TIMEOUT: return ("requested from peer STA (timeout)"); case IEEE80211_REASON_PEER_LINK_CANCELED: return ("SME cancels the mesh peering instance (not related " "to the maximum number of peer mesh STAs)"); case IEEE80211_REASON_MESH_MAX_PEERS: return ("maximum number of peer mesh STAs was reached"); case IEEE80211_REASON_MESH_CPVIOLATION: return ("the received information violates the Mesh " "Configuration policy configured in the mesh STA " "profile"); case IEEE80211_REASON_MESH_CLOSE_RCVD: return ("the mesh STA has received a Mesh Peering Close " "message requesting to close the mesh peering"); case IEEE80211_REASON_MESH_MAX_RETRIES: return ("the mesh STA has resent dot11MeshMaxRetries Mesh " "Peering Open messages, without receiving a Mesh " "Peering Confirm message"); case IEEE80211_REASON_MESH_CONFIRM_TIMEOUT: return ("the confirmTimer for the mesh peering instance times " "out"); case IEEE80211_REASON_MESH_INVALID_GTK: return ("the mesh STA fails to unwrap the GTK or the values " "in the wrapped contents do not match"); case IEEE80211_REASON_MESH_INCONS_PARAMS: return ("the mesh STA receives inconsistent information about " "the mesh parameters between Mesh Peering Management " "frames"); case IEEE80211_REASON_MESH_INVALID_SECURITY: return ("the mesh STA fails the authenticated mesh peering " "exchange because due to failure in selecting " "pairwise/group ciphersuite"); case IEEE80211_REASON_MESH_PERR_NO_PROXY: return ("the mesh STA does not have proxy information for " "this external destination"); case IEEE80211_REASON_MESH_PERR_NO_FI: return ("the mesh STA does not have forwarding information " "for this destination"); case IEEE80211_REASON_MESH_PERR_DEST_UNREACH: return ("the mesh STA determines that the link to the next " "hop of an active path in its forwarding information " "is no longer usable"); case IEEE80211_REASON_MESH_MAC_ALRDY_EXISTS_MBSS: return ("the MAC address of the STA already exists in the " "mesh BSS"); case IEEE80211_REASON_MESH_CHAN_SWITCH_REG: return ("the mesh STA performs channel switch to meet " "regulatory requirements"); case IEEE80211_REASON_MESH_CHAN_SWITCH_UNSPEC: return ("the mesh STA performs channel switch with " "unspecified reason"); default: return ("reserved/unknown"); } } static void beacon_miss(void *, int); static void beacon_swmiss(void *, int); static void parent_updown(void *, int); static void update_mcast(void *, int); static void update_promisc(void *, int); static void update_channel(void *, int); static void update_chw(void *, int); static void vap_update_wme(void *, int); static void vap_update_slot(void *, int); static void restart_vaps(void *, int); static void vap_update_erp_protmode(void *, int); static void vap_update_preamble(void *, int); static void vap_update_ht_protmode(void *, int); static void ieee80211_newstate_cb(void *, int); static struct ieee80211_node *vap_update_bss(struct ieee80211vap *, struct ieee80211_node *); static int null_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { ic_printf(ni->ni_ic, "missing ic_raw_xmit callback, drop frame\n"); m_freem(m); return ENETDOWN; } void ieee80211_proto_attach(struct ieee80211com *ic) { uint8_t hdrlen; /* override the 802.3 setting */ hdrlen = ic->ic_headroom + sizeof(struct ieee80211_qosframe_addr4) + IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN + IEEE80211_WEP_EXTIVLEN; /* XXX no way to recalculate on ifdetach */ max_linkhdr_grow(ALIGN(hdrlen)); //ic->ic_protmode = IEEE80211_PROT_CTSONLY; TASK_INIT(&ic->ic_parent_task, 0, parent_updown, ic); TASK_INIT(&ic->ic_mcast_task, 0, update_mcast, ic); TASK_INIT(&ic->ic_promisc_task, 0, update_promisc, ic); TASK_INIT(&ic->ic_chan_task, 0, update_channel, ic); TASK_INIT(&ic->ic_bmiss_task, 0, beacon_miss, ic); TASK_INIT(&ic->ic_chw_task, 0, update_chw, ic); TASK_INIT(&ic->ic_restart_task, 0, restart_vaps, ic); ic->ic_wme.wme_hipri_switch_hysteresis = AGGRESSIVE_MODE_SWITCH_HYSTERESIS; /* initialize management frame handlers */ ic->ic_send_mgmt = ieee80211_send_mgmt; ic->ic_raw_xmit = null_raw_xmit; ieee80211_adhoc_attach(ic); ieee80211_sta_attach(ic); ieee80211_wds_attach(ic); ieee80211_hostap_attach(ic); #ifdef IEEE80211_SUPPORT_MESH ieee80211_mesh_attach(ic); #endif ieee80211_monitor_attach(ic); } void ieee80211_proto_detach(struct ieee80211com *ic) { ieee80211_monitor_detach(ic); #ifdef IEEE80211_SUPPORT_MESH ieee80211_mesh_detach(ic); #endif ieee80211_hostap_detach(ic); ieee80211_wds_detach(ic); ieee80211_adhoc_detach(ic); ieee80211_sta_detach(ic); } static void null_update_beacon(struct ieee80211vap *vap, int item) { } void ieee80211_proto_vattach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ifnet *ifp = vap->iv_ifp; int i; /* override the 802.3 setting */ ifp->if_hdrlen = ic->ic_headroom + sizeof(struct ieee80211_qosframe_addr4) + IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN + IEEE80211_WEP_EXTIVLEN; vap->iv_rtsthreshold = IEEE80211_RTS_DEFAULT; vap->iv_fragthreshold = IEEE80211_FRAG_DEFAULT; vap->iv_bmiss_max = IEEE80211_BMISS_MAX; callout_init_mtx(&vap->iv_swbmiss, IEEE80211_LOCK_OBJ(ic), 0); callout_init(&vap->iv_mgtsend, 1); for (i = 0; i < NET80211_IV_NSTATE_NUM; i++) TASK_INIT(&vap->iv_nstate_task[i], 0, ieee80211_newstate_cb, vap); TASK_INIT(&vap->iv_swbmiss_task, 0, beacon_swmiss, vap); TASK_INIT(&vap->iv_wme_task, 0, vap_update_wme, vap); TASK_INIT(&vap->iv_slot_task, 0, vap_update_slot, vap); TASK_INIT(&vap->iv_erp_protmode_task, 0, vap_update_erp_protmode, vap); TASK_INIT(&vap->iv_ht_protmode_task, 0, vap_update_ht_protmode, vap); TASK_INIT(&vap->iv_preamble_task, 0, vap_update_preamble, vap); /* * Install default tx rate handling: no fixed rate, lowest * supported rate for mgmt and multicast frames. Default * max retry count. These settings can be changed by the * driver and/or user applications. */ for (i = IEEE80211_MODE_11A; i < IEEE80211_MODE_MAX; i++) { if (isclr(ic->ic_modecaps, i)) continue; const struct ieee80211_rateset *rs = &ic->ic_sup_rates[i]; vap->iv_txparms[i].ucastrate = IEEE80211_FIXED_RATE_NONE; /* * Setting the management rate to MCS 0 assumes that the * BSS Basic rate set is empty and the BSS Basic MCS set * is not. * * Since we're not checking this, default to the lowest * defined rate for this mode. * * At least one 11n AP (DLINK DIR-825) is reported to drop * some MCS management traffic (eg BA response frames.) * * See also: 9.6.0 of the 802.11n-2009 specification. */ #ifdef NOTYET if (i == IEEE80211_MODE_11NA || i == IEEE80211_MODE_11NG) { vap->iv_txparms[i].mgmtrate = 0 | IEEE80211_RATE_MCS; vap->iv_txparms[i].mcastrate = 0 | IEEE80211_RATE_MCS; } else { vap->iv_txparms[i].mgmtrate = rs->rs_rates[0] & IEEE80211_RATE_VAL; vap->iv_txparms[i].mcastrate = rs->rs_rates[0] & IEEE80211_RATE_VAL; } #endif vap->iv_txparms[i].mgmtrate = rs->rs_rates[0] & IEEE80211_RATE_VAL; vap->iv_txparms[i].mcastrate = rs->rs_rates[0] & IEEE80211_RATE_VAL; vap->iv_txparms[i].maxretry = IEEE80211_TXMAX_DEFAULT; } vap->iv_roaming = IEEE80211_ROAMING_AUTO; vap->iv_update_beacon = null_update_beacon; vap->iv_deliver_data = ieee80211_deliver_data; vap->iv_protmode = IEEE80211_PROT_CTSONLY; vap->iv_update_bss = vap_update_bss; /* attach support for operating mode */ ic->ic_vattach[vap->iv_opmode](vap); } void ieee80211_proto_vdetach(struct ieee80211vap *vap) { #define FREEAPPIE(ie) do { \ if (ie != NULL) \ IEEE80211_FREE(ie, M_80211_NODE_IE); \ } while (0) /* * Detach operating mode module. */ if (vap->iv_opdetach != NULL) vap->iv_opdetach(vap); /* * This should not be needed as we detach when reseting * the state but be conservative here since the * authenticator may do things like spawn kernel threads. */ if (vap->iv_auth->ia_detach != NULL) vap->iv_auth->ia_detach(vap); /* * Detach any ACL'ator. */ if (vap->iv_acl != NULL) vap->iv_acl->iac_detach(vap); FREEAPPIE(vap->iv_appie_beacon); FREEAPPIE(vap->iv_appie_probereq); FREEAPPIE(vap->iv_appie_proberesp); FREEAPPIE(vap->iv_appie_assocreq); FREEAPPIE(vap->iv_appie_assocresp); FREEAPPIE(vap->iv_appie_wpa); #undef FREEAPPIE } /* * Simple-minded authenticator module support. */ #define IEEE80211_AUTH_MAX (IEEE80211_AUTH_WPA+1) /* XXX well-known names */ static const char *auth_modnames[IEEE80211_AUTH_MAX] = { "wlan_internal", /* IEEE80211_AUTH_NONE */ "wlan_internal", /* IEEE80211_AUTH_OPEN */ "wlan_internal", /* IEEE80211_AUTH_SHARED */ "wlan_xauth", /* IEEE80211_AUTH_8021X */ "wlan_internal", /* IEEE80211_AUTH_AUTO */ "wlan_xauth", /* IEEE80211_AUTH_WPA */ }; static const struct ieee80211_authenticator *authenticators[IEEE80211_AUTH_MAX]; static const struct ieee80211_authenticator auth_internal = { .ia_name = "wlan_internal", .ia_attach = NULL, .ia_detach = NULL, .ia_node_join = NULL, .ia_node_leave = NULL, }; /* * Setup internal authenticators once; they are never unregistered. */ static void ieee80211_auth_setup(void) { ieee80211_authenticator_register(IEEE80211_AUTH_OPEN, &auth_internal); ieee80211_authenticator_register(IEEE80211_AUTH_SHARED, &auth_internal); ieee80211_authenticator_register(IEEE80211_AUTH_AUTO, &auth_internal); } SYSINIT(wlan_auth, SI_SUB_DRIVERS, SI_ORDER_FIRST, ieee80211_auth_setup, NULL); const struct ieee80211_authenticator * ieee80211_authenticator_get(int auth) { if (auth >= IEEE80211_AUTH_MAX) return NULL; if (authenticators[auth] == NULL) ieee80211_load_module(auth_modnames[auth]); return authenticators[auth]; } void ieee80211_authenticator_register(int type, const struct ieee80211_authenticator *auth) { if (type >= IEEE80211_AUTH_MAX) return; authenticators[type] = auth; } void ieee80211_authenticator_unregister(int type) { if (type >= IEEE80211_AUTH_MAX) return; authenticators[type] = NULL; } /* * Very simple-minded ACL module support. */ /* XXX just one for now */ static const struct ieee80211_aclator *acl = NULL; void ieee80211_aclator_register(const struct ieee80211_aclator *iac) { - printf("wlan: %s acl policy registered\n", iac->iac_name); + net80211_printf("wlan: %s acl policy registered\n", iac->iac_name); acl = iac; } void ieee80211_aclator_unregister(const struct ieee80211_aclator *iac) { if (acl == iac) acl = NULL; - printf("wlan: %s acl policy unregistered\n", iac->iac_name); + net80211_printf("wlan: %s acl policy unregistered\n", iac->iac_name); } const struct ieee80211_aclator * ieee80211_aclator_get(const char *name) { if (acl == NULL) ieee80211_load_module("wlan_acl"); return acl != NULL && strcmp(acl->iac_name, name) == 0 ? acl : NULL; } void ieee80211_print_essid(const uint8_t *essid, int len) { const uint8_t *p; int i; if (len > IEEE80211_NWID_LEN) len = IEEE80211_NWID_LEN; /* determine printable or not */ for (i = 0, p = essid; i < len; i++, p++) { if (*p < ' ' || *p > 0x7e) break; } if (i == len) { - printf("\""); + net80211_printf("\""); for (i = 0, p = essid; i < len; i++, p++) - printf("%c", *p); - printf("\""); + net80211_printf("%c", *p); + net80211_printf("\""); } else { - printf("0x"); + net80211_printf("0x"); for (i = 0, p = essid; i < len; i++, p++) - printf("%02x", *p); + net80211_printf("%02x", *p); } } void ieee80211_dump_pkt(struct ieee80211com *ic, const uint8_t *buf, int len, int rate, int rssi) { const struct ieee80211_frame *wh; int i; wh = (const struct ieee80211_frame *)buf; switch (wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) { case IEEE80211_FC1_DIR_NODS: - printf("NODS %s", ether_sprintf(wh->i_addr2)); - printf("->%s", ether_sprintf(wh->i_addr1)); - printf("(%s)", ether_sprintf(wh->i_addr3)); + net80211_printf("NODS %s", ether_sprintf(wh->i_addr2)); + net80211_printf("->%s", ether_sprintf(wh->i_addr1)); + net80211_printf("(%s)", ether_sprintf(wh->i_addr3)); break; case IEEE80211_FC1_DIR_TODS: - printf("TODS %s", ether_sprintf(wh->i_addr2)); - printf("->%s", ether_sprintf(wh->i_addr3)); - printf("(%s)", ether_sprintf(wh->i_addr1)); + net80211_printf("TODS %s", ether_sprintf(wh->i_addr2)); + net80211_printf("->%s", ether_sprintf(wh->i_addr3)); + net80211_printf("(%s)", ether_sprintf(wh->i_addr1)); break; case IEEE80211_FC1_DIR_FROMDS: - printf("FRDS %s", ether_sprintf(wh->i_addr3)); - printf("->%s", ether_sprintf(wh->i_addr1)); - printf("(%s)", ether_sprintf(wh->i_addr2)); + net80211_printf("FRDS %s", ether_sprintf(wh->i_addr3)); + net80211_printf("->%s", ether_sprintf(wh->i_addr1)); + net80211_printf("(%s)", ether_sprintf(wh->i_addr2)); break; case IEEE80211_FC1_DIR_DSTODS: - printf("DSDS %s", ether_sprintf((const uint8_t *)&wh[1])); - printf("->%s", ether_sprintf(wh->i_addr3)); - printf("(%s", ether_sprintf(wh->i_addr2)); - printf("->%s)", ether_sprintf(wh->i_addr1)); + net80211_printf("DSDS %s", ether_sprintf((const uint8_t *)&wh[1])); + net80211_printf("->%s", ether_sprintf(wh->i_addr3)); + net80211_printf("(%s", ether_sprintf(wh->i_addr2)); + net80211_printf("->%s)", ether_sprintf(wh->i_addr1)); break; } switch (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) { case IEEE80211_FC0_TYPE_DATA: - printf(" data"); + net80211_printf(" data"); break; case IEEE80211_FC0_TYPE_MGT: - printf(" %s", ieee80211_mgt_subtype_name(wh->i_fc[0])); + net80211_printf(" %s", ieee80211_mgt_subtype_name(wh->i_fc[0])); break; default: - printf(" type#%d", wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK); + net80211_printf(" type#%d", wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK); break; } if (IEEE80211_QOS_HAS_SEQ(wh)) { const struct ieee80211_qosframe *qwh = (const struct ieee80211_qosframe *)buf; - printf(" QoS [TID %u%s]", qwh->i_qos[0] & IEEE80211_QOS_TID, + net80211_printf(" QoS [TID %u%s]", qwh->i_qos[0] & IEEE80211_QOS_TID, qwh->i_qos[0] & IEEE80211_QOS_ACKPOLICY ? " ACM" : ""); } if (IEEE80211_IS_PROTECTED(wh)) { int off; off = ieee80211_anyhdrspace(ic, wh); - printf(" WEP [IV %.02x %.02x %.02x", + net80211_printf(" WEP [IV %.02x %.02x %.02x", buf[off+0], buf[off+1], buf[off+2]); if (buf[off+IEEE80211_WEP_IVLEN] & IEEE80211_WEP_EXTIV) - printf(" %.02x %.02x %.02x", + net80211_printf(" %.02x %.02x %.02x", buf[off+4], buf[off+5], buf[off+6]); - printf(" KID %u]", buf[off+IEEE80211_WEP_IVLEN] >> 6); + net80211_printf(" KID %u]", buf[off+IEEE80211_WEP_IVLEN] >> 6); } if (rate >= 0) - printf(" %dM", rate / 2); + net80211_printf(" %dM", rate / 2); if (rssi >= 0) - printf(" +%d", rssi); - printf("\n"); + net80211_printf(" +%d", rssi); + net80211_printf("\n"); if (len > 0) { for (i = 0; i < len; i++) { if ((i & 1) == 0) - printf(" "); - printf("%02x", buf[i]); + net80211_printf(" "); + net80211_printf("%02x", buf[i]); } - printf("\n"); + net80211_printf("\n"); } } static __inline int findrix(const struct ieee80211_rateset *rs, int r) { int i; for (i = 0; i < rs->rs_nrates; i++) if ((rs->rs_rates[i] & IEEE80211_RATE_VAL) == r) return i; return -1; } int ieee80211_fix_rate(struct ieee80211_node *ni, struct ieee80211_rateset *nrs, int flags) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; int i, j, rix, error; int okrate, badrate, fixedrate, ucastrate; const struct ieee80211_rateset *srs; uint8_t r; error = 0; okrate = badrate = 0; ucastrate = vap->iv_txparms[ieee80211_chan2mode(ni->ni_chan)].ucastrate; if (ucastrate != IEEE80211_FIXED_RATE_NONE) { /* * Workaround awkwardness with fixed rate. We are called * to check both the legacy rate set and the HT rate set * but we must apply any legacy fixed rate check only to the * legacy rate set and vice versa. We cannot tell what type * of rate set we've been given (legacy or HT) but we can * distinguish the fixed rate type (MCS have 0x80 set). * So to deal with this the caller communicates whether to * check MCS or legacy rate using the flags and we use the * type of any fixed rate to avoid applying an MCS to a * legacy rate and vice versa. */ if (ucastrate & 0x80) { if (flags & IEEE80211_F_DOFRATE) flags &= ~IEEE80211_F_DOFRATE; } else if ((ucastrate & 0x80) == 0) { if (flags & IEEE80211_F_DOFMCS) flags &= ~IEEE80211_F_DOFMCS; } /* NB: required to make MCS match below work */ ucastrate &= IEEE80211_RATE_VAL; } fixedrate = IEEE80211_FIXED_RATE_NONE; /* * XXX we are called to process both MCS and legacy rates; * we must use the appropriate basic rate set or chaos will * ensue; for now callers that want MCS must supply * IEEE80211_F_DOBRS; at some point we'll need to split this * function so there are two variants, one for MCS and one * for legacy rates. */ if (flags & IEEE80211_F_DOBRS) srs = (const struct ieee80211_rateset *) ieee80211_get_suphtrates(ic, ni->ni_chan); else srs = ieee80211_get_suprates(ic, ni->ni_chan); for (i = 0; i < nrs->rs_nrates; ) { if (flags & IEEE80211_F_DOSORT) { /* * Sort rates. */ for (j = i + 1; j < nrs->rs_nrates; j++) { if (IEEE80211_RV(nrs->rs_rates[i]) > IEEE80211_RV(nrs->rs_rates[j])) { r = nrs->rs_rates[i]; nrs->rs_rates[i] = nrs->rs_rates[j]; nrs->rs_rates[j] = r; } } } r = nrs->rs_rates[i] & IEEE80211_RATE_VAL; badrate = r; /* * Check for fixed rate. */ if (r == ucastrate) fixedrate = r; /* * Check against supported rates. */ rix = findrix(srs, r); if (flags & IEEE80211_F_DONEGO) { if (rix < 0) { /* * A rate in the node's rate set is not * supported. If this is a basic rate and we * are operating as a STA then this is an error. * Otherwise we just discard/ignore the rate. */ if ((flags & IEEE80211_F_JOIN) && (nrs->rs_rates[i] & IEEE80211_RATE_BASIC)) error++; } else if ((flags & IEEE80211_F_JOIN) == 0) { /* * Overwrite with the supported rate * value so any basic rate bit is set. */ nrs->rs_rates[i] = srs->rs_rates[rix]; } } if ((flags & IEEE80211_F_DODEL) && rix < 0) { /* * Delete unacceptable rates. */ nrs->rs_nrates--; for (j = i; j < nrs->rs_nrates; j++) nrs->rs_rates[j] = nrs->rs_rates[j + 1]; nrs->rs_rates[j] = 0; continue; } if (rix >= 0) okrate = nrs->rs_rates[i]; i++; } if (okrate == 0 || error != 0 || ((flags & (IEEE80211_F_DOFRATE|IEEE80211_F_DOFMCS)) && fixedrate != ucastrate)) { IEEE80211_NOTE(vap, IEEE80211_MSG_XRATE | IEEE80211_MSG_11N, ni, "%s: flags 0x%x okrate %d error %d fixedrate 0x%x " "ucastrate %x\n", __func__, fixedrate, ucastrate, flags); return badrate | IEEE80211_RATE_BASIC; } else return IEEE80211_RV(okrate); } /* * Reset 11g-related state. * * This is for per-VAP ERP/11g state. * * Eventually everything in ieee80211_reset_erp() will be * per-VAP and in here. */ void ieee80211_vap_reset_erp(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; vap->iv_nonerpsta = 0; vap->iv_longslotsta = 0; vap->iv_flags &= ~IEEE80211_F_USEPROT; /* * Set short preamble and ERP barker-preamble flags. */ if (IEEE80211_IS_CHAN_A(ic->ic_curchan) || (vap->iv_caps & IEEE80211_C_SHPREAMBLE)) { vap->iv_flags |= IEEE80211_F_SHPREAMBLE; vap->iv_flags &= ~IEEE80211_F_USEBARKER; } else { vap->iv_flags &= ~IEEE80211_F_SHPREAMBLE; vap->iv_flags |= IEEE80211_F_USEBARKER; } /* * Short slot time is enabled only when operating in 11g * and not in an IBSS. We must also honor whether or not * the driver is capable of doing it. */ ieee80211_vap_set_shortslottime(vap, IEEE80211_IS_CHAN_A(ic->ic_curchan) || IEEE80211_IS_CHAN_HT(ic->ic_curchan) || (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && vap->iv_opmode == IEEE80211_M_HOSTAP && (ic->ic_caps & IEEE80211_C_SHSLOT))); } /* * Reset 11g-related state. * * Note this resets the global state and a caller should schedule * a re-check of all the VAPs after setup to update said state. */ void ieee80211_reset_erp(struct ieee80211com *ic) { #if 0 ic->ic_flags &= ~IEEE80211_F_USEPROT; /* * Set short preamble and ERP barker-preamble flags. */ if (IEEE80211_IS_CHAN_A(ic->ic_curchan) || (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; } #endif /* XXX TODO: schedule a new per-VAP ERP calculation */ } static struct ieee80211_node * vap_update_bss(struct ieee80211vap *vap, struct ieee80211_node *ni) { struct ieee80211_node *obss; IEEE80211_LOCK_ASSERT(vap->iv_ic); obss = vap->iv_bss; vap->iv_bss = ni; return (obss); } /* * Deferred slot time update. * * For per-VAP slot time configuration, call the VAP * method if the VAP requires it. Otherwise, just call the * older global method. * * If the per-VAP method is called then it's expected that * the driver/firmware will take care of turning the per-VAP * flags into slot time configuration. * * If the per-VAP method is not called then the global flags will be * flipped into sync with the VAPs; ic_flags IEEE80211_F_SHSLOT will * be set only if all of the vaps will have it set. * * Look at the comments for vap_update_erp_protmode() for more * background; this assumes all VAPs are on the same channel. */ static void vap_update_slot(void *arg, int npending) { struct ieee80211vap *vap = arg; struct ieee80211com *ic = vap->iv_ic; struct ieee80211vap *iv; int num_shslot = 0, num_lgslot = 0; /* * Per-VAP path - we've already had the flags updated; * so just notify the driver and move on. */ if (vap->iv_updateslot != NULL) { vap->iv_updateslot(vap); return; } /* * Iterate over all of the VAP flags to update the * global flag. * * If all vaps have short slot enabled then flip on * short slot. If any vap has it disabled then * we leave it globally disabled. This should provide * correct behaviour in a multi-BSS scenario where * at least one VAP has short slot disabled for some * reason. */ IEEE80211_LOCK(ic); TAILQ_FOREACH(iv, &ic->ic_vaps, iv_next) { if (iv->iv_flags & IEEE80211_F_SHSLOT) num_shslot++; else num_lgslot++; } /* * It looks backwards but - if the number of short slot VAPs * is zero then we're not short slot. Else, we have one * or more short slot VAPs and we're checking to see if ANY * of them have short slot disabled. */ if (num_shslot == 0) ic->ic_flags &= ~IEEE80211_F_SHSLOT; else if (num_lgslot == 0) ic->ic_flags |= IEEE80211_F_SHSLOT; IEEE80211_UNLOCK(ic); /* * Call the driver with our new global slot time flags. */ if (ic->ic_updateslot != NULL) ic->ic_updateslot(ic); } /* * Deferred ERP protmode update. * * This currently calculates the global ERP protection mode flag * based on each of the VAPs. Any VAP with it enabled is enough * for the global flag to be enabled. All VAPs with it disabled * is enough for it to be disabled. * * This may make sense right now for the supported hardware where * net80211 is controlling the single channel configuration, but * offload firmware that's doing channel changes (eg off-channel * TDLS, off-channel STA, off-channel P2P STA/AP) may get some * silly looking flag updates. * * Ideally the protection mode calculation is done based on the * channel, and all VAPs using that channel will inherit it. * But until that's what net80211 does, this wil have to do. */ static void vap_update_erp_protmode(void *arg, int npending) { struct ieee80211vap *vap = arg; struct ieee80211com *ic = vap->iv_ic; struct ieee80211vap *iv; int enable_protmode = 0; int non_erp_present = 0; /* * Iterate over all of the VAPs to calculate the overlapping * ERP protection mode configuration and ERP present math. * * For now we assume that if a driver can handle this per-VAP * then it'll ignore the ic->ic_protmode variant and instead * will look at the vap related flags. */ IEEE80211_LOCK(ic); TAILQ_FOREACH(iv, &ic->ic_vaps, iv_next) { if (iv->iv_flags & IEEE80211_F_USEPROT) enable_protmode = 1; if (iv->iv_flags_ext & IEEE80211_FEXT_NONERP_PR) non_erp_present = 1; } if (enable_protmode) ic->ic_flags |= IEEE80211_F_USEPROT; else ic->ic_flags &= ~IEEE80211_F_USEPROT; if (non_erp_present) ic->ic_flags_ext |= IEEE80211_FEXT_NONERP_PR; else ic->ic_flags_ext &= ~IEEE80211_FEXT_NONERP_PR; /* Beacon update on all VAPs */ ieee80211_notify_erp_locked(ic); IEEE80211_UNLOCK(ic); IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG, "%s: called; enable_protmode=%d, non_erp_present=%d\n", __func__, enable_protmode, non_erp_present); /* * Now that the global configuration flags are calculated, * notify the VAP about its configuration. * * The global flags will be used when assembling ERP IEs * for multi-VAP operation, even if it's on a different * channel. Yes, that's going to need fixing in the * future. */ if (vap->iv_erp_protmode_update != NULL) vap->iv_erp_protmode_update(vap); } /* * Deferred ERP short preamble/barker update. * * All VAPs need to use short preamble for it to be globally * enabled or not. * * Look at the comments for vap_update_erp_protmode() for more * background; this assumes all VAPs are on the same channel. */ static void vap_update_preamble(void *arg, int npending) { struct ieee80211vap *vap = arg; struct ieee80211com *ic = vap->iv_ic; struct ieee80211vap *iv; int barker_count = 0, short_preamble_count = 0, count = 0; /* * Iterate over all of the VAPs to calculate the overlapping * short or long preamble configuration. * * For now we assume that if a driver can handle this per-VAP * then it'll ignore the ic->ic_flags variant and instead * will look at the vap related flags. */ IEEE80211_LOCK(ic); TAILQ_FOREACH(iv, &ic->ic_vaps, iv_next) { if (iv->iv_flags & IEEE80211_F_USEBARKER) barker_count++; if (iv->iv_flags & IEEE80211_F_SHPREAMBLE) short_preamble_count++; count++; } /* * As with vap_update_erp_protmode(), the global flags are * currently used for beacon IEs. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG, "%s: called; barker_count=%d, short_preamble_count=%d\n", __func__, barker_count, short_preamble_count); /* * Only flip on short preamble if all of the VAPs support * it. */ if (barker_count == 0 && short_preamble_count == count) { 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_DPRINTF(vap, IEEE80211_MSG_DEBUG, "%s: global barker=%d preamble=%d\n", __func__, !! (ic->ic_flags & IEEE80211_F_USEBARKER), !! (ic->ic_flags & IEEE80211_F_SHPREAMBLE)); /* Beacon update on all VAPs */ ieee80211_notify_erp_locked(ic); IEEE80211_UNLOCK(ic); /* Driver notification */ if (vap->iv_preamble_update != NULL) vap->iv_preamble_update(vap); } /* * Deferred HT protmode update and beacon update. * * Look at the comments for vap_update_erp_protmode() for more * background; this assumes all VAPs are on the same channel. */ static void vap_update_ht_protmode(void *arg, int npending) { struct ieee80211vap *vap = arg; struct ieee80211vap *iv; struct ieee80211com *ic = vap->iv_ic; int num_vaps = 0, num_pure = 0; int num_optional = 0, num_ht2040 = 0, num_nonht = 0; int num_ht_sta = 0, num_ht40_sta = 0, num_sta = 0; int num_nonhtpr = 0; /* * Iterate over all of the VAPs to calculate everything. * * There are a few different flags to calculate: * * + whether there's HT only or HT+legacy stations; * + whether there's HT20, HT40, or HT20+HT40 stations; * + whether the desired protection mode is mixed, pure or * one of the two above. * * For now we assume that if a driver can handle this per-VAP * then it'll ignore the ic->ic_htprotmode / ic->ic_curhtprotmode * variant and instead will look at the vap related variables. * * XXX TODO: non-greenfield STAs present (IEEE80211_HTINFO_NONGF_PRESENT) ! */ IEEE80211_LOCK(ic); TAILQ_FOREACH(iv, &ic->ic_vaps, iv_next) { num_vaps++; /* overlapping BSSes advertising non-HT status present */ if (iv->iv_flags_ht & IEEE80211_FHT_NONHT_PR) num_nonht++; /* Operating mode flags */ if (iv->iv_curhtprotmode & IEEE80211_HTINFO_NONHT_PRESENT) num_nonhtpr++; switch (iv->iv_curhtprotmode & IEEE80211_HTINFO_OPMODE) { case IEEE80211_HTINFO_OPMODE_PURE: num_pure++; break; case IEEE80211_HTINFO_OPMODE_PROTOPT: num_optional++; break; case IEEE80211_HTINFO_OPMODE_HT20PR: num_ht2040++; break; } IEEE80211_DPRINTF(vap, IEEE80211_MSG_11N, "%s: vap %s: nonht_pr=%d, curhtprotmode=0x%02x\n", __func__, ieee80211_get_vap_ifname(iv), !! (iv->iv_flags_ht & IEEE80211_FHT_NONHT_PR), iv->iv_curhtprotmode); num_ht_sta += iv->iv_ht_sta_assoc; num_ht40_sta += iv->iv_ht40_sta_assoc; num_sta += iv->iv_sta_assoc; } /* * Step 1 - if any VAPs indicate NONHT_PR set (overlapping BSS * non-HT present), set it here. This shouldn't be used by * anything but the old overlapping BSS logic so if any drivers * consume it, it's up to date. */ if (num_nonht > 0) ic->ic_flags_ht |= IEEE80211_FHT_NONHT_PR; else ic->ic_flags_ht &= ~IEEE80211_FHT_NONHT_PR; /* * Step 2 - default HT protection mode to MIXED (802.11-2016 10.26.3.1.) * * + If all VAPs are PURE, we can stay PURE. * + If all VAPs are PROTOPT, we can go to PROTOPT. * + If any VAP has HT20PR then it sees at least a HT40+HT20 station. * Note that we may have a VAP with one HT20 and a VAP with one HT40; * So we look at the sum ht and sum ht40 sta counts; if we have a * HT station and the HT20 != HT40 count, we have to do HT20PR here. * Note all stations need to be HT for this to be an option. * + The fall-through is MIXED, because it means we have some odd * non HT40-involved combination of opmode and this is the most * sensible default. */ ic->ic_curhtprotmode = IEEE80211_HTINFO_OPMODE_MIXED; if (num_pure == num_vaps) ic->ic_curhtprotmode = IEEE80211_HTINFO_OPMODE_PURE; if (num_optional == num_vaps) ic->ic_curhtprotmode = IEEE80211_HTINFO_OPMODE_PROTOPT; /* * Note: we need /a/ HT40 station somewhere for this to * be a possibility. */ if ((num_ht2040 > 0) || ((num_ht_sta > 0) && (num_ht40_sta > 0) && (num_ht_sta != num_ht40_sta))) ic->ic_curhtprotmode = IEEE80211_HTINFO_OPMODE_HT20PR; /* * Step 3 - if any of the stations across the VAPs are * non-HT then this needs to be flipped back to MIXED. */ if (num_ht_sta != num_sta) ic->ic_curhtprotmode = IEEE80211_HTINFO_OPMODE_MIXED; /* * Step 4 - If we see any overlapping BSS non-HT stations * via beacons then flip on NONHT_PRESENT. */ if (num_nonhtpr > 0) ic->ic_curhtprotmode |= IEEE80211_HTINFO_NONHT_PRESENT; /* Notify all VAPs to potentially update their beacons */ TAILQ_FOREACH(iv, &ic->ic_vaps, iv_next) ieee80211_htinfo_notify(iv); IEEE80211_UNLOCK(ic); IEEE80211_DPRINTF(vap, IEEE80211_MSG_11N, "%s: global: nonht_pr=%d ht_opmode=0x%02x\n", __func__, !! (ic->ic_flags_ht & IEEE80211_FHT_NONHT_PR), ic->ic_curhtprotmode); /* Driver update */ if (vap->iv_ht_protmode_update != NULL) vap->iv_ht_protmode_update(vap); } /* * Set the short slot time state and notify the driver. * * This is the per-VAP slot time state. */ void ieee80211_vap_set_shortslottime(struct ieee80211vap *vap, int onoff) { struct ieee80211com *ic = vap->iv_ic; /* XXX lock? */ /* * Only modify the per-VAP slot time. */ if (onoff) vap->iv_flags |= IEEE80211_F_SHSLOT; else vap->iv_flags &= ~IEEE80211_F_SHSLOT; IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG, "%s: called; onoff=%d\n", __func__, onoff); /* schedule the deferred slot flag update and update */ ieee80211_runtask(ic, &vap->iv_slot_task); } /* * Update the VAP short /long / barker preamble state and * update beacon state if needed. * * For now it simply copies the global flags into the per-vap * flags and schedules the callback. Later this will support * both global and per-VAP flags, especially useful for * and STA+STA multi-channel operation (eg p2p). */ void ieee80211_vap_update_preamble(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; /* XXX lock? */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG, "%s: called\n", __func__); /* schedule the deferred slot flag update and update */ ieee80211_runtask(ic, &vap->iv_preamble_task); } /* * Update the VAP 11g protection mode and update beacon state * if needed. */ void ieee80211_vap_update_erp_protmode(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; /* XXX lock? */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG, "%s: called\n", __func__); /* schedule the deferred slot flag update and update */ ieee80211_runtask(ic, &vap->iv_erp_protmode_task); } /* * Update the VAP 11n protection mode and update beacon state * if needed. */ void ieee80211_vap_update_ht_protmode(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; /* XXX lock? */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG, "%s: called\n", __func__); /* schedule the deferred protmode update */ ieee80211_runtask(ic, &vap->iv_ht_protmode_task); } /* * Check if the specified rate set supports ERP. * NB: the rate set is assumed to be sorted. */ int ieee80211_iserp_rateset(const struct ieee80211_rateset *rs) { static const int rates[] = { 2, 4, 11, 22, 12, 24, 48 }; int i, j; if (rs->rs_nrates < nitems(rates)) return 0; for (i = 0; i < nitems(rates); i++) { for (j = 0; j < rs->rs_nrates; j++) { int r = rs->rs_rates[j] & IEEE80211_RATE_VAL; if (rates[i] == r) goto next; if (r > rates[i]) return 0; } return 0; next: ; } return 1; } /* * Mark the basic rates for the rate table based on the * operating mode. For real 11g we mark all the 11b rates * and 6, 12, and 24 OFDM. For 11b compatibility we mark only * 11b rates. There's also a pseudo 11a-mode used to mark only * the basic OFDM rates. */ static void setbasicrates(struct ieee80211_rateset *rs, enum ieee80211_phymode mode, int add) { static const struct ieee80211_rateset basic[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_11A] = { 3, { 12, 24, 48 } }, [IEEE80211_MODE_11B] = { 2, { 2, 4 } }, /* NB: mixed b/g */ [IEEE80211_MODE_11G] = { 4, { 2, 4, 11, 22 } }, [IEEE80211_MODE_TURBO_A] = { 3, { 12, 24, 48 } }, [IEEE80211_MODE_TURBO_G] = { 4, { 2, 4, 11, 22 } }, [IEEE80211_MODE_STURBO_A] = { 3, { 12, 24, 48 } }, [IEEE80211_MODE_HALF] = { 3, { 6, 12, 24 } }, [IEEE80211_MODE_QUARTER] = { 3, { 3, 6, 12 } }, [IEEE80211_MODE_11NA] = { 3, { 12, 24, 48 } }, /* NB: mixed b/g */ [IEEE80211_MODE_11NG] = { 4, { 2, 4, 11, 22 } }, /* NB: mixed b/g */ [IEEE80211_MODE_VHT_2GHZ] = { 4, { 2, 4, 11, 22 } }, [IEEE80211_MODE_VHT_5GHZ] = { 3, { 12, 24, 48 } }, }; int i, j; for (i = 0; i < rs->rs_nrates; i++) { if (!add) rs->rs_rates[i] &= IEEE80211_RATE_VAL; for (j = 0; j < basic[mode].rs_nrates; j++) if (basic[mode].rs_rates[j] == rs->rs_rates[i]) { rs->rs_rates[i] |= IEEE80211_RATE_BASIC; break; } } } /* * Set the basic rates in a rate set. */ void ieee80211_setbasicrates(struct ieee80211_rateset *rs, enum ieee80211_phymode mode) { setbasicrates(rs, mode, 0); } /* * Add basic rates to a rate set. */ void ieee80211_addbasicrates(struct ieee80211_rateset *rs, enum ieee80211_phymode mode) { setbasicrates(rs, mode, 1); } /* * WME protocol support. * * The default 11a/b/g/n parameters come from the WiFi Alliance WMM * System Interopability Test Plan (v1.4, Appendix F) and the 802.11n * Draft 2.0 Test Plan (Appendix D). * * Static/Dynamic Turbo mode settings come from Atheros. */ typedef struct phyParamType { uint8_t aifsn; uint8_t logcwmin; uint8_t logcwmax; uint16_t txopLimit; uint8_t acm; } paramType; static const struct phyParamType phyParamForAC_BE[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = { 3, 4, 6, 0, 0 }, [IEEE80211_MODE_11A] = { 3, 4, 6, 0, 0 }, [IEEE80211_MODE_11B] = { 3, 4, 6, 0, 0 }, [IEEE80211_MODE_11G] = { 3, 4, 6, 0, 0 }, [IEEE80211_MODE_FH] = { 3, 4, 6, 0, 0 }, [IEEE80211_MODE_TURBO_A]= { 2, 3, 5, 0, 0 }, [IEEE80211_MODE_TURBO_G]= { 2, 3, 5, 0, 0 }, [IEEE80211_MODE_STURBO_A]={ 2, 3, 5, 0, 0 }, [IEEE80211_MODE_HALF] = { 3, 4, 6, 0, 0 }, [IEEE80211_MODE_QUARTER]= { 3, 4, 6, 0, 0 }, [IEEE80211_MODE_11NA] = { 3, 4, 6, 0, 0 }, [IEEE80211_MODE_11NG] = { 3, 4, 6, 0, 0 }, [IEEE80211_MODE_VHT_2GHZ] = { 3, 4, 6, 0, 0 }, [IEEE80211_MODE_VHT_5GHZ] = { 3, 4, 6, 0, 0 }, }; static const struct phyParamType phyParamForAC_BK[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = { 7, 4, 10, 0, 0 }, [IEEE80211_MODE_11A] = { 7, 4, 10, 0, 0 }, [IEEE80211_MODE_11B] = { 7, 4, 10, 0, 0 }, [IEEE80211_MODE_11G] = { 7, 4, 10, 0, 0 }, [IEEE80211_MODE_FH] = { 7, 4, 10, 0, 0 }, [IEEE80211_MODE_TURBO_A]= { 7, 3, 10, 0, 0 }, [IEEE80211_MODE_TURBO_G]= { 7, 3, 10, 0, 0 }, [IEEE80211_MODE_STURBO_A]={ 7, 3, 10, 0, 0 }, [IEEE80211_MODE_HALF] = { 7, 4, 10, 0, 0 }, [IEEE80211_MODE_QUARTER]= { 7, 4, 10, 0, 0 }, [IEEE80211_MODE_11NA] = { 7, 4, 10, 0, 0 }, [IEEE80211_MODE_11NG] = { 7, 4, 10, 0, 0 }, [IEEE80211_MODE_VHT_2GHZ] = { 7, 4, 10, 0, 0 }, [IEEE80211_MODE_VHT_5GHZ] = { 7, 4, 10, 0, 0 }, }; static const struct phyParamType phyParamForAC_VI[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = { 1, 3, 4, 94, 0 }, [IEEE80211_MODE_11A] = { 1, 3, 4, 94, 0 }, [IEEE80211_MODE_11B] = { 1, 3, 4, 188, 0 }, [IEEE80211_MODE_11G] = { 1, 3, 4, 94, 0 }, [IEEE80211_MODE_FH] = { 1, 3, 4, 188, 0 }, [IEEE80211_MODE_TURBO_A]= { 1, 2, 3, 94, 0 }, [IEEE80211_MODE_TURBO_G]= { 1, 2, 3, 94, 0 }, [IEEE80211_MODE_STURBO_A]={ 1, 2, 3, 94, 0 }, [IEEE80211_MODE_HALF] = { 1, 3, 4, 94, 0 }, [IEEE80211_MODE_QUARTER]= { 1, 3, 4, 94, 0 }, [IEEE80211_MODE_11NA] = { 1, 3, 4, 94, 0 }, [IEEE80211_MODE_11NG] = { 1, 3, 4, 94, 0 }, [IEEE80211_MODE_VHT_2GHZ] = { 1, 3, 4, 94, 0 }, [IEEE80211_MODE_VHT_5GHZ] = { 1, 3, 4, 94, 0 }, }; static const struct phyParamType phyParamForAC_VO[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = { 1, 2, 3, 47, 0 }, [IEEE80211_MODE_11A] = { 1, 2, 3, 47, 0 }, [IEEE80211_MODE_11B] = { 1, 2, 3, 102, 0 }, [IEEE80211_MODE_11G] = { 1, 2, 3, 47, 0 }, [IEEE80211_MODE_FH] = { 1, 2, 3, 102, 0 }, [IEEE80211_MODE_TURBO_A]= { 1, 2, 2, 47, 0 }, [IEEE80211_MODE_TURBO_G]= { 1, 2, 2, 47, 0 }, [IEEE80211_MODE_STURBO_A]={ 1, 2, 2, 47, 0 }, [IEEE80211_MODE_HALF] = { 1, 2, 3, 47, 0 }, [IEEE80211_MODE_QUARTER]= { 1, 2, 3, 47, 0 }, [IEEE80211_MODE_11NA] = { 1, 2, 3, 47, 0 }, [IEEE80211_MODE_11NG] = { 1, 2, 3, 47, 0 }, [IEEE80211_MODE_VHT_2GHZ] = { 1, 2, 3, 47, 0 }, [IEEE80211_MODE_VHT_5GHZ] = { 1, 2, 3, 47, 0 }, }; static const struct phyParamType bssPhyParamForAC_BE[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = { 3, 4, 10, 0, 0 }, [IEEE80211_MODE_11A] = { 3, 4, 10, 0, 0 }, [IEEE80211_MODE_11B] = { 3, 4, 10, 0, 0 }, [IEEE80211_MODE_11G] = { 3, 4, 10, 0, 0 }, [IEEE80211_MODE_FH] = { 3, 4, 10, 0, 0 }, [IEEE80211_MODE_TURBO_A]= { 2, 3, 10, 0, 0 }, [IEEE80211_MODE_TURBO_G]= { 2, 3, 10, 0, 0 }, [IEEE80211_MODE_STURBO_A]={ 2, 3, 10, 0, 0 }, [IEEE80211_MODE_HALF] = { 3, 4, 10, 0, 0 }, [IEEE80211_MODE_QUARTER]= { 3, 4, 10, 0, 0 }, [IEEE80211_MODE_11NA] = { 3, 4, 10, 0, 0 }, [IEEE80211_MODE_11NG] = { 3, 4, 10, 0, 0 }, }; static const struct phyParamType bssPhyParamForAC_VI[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = { 2, 3, 4, 94, 0 }, [IEEE80211_MODE_11A] = { 2, 3, 4, 94, 0 }, [IEEE80211_MODE_11B] = { 2, 3, 4, 188, 0 }, [IEEE80211_MODE_11G] = { 2, 3, 4, 94, 0 }, [IEEE80211_MODE_FH] = { 2, 3, 4, 188, 0 }, [IEEE80211_MODE_TURBO_A]= { 2, 2, 3, 94, 0 }, [IEEE80211_MODE_TURBO_G]= { 2, 2, 3, 94, 0 }, [IEEE80211_MODE_STURBO_A]={ 2, 2, 3, 94, 0 }, [IEEE80211_MODE_HALF] = { 2, 3, 4, 94, 0 }, [IEEE80211_MODE_QUARTER]= { 2, 3, 4, 94, 0 }, [IEEE80211_MODE_11NA] = { 2, 3, 4, 94, 0 }, [IEEE80211_MODE_11NG] = { 2, 3, 4, 94, 0 }, }; static const struct phyParamType bssPhyParamForAC_VO[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = { 2, 2, 3, 47, 0 }, [IEEE80211_MODE_11A] = { 2, 2, 3, 47, 0 }, [IEEE80211_MODE_11B] = { 2, 2, 3, 102, 0 }, [IEEE80211_MODE_11G] = { 2, 2, 3, 47, 0 }, [IEEE80211_MODE_FH] = { 2, 2, 3, 102, 0 }, [IEEE80211_MODE_TURBO_A]= { 1, 2, 2, 47, 0 }, [IEEE80211_MODE_TURBO_G]= { 1, 2, 2, 47, 0 }, [IEEE80211_MODE_STURBO_A]={ 1, 2, 2, 47, 0 }, [IEEE80211_MODE_HALF] = { 2, 2, 3, 47, 0 }, [IEEE80211_MODE_QUARTER]= { 2, 2, 3, 47, 0 }, [IEEE80211_MODE_11NA] = { 2, 2, 3, 47, 0 }, [IEEE80211_MODE_11NG] = { 2, 2, 3, 47, 0 }, }; static void _setifsparams(struct wmeParams *wmep, const paramType *phy) { wmep->wmep_aifsn = phy->aifsn; wmep->wmep_logcwmin = phy->logcwmin; wmep->wmep_logcwmax = phy->logcwmax; wmep->wmep_txopLimit = phy->txopLimit; } static void setwmeparams(struct ieee80211vap *vap, const char *type, int ac, struct wmeParams *wmep, const paramType *phy) { wmep->wmep_acm = phy->acm; _setifsparams(wmep, phy); IEEE80211_DPRINTF(vap, IEEE80211_MSG_WME, "set %s (%s) [acm %u aifsn %u logcwmin %u logcwmax %u txop %u]\n", ieee80211_wme_acnames[ac], type, wmep->wmep_acm, wmep->wmep_aifsn, wmep->wmep_logcwmin, wmep->wmep_logcwmax, wmep->wmep_txopLimit); } static void ieee80211_wme_initparams_locked(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_wme_state *wme = &ic->ic_wme; const paramType *pPhyParam, *pBssPhyParam; struct wmeParams *wmep; enum ieee80211_phymode mode; int i; IEEE80211_LOCK_ASSERT(ic); if ((ic->ic_caps & IEEE80211_C_WME) == 0 || ic->ic_nrunning > 1) return; /* * Clear the wme cap_info field so a qoscount from a previous * vap doesn't confuse later code which only parses the beacon * field and updates hardware when said field changes. * Otherwise the hardware is programmed with defaults, not what * the beacon actually announces. * * Note that we can't ever have 0xff as an actual value; * the only valid values are 0..15. */ wme->wme_wmeChanParams.cap_info = 0xfe; /* * Select mode; we can be called early in which case we * always use auto mode. We know we'll be called when * entering the RUN state with bsschan setup properly * so state will eventually get set correctly */ if (ic->ic_bsschan != IEEE80211_CHAN_ANYC) mode = ieee80211_chan2mode(ic->ic_bsschan); else mode = IEEE80211_MODE_AUTO; for (i = 0; i < WME_NUM_AC; i++) { switch (i) { case WME_AC_BK: pPhyParam = &phyParamForAC_BK[mode]; pBssPhyParam = &phyParamForAC_BK[mode]; break; case WME_AC_VI: pPhyParam = &phyParamForAC_VI[mode]; pBssPhyParam = &bssPhyParamForAC_VI[mode]; break; case WME_AC_VO: pPhyParam = &phyParamForAC_VO[mode]; pBssPhyParam = &bssPhyParamForAC_VO[mode]; break; case WME_AC_BE: default: pPhyParam = &phyParamForAC_BE[mode]; pBssPhyParam = &bssPhyParamForAC_BE[mode]; break; } wmep = &wme->wme_wmeChanParams.cap_wmeParams[i]; if (ic->ic_opmode == IEEE80211_M_HOSTAP) { setwmeparams(vap, "chan", i, wmep, pPhyParam); } else { setwmeparams(vap, "chan", i, wmep, pBssPhyParam); } wmep = &wme->wme_wmeBssChanParams.cap_wmeParams[i]; setwmeparams(vap, "bss ", i, wmep, pBssPhyParam); } /* NB: check ic_bss to avoid NULL deref on initial attach */ if (vap->iv_bss != NULL) { /* * Calculate aggressive mode switching threshold based * on beacon interval. This doesn't need locking since * we're only called before entering the RUN state at * which point we start sending beacon frames. */ wme->wme_hipri_switch_thresh = (HIGH_PRI_SWITCH_THRESH * vap->iv_bss->ni_intval) / 100; wme->wme_flags &= ~WME_F_AGGRMODE; ieee80211_wme_updateparams(vap); } } void ieee80211_wme_initparams(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); ieee80211_wme_initparams_locked(vap); IEEE80211_UNLOCK(ic); } /* * Update WME parameters for ourself and the BSS. */ void ieee80211_wme_updateparams_locked(struct ieee80211vap *vap) { static const paramType aggrParam[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = { 2, 4, 10, 64, 0 }, [IEEE80211_MODE_11A] = { 2, 4, 10, 64, 0 }, [IEEE80211_MODE_11B] = { 2, 5, 10, 64, 0 }, [IEEE80211_MODE_11G] = { 2, 4, 10, 64, 0 }, [IEEE80211_MODE_FH] = { 2, 5, 10, 64, 0 }, [IEEE80211_MODE_TURBO_A] = { 1, 3, 10, 64, 0 }, [IEEE80211_MODE_TURBO_G] = { 1, 3, 10, 64, 0 }, [IEEE80211_MODE_STURBO_A] = { 1, 3, 10, 64, 0 }, [IEEE80211_MODE_HALF] = { 2, 4, 10, 64, 0 }, [IEEE80211_MODE_QUARTER] = { 2, 4, 10, 64, 0 }, [IEEE80211_MODE_11NA] = { 2, 4, 10, 64, 0 }, /* XXXcheck*/ [IEEE80211_MODE_11NG] = { 2, 4, 10, 64, 0 }, /* XXXcheck*/ [IEEE80211_MODE_VHT_2GHZ] = { 2, 4, 10, 64, 0 }, /* XXXcheck*/ [IEEE80211_MODE_VHT_5GHZ] = { 2, 4, 10, 64, 0 }, /* XXXcheck*/ }; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_wme_state *wme = &ic->ic_wme; const struct wmeParams *wmep; struct wmeParams *chanp, *bssp; enum ieee80211_phymode mode; int i; int do_aggrmode = 0; /* * Set up the channel access parameters for the physical * device. First populate the configured settings. */ for (i = 0; i < WME_NUM_AC; i++) { chanp = &wme->wme_chanParams.cap_wmeParams[i]; wmep = &wme->wme_wmeChanParams.cap_wmeParams[i]; chanp->wmep_aifsn = wmep->wmep_aifsn; chanp->wmep_logcwmin = wmep->wmep_logcwmin; chanp->wmep_logcwmax = wmep->wmep_logcwmax; chanp->wmep_txopLimit = wmep->wmep_txopLimit; chanp = &wme->wme_bssChanParams.cap_wmeParams[i]; wmep = &wme->wme_wmeBssChanParams.cap_wmeParams[i]; chanp->wmep_aifsn = wmep->wmep_aifsn; chanp->wmep_logcwmin = wmep->wmep_logcwmin; chanp->wmep_logcwmax = wmep->wmep_logcwmax; chanp->wmep_txopLimit = wmep->wmep_txopLimit; } /* * Select mode; we can be called early in which case we * always use auto mode. We know we'll be called when * entering the RUN state with bsschan setup properly * so state will eventually get set correctly */ if (ic->ic_bsschan != IEEE80211_CHAN_ANYC) mode = ieee80211_chan2mode(ic->ic_bsschan); else mode = IEEE80211_MODE_AUTO; /* * This implements aggressive mode as found in certain * vendors' AP's. When there is significant high * priority (VI/VO) traffic in the BSS throttle back BE * traffic by using conservative parameters. Otherwise * BE uses aggressive params to optimize performance of * legacy/non-QoS traffic. */ /* Hostap? Only if aggressive mode is enabled */ if (vap->iv_opmode == IEEE80211_M_HOSTAP && (wme->wme_flags & WME_F_AGGRMODE) != 0) do_aggrmode = 1; /* * Station? Only if we're in a non-QoS BSS. */ else if ((vap->iv_opmode == IEEE80211_M_STA && (vap->iv_bss->ni_flags & IEEE80211_NODE_QOS) == 0)) do_aggrmode = 1; /* * IBSS? Only if we have WME enabled. */ else if ((vap->iv_opmode == IEEE80211_M_IBSS) && (vap->iv_flags & IEEE80211_F_WME)) do_aggrmode = 1; /* * If WME is disabled on this VAP, default to aggressive mode * regardless of the configuration. */ if ((vap->iv_flags & IEEE80211_F_WME) == 0) do_aggrmode = 1; /* XXX WDS? */ /* XXX MBSS? */ if (do_aggrmode) { chanp = &wme->wme_chanParams.cap_wmeParams[WME_AC_BE]; bssp = &wme->wme_bssChanParams.cap_wmeParams[WME_AC_BE]; chanp->wmep_aifsn = bssp->wmep_aifsn = aggrParam[mode].aifsn; chanp->wmep_logcwmin = bssp->wmep_logcwmin = aggrParam[mode].logcwmin; chanp->wmep_logcwmax = bssp->wmep_logcwmax = aggrParam[mode].logcwmax; chanp->wmep_txopLimit = bssp->wmep_txopLimit = (vap->iv_flags & IEEE80211_F_BURST) ? aggrParam[mode].txopLimit : 0; IEEE80211_DPRINTF(vap, IEEE80211_MSG_WME, "update %s (chan+bss) [acm %u aifsn %u logcwmin %u " "logcwmax %u txop %u]\n", ieee80211_wme_acnames[WME_AC_BE], chanp->wmep_acm, chanp->wmep_aifsn, chanp->wmep_logcwmin, chanp->wmep_logcwmax, chanp->wmep_txopLimit); } /* * Change the contention window based on the number of associated * stations. If the number of associated stations is 1 and * aggressive mode is enabled, lower the contention window even * further. */ if (vap->iv_opmode == IEEE80211_M_HOSTAP && vap->iv_sta_assoc < 2 && (wme->wme_flags & WME_F_AGGRMODE) != 0) { static const uint8_t logCwMin[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = 3, [IEEE80211_MODE_11A] = 3, [IEEE80211_MODE_11B] = 4, [IEEE80211_MODE_11G] = 3, [IEEE80211_MODE_FH] = 4, [IEEE80211_MODE_TURBO_A] = 3, [IEEE80211_MODE_TURBO_G] = 3, [IEEE80211_MODE_STURBO_A] = 3, [IEEE80211_MODE_HALF] = 3, [IEEE80211_MODE_QUARTER] = 3, [IEEE80211_MODE_11NA] = 3, [IEEE80211_MODE_11NG] = 3, [IEEE80211_MODE_VHT_2GHZ] = 3, [IEEE80211_MODE_VHT_5GHZ] = 3, }; chanp = &wme->wme_chanParams.cap_wmeParams[WME_AC_BE]; bssp = &wme->wme_bssChanParams.cap_wmeParams[WME_AC_BE]; chanp->wmep_logcwmin = bssp->wmep_logcwmin = logCwMin[mode]; IEEE80211_DPRINTF(vap, IEEE80211_MSG_WME, "update %s (chan+bss) logcwmin %u\n", ieee80211_wme_acnames[WME_AC_BE], chanp->wmep_logcwmin); } /* schedule the deferred WME update */ ieee80211_runtask(ic, &vap->iv_wme_task); IEEE80211_DPRINTF(vap, IEEE80211_MSG_WME, "%s: WME params updated, cap_info 0x%x\n", __func__, vap->iv_opmode == IEEE80211_M_STA ? wme->wme_wmeChanParams.cap_info : wme->wme_bssChanParams.cap_info); } void ieee80211_wme_updateparams(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; if (ic->ic_caps & IEEE80211_C_WME) { IEEE80211_LOCK(ic); ieee80211_wme_updateparams_locked(vap); IEEE80211_UNLOCK(ic); } } /* * Fetch the WME parameters for the given VAP. * * When net80211 grows p2p, etc support, this may return different * parameters for each VAP. */ void ieee80211_wme_vap_getparams(struct ieee80211vap *vap, struct chanAccParams *wp) { memcpy(wp, &vap->iv_ic->ic_wme.wme_chanParams, sizeof(*wp)); } /* * For NICs which only support one set of WME parameters (ie, softmac NICs) * there may be different VAP WME parameters but only one is "active". * This returns the "NIC" WME parameters for the currently active * context. */ void ieee80211_wme_ic_getparams(struct ieee80211com *ic, struct chanAccParams *wp) { memcpy(wp, &ic->ic_wme.wme_chanParams, sizeof(*wp)); } /* * Return whether to use QoS on a given WME queue. * * This is intended to be called from the transmit path of softmac drivers * which are setting NoAck bits in transmit descriptors. * * Ideally this would be set in some transmit field before the packet is * queued to the driver but net80211 isn't quite there yet. */ int ieee80211_wme_vap_ac_is_noack(struct ieee80211vap *vap, int ac) { /* Bounds/sanity check */ if (ac < 0 || ac >= WME_NUM_AC) return (0); /* Again, there's only one global context for now */ return (!! vap->iv_ic->ic_wme.wme_chanParams.cap_wmeParams[ac].wmep_noackPolicy); } static void parent_updown(void *arg, int npending) { struct ieee80211com *ic = arg; ic->ic_parent(ic); } static void update_mcast(void *arg, int npending) { struct ieee80211com *ic = arg; ic->ic_update_mcast(ic); } static void update_promisc(void *arg, int npending) { struct ieee80211com *ic = arg; ic->ic_update_promisc(ic); } static void update_channel(void *arg, int npending) { struct ieee80211com *ic = arg; ic->ic_set_channel(ic); ieee80211_radiotap_chan_change(ic); } static void update_chw(void *arg, int npending) { struct ieee80211com *ic = arg; /* * XXX should we defer the channel width _config_ update until now? */ ic->ic_update_chw(ic); } /* * Deferred WME parameter and beacon update. * * In preparation for per-VAP WME configuration, call the VAP * method if the VAP requires it. Otherwise, just call the * older global method. There isn't a per-VAP WME configuration * just yet so for now just use the global configuration. */ static void vap_update_wme(void *arg, int npending) { struct ieee80211vap *vap = arg; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_wme_state *wme = &ic->ic_wme; /* Driver update */ if (vap->iv_wme_update != NULL) vap->iv_wme_update(vap, ic->ic_wme.wme_chanParams.cap_wmeParams); else ic->ic_wme.wme_update(ic); IEEE80211_LOCK(ic); /* * Arrange for the beacon update. * * XXX what about MBSS, WDS? */ if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_IBSS) { /* * Arrange for a beacon update and bump the parameter * set number so associated stations load the new values. */ wme->wme_bssChanParams.cap_info = (wme->wme_bssChanParams.cap_info+1) & WME_QOSINFO_COUNT; ieee80211_beacon_notify(vap, IEEE80211_BEACON_WME); } IEEE80211_UNLOCK(ic); } static void restart_vaps(void *arg, int npending) { struct ieee80211com *ic = arg; ieee80211_suspend_all(ic); ieee80211_resume_all(ic); } /* * Block until the parent is in a known state. This is * used after any operations that dispatch a task (e.g. * to auto-configure the parent device up/down). */ void ieee80211_waitfor_parent(struct ieee80211com *ic) { taskqueue_block(ic->ic_tq); ieee80211_draintask(ic, &ic->ic_parent_task); ieee80211_draintask(ic, &ic->ic_mcast_task); ieee80211_draintask(ic, &ic->ic_promisc_task); ieee80211_draintask(ic, &ic->ic_chan_task); ieee80211_draintask(ic, &ic->ic_bmiss_task); ieee80211_draintask(ic, &ic->ic_chw_task); taskqueue_unblock(ic->ic_tq); } /* * Check to see whether the current channel needs reset. * * Some devices don't handle being given an invalid channel * in their operating mode very well (eg wpi(4) will throw a * firmware exception.) * * Return 0 if we're ok, 1 if the channel needs to be reset. * * See PR kern/202502. */ static int ieee80211_start_check_reset_chan(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; if ((vap->iv_opmode == IEEE80211_M_IBSS && IEEE80211_IS_CHAN_NOADHOC(ic->ic_curchan)) || (vap->iv_opmode == IEEE80211_M_HOSTAP && IEEE80211_IS_CHAN_NOHOSTAP(ic->ic_curchan))) return (1); return (0); } /* * Reset the curchan to a known good state. */ static void ieee80211_start_reset_chan(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; ic->ic_curchan = &ic->ic_channels[0]; } /* * Start a vap running. If this is the first vap to be * set running on the underlying device then we * automatically bring the device up. */ void ieee80211_start_locked(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK_ASSERT(ic); IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "start running, %d vaps running\n", ic->ic_nrunning); if (!ieee80211_vap_ifp_check_is_running(vap)) { /* * Mark us running. Note that it's ok to do this first; * if we need to bring the parent device up we defer that * to avoid dropping the com lock. We expect the device * to respond to being marked up by calling back into us * through ieee80211_start_all at which point we'll come * back in here and complete the work. */ ieee80211_vap_ifp_set_running_state(vap, true); ieee80211_notify_ifnet_change(vap, IFF_DRV_RUNNING); /* * We are not running; if this we are the first vap * to be brought up auto-up the parent if necessary. */ if (ic->ic_nrunning++ == 0) { /* reset the channel to a known good channel */ if (ieee80211_start_check_reset_chan(vap)) ieee80211_start_reset_chan(vap); IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "%s: up parent %s\n", __func__, ic->ic_name); ieee80211_runtask(ic, &ic->ic_parent_task); return; } } /* * If the parent is up and running, then kick the * 802.11 state machine as appropriate. */ if (vap->iv_roaming != IEEE80211_ROAMING_MANUAL) { if (vap->iv_opmode == IEEE80211_M_STA) { #if 0 /* XXX bypasses scan too easily; disable for now */ /* * Try to be intelligent about clocking the state * machine. If we're currently in RUN state then * we should be able to apply any new state/parameters * simply by re-associating. Otherwise we need to * re-scan to select an appropriate ap. */ if (vap->iv_state >= IEEE80211_S_RUN) ieee80211_new_state_locked(vap, IEEE80211_S_ASSOC, 1); else #endif ieee80211_new_state_locked(vap, IEEE80211_S_SCAN, 0); } else { /* * For monitor+wds mode there's nothing to do but * start running. Otherwise if this is the first * vap to be brought up, start a scan which may be * preempted if the station is locked to a particular * channel. */ vap->iv_flags_ext |= IEEE80211_FEXT_REINIT; if (vap->iv_opmode == IEEE80211_M_MONITOR || vap->iv_opmode == IEEE80211_M_WDS) ieee80211_new_state_locked(vap, IEEE80211_S_RUN, -1); else ieee80211_new_state_locked(vap, IEEE80211_S_SCAN, 0); } } } /* * Start a single vap. */ void ieee80211_init(void *arg) { struct ieee80211vap *vap = arg; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "%s\n", __func__); IEEE80211_LOCK(vap->iv_ic); ieee80211_start_locked(vap); IEEE80211_UNLOCK(vap->iv_ic); } /* * Start all runnable vap's on a device. */ void ieee80211_start_all(struct ieee80211com *ic) { struct ieee80211vap *vap; IEEE80211_LOCK(ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { struct ifnet *ifp = vap->iv_ifp; if (IFNET_IS_UP_RUNNING(ifp)) /* NB: avoid recursion */ ieee80211_start_locked(vap); } IEEE80211_UNLOCK(ic); } /* * Stop a vap. We force it down using the state machine * then mark it's ifnet not running. If this is the last * vap running on the underlying device then we close it * too to insure it will be properly initialized when the * next vap is brought up. */ void ieee80211_stop_locked(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK_ASSERT(ic); IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "stop running, %d vaps running\n", ic->ic_nrunning); ieee80211_new_state_locked(vap, IEEE80211_S_INIT, -1); if (ieee80211_vap_ifp_check_is_running(vap)) { /* mark us stopped */ ieee80211_vap_ifp_set_running_state(vap, false); ieee80211_notify_ifnet_change(vap, IFF_DRV_RUNNING); if (--ic->ic_nrunning == 0) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "down parent %s\n", ic->ic_name); ieee80211_runtask(ic, &ic->ic_parent_task); } } } void ieee80211_stop(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); ieee80211_stop_locked(vap); IEEE80211_UNLOCK(ic); } /* * Stop all vap's running on a device. */ void ieee80211_stop_all(struct ieee80211com *ic) { struct ieee80211vap *vap; IEEE80211_LOCK(ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { struct ifnet *ifp = vap->iv_ifp; if (IFNET_IS_UP_RUNNING(ifp)) /* NB: avoid recursion */ ieee80211_stop_locked(vap); } IEEE80211_UNLOCK(ic); ieee80211_waitfor_parent(ic); } /* * Stop all vap's running on a device and arrange * for those that were running to be resumed. */ void ieee80211_suspend_all(struct ieee80211com *ic) { struct ieee80211vap *vap; IEEE80211_LOCK(ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { struct ifnet *ifp = vap->iv_ifp; if (IFNET_IS_UP_RUNNING(ifp)) { /* NB: avoid recursion */ vap->iv_flags_ext |= IEEE80211_FEXT_RESUME; ieee80211_stop_locked(vap); } } IEEE80211_UNLOCK(ic); ieee80211_waitfor_parent(ic); } /* * Start all vap's marked for resume. */ void ieee80211_resume_all(struct ieee80211com *ic) { struct ieee80211vap *vap; IEEE80211_LOCK(ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { struct ifnet *ifp = vap->iv_ifp; if (!IFNET_IS_UP_RUNNING(ifp) && (vap->iv_flags_ext & IEEE80211_FEXT_RESUME)) { vap->iv_flags_ext &= ~IEEE80211_FEXT_RESUME; ieee80211_start_locked(vap); } } IEEE80211_UNLOCK(ic); } /* * Restart all vap's running on a device. */ void ieee80211_restart_all(struct ieee80211com *ic) { /* * NB: do not use ieee80211_runtask here, we will * block & drain net80211 taskqueue. */ taskqueue_enqueue(taskqueue_thread, &ic->ic_restart_task); } void ieee80211_beacon_miss(struct ieee80211com *ic) { IEEE80211_LOCK(ic); if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { /* Process in a taskq, the handler may reenter the driver */ ieee80211_runtask(ic, &ic->ic_bmiss_task); } IEEE80211_UNLOCK(ic); } static void beacon_miss(void *arg, int npending) { struct ieee80211com *ic = arg; struct ieee80211vap *vap; IEEE80211_LOCK(ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { /* * We only pass events through for sta vap's in RUN+ state; * may be too restrictive but for now this saves all the * handlers duplicating these checks. */ if (vap->iv_opmode == IEEE80211_M_STA && vap->iv_state >= IEEE80211_S_RUN && vap->iv_bmiss != NULL) vap->iv_bmiss(vap); } IEEE80211_UNLOCK(ic); } static void beacon_swmiss(void *arg, int npending) { struct ieee80211vap *vap = arg; struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); if (vap->iv_state >= IEEE80211_S_RUN) { /* XXX Call multiple times if npending > zero? */ vap->iv_bmiss(vap); } IEEE80211_UNLOCK(ic); } /* * Software beacon miss handling. Check if any beacons * were received in the last period. If not post a * beacon miss; otherwise reset the counter. */ void ieee80211_swbmiss(void *arg) { struct ieee80211vap *vap = arg; struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK_ASSERT(ic); KASSERT(vap->iv_state >= IEEE80211_S_RUN, ("wrong state %d", vap->iv_state)); if (ic->ic_flags & IEEE80211_F_SCAN) { /* * If scanning just ignore and reset state. If we get a * bmiss after coming out of scan because we haven't had * time to receive a beacon then we should probe the AP * before posting a real bmiss (unless iv_bmiss_max has * been artifiically lowered). A cleaner solution might * be to disable the timer on scan start/end but to handle * case of multiple sta vap's we'd need to disable the * timers of all affected vap's. */ vap->iv_swbmiss_count = 0; } else if (vap->iv_swbmiss_count == 0) { if (vap->iv_bmiss != NULL) ieee80211_runtask(ic, &vap->iv_swbmiss_task); } else vap->iv_swbmiss_count = 0; callout_reset(&vap->iv_swbmiss, vap->iv_swbmiss_period, ieee80211_swbmiss, vap); } /* * Start an 802.11h channel switch. We record the parameters, * mark the operation pending, notify each vap through the * beacon update mechanism so it can update the beacon frame * contents, and then switch vap's to CSA state to block outbound * traffic. Devices that handle CSA directly can use the state * switch to do the right thing so long as they call * ieee80211_csa_completeswitch when it's time to complete the * channel change. Devices that depend on the net80211 layer can * use ieee80211_beacon_update to handle the countdown and the * channel switch. */ void ieee80211_csa_startswitch(struct ieee80211com *ic, struct ieee80211_channel *c, int mode, int count) { struct ieee80211vap *vap; IEEE80211_LOCK_ASSERT(ic); ic->ic_csa_newchan = c; ic->ic_csa_mode = mode; ic->ic_csa_count = count; ic->ic_flags |= IEEE80211_F_CSAPENDING; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_beacon_notify(vap, IEEE80211_BEACON_CSA); /* switch to CSA state to block outbound traffic */ if (vap->iv_state == IEEE80211_S_RUN) ieee80211_new_state_locked(vap, IEEE80211_S_CSA, 0); } ieee80211_notify_csa(ic, c, mode, count); } /* * Complete the channel switch by transitioning all CSA VAPs to RUN. * This is called by both the completion and cancellation functions * so each VAP is placed back in the RUN state and can thus transmit. */ static void csa_completeswitch(struct ieee80211com *ic) { struct ieee80211vap *vap; ic->ic_csa_newchan = NULL; ic->ic_flags &= ~IEEE80211_F_CSAPENDING; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_state == IEEE80211_S_CSA) ieee80211_new_state_locked(vap, IEEE80211_S_RUN, 0); } /* * Complete an 802.11h channel switch started by ieee80211_csa_startswitch. * We clear state and move all vap's in CSA state to RUN state * so they can again transmit. * * Although this may not be completely correct, update the BSS channel * for each VAP to the newly configured channel. The setcurchan sets * the current operating channel for the interface (so the radio does * switch over) but the VAP BSS isn't updated, leading to incorrectly * reported information via ioctl. */ void ieee80211_csa_completeswitch(struct ieee80211com *ic) { struct ieee80211vap *vap; IEEE80211_LOCK_ASSERT(ic); KASSERT(ic->ic_flags & IEEE80211_F_CSAPENDING, ("csa not pending")); ieee80211_setcurchan(ic, ic->ic_csa_newchan); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_state == IEEE80211_S_CSA) vap->iv_bss->ni_chan = ic->ic_curchan; csa_completeswitch(ic); } /* * Cancel an 802.11h channel switch started by ieee80211_csa_startswitch. * We clear state and move all vap's in CSA state to RUN state * so they can again transmit. */ void ieee80211_csa_cancelswitch(struct ieee80211com *ic) { IEEE80211_LOCK_ASSERT(ic); csa_completeswitch(ic); } /* * Complete a DFS CAC started by ieee80211_dfs_cac_start. * We clear state and move all vap's in CAC state to RUN state. */ void ieee80211_cac_completeswitch(struct ieee80211vap *vap0) { struct ieee80211com *ic = vap0->iv_ic; struct ieee80211vap *vap; IEEE80211_LOCK(ic); /* * Complete CAC state change for lead vap first; then * clock all the other vap's waiting. */ KASSERT(vap0->iv_state == IEEE80211_S_CAC, ("wrong state %d", vap0->iv_state)); ieee80211_new_state_locked(vap0, IEEE80211_S_RUN, 0); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_state == IEEE80211_S_CAC && vap != vap0) ieee80211_new_state_locked(vap, IEEE80211_S_RUN, 0); IEEE80211_UNLOCK(ic); } /* * Force all vap's other than the specified vap to the INIT state * and mark them as waiting for a scan to complete. These vaps * will be brought up when the scan completes and the scanning vap * reaches RUN state by wakeupwaiting. */ static void markwaiting(struct ieee80211vap *vap0) { struct ieee80211com *ic = vap0->iv_ic; struct ieee80211vap *vap; IEEE80211_LOCK_ASSERT(ic); /* * A vap list entry can not disappear since we are running on the * taskqueue and a vap destroy will queue and drain another state * change task. */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { if (vap == vap0) continue; if (vap->iv_state != IEEE80211_S_INIT) { /* NB: iv_newstate may drop the lock */ vap->iv_newstate(vap, IEEE80211_S_INIT, 0); IEEE80211_LOCK_ASSERT(ic); vap->iv_flags_ext |= IEEE80211_FEXT_SCANWAIT; } } } /* * Wakeup all vap's waiting for a scan to complete. This is the * companion to markwaiting (above) and is used to coordinate * multiple vaps scanning. * This is called from the state taskqueue. */ static void wakeupwaiting(struct ieee80211vap *vap0) { struct ieee80211com *ic = vap0->iv_ic; struct ieee80211vap *vap; IEEE80211_LOCK_ASSERT(ic); /* * A vap list entry can not disappear since we are running on the * taskqueue and a vap destroy will queue and drain another state * change task. */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { if (vap == vap0) continue; if (vap->iv_flags_ext & IEEE80211_FEXT_SCANWAIT) { vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANWAIT; /* NB: sta's cannot go INIT->RUN */ /* NB: iv_newstate may drop the lock */ /* * This is problematic if the interface has OACTIVE * set. Only the deferred ieee80211_newstate_cb() * will end up actually /clearing/ the OACTIVE * flag on a state transition to RUN from a non-RUN * state. * * But, we're not actually deferring this callback; * and when the deferred call occurs it shows up as * a RUN->RUN transition! So the flag isn't/wasn't * cleared! * * I'm also not sure if it's correct to actually * do the transitions here fully through the deferred * paths either as other things can be invoked as * part of that state machine. * * So just keep this in mind when looking at what * the markwaiting/wakeupwaiting routines are doing * and how they invoke vap state changes. */ vap->iv_newstate(vap, vap->iv_opmode == IEEE80211_M_STA ? IEEE80211_S_SCAN : IEEE80211_S_RUN, 0); IEEE80211_LOCK_ASSERT(ic); } } } static int _ieee80211_newstate_get_next_empty_slot(struct ieee80211vap *vap) { int nstate_num; IEEE80211_LOCK_ASSERT(vap->iv_ic); if (vap->iv_nstate_n >= NET80211_IV_NSTATE_NUM) return (-1); nstate_num = vap->iv_nstate_b + vap->iv_nstate_n; nstate_num %= NET80211_IV_NSTATE_NUM; vap->iv_nstate_n++; return (nstate_num); } static int _ieee80211_newstate_get_next_pending_slot(struct ieee80211vap *vap) { int nstate_num; IEEE80211_LOCK_ASSERT(vap->iv_ic); KASSERT(vap->iv_nstate_n > 0, ("%s: vap %p iv_nstate_n %d\n", __func__, vap, vap->iv_nstate_n)); nstate_num = vap->iv_nstate_b; vap->iv_nstate_b++; if (vap->iv_nstate_b >= NET80211_IV_NSTATE_NUM) vap->iv_nstate_b = 0; vap->iv_nstate_n--; return (nstate_num); } static int _ieee80211_newstate_get_npending(struct ieee80211vap *vap) { IEEE80211_LOCK_ASSERT(vap->iv_ic); return (vap->iv_nstate_n); } /* * Handle post state change work common to all operating modes. */ static void ieee80211_newstate_cb(void *xvap, int npending) { struct ieee80211vap *vap = xvap; struct ieee80211com *ic = vap->iv_ic; enum ieee80211_state nstate, ostate; int arg, rc, nstate_num; KASSERT(npending == 1, ("%s: vap %p with npending %d != 1\n", __func__, vap, npending)); IEEE80211_LOCK(ic); nstate_num = _ieee80211_newstate_get_next_pending_slot(vap); /* * Update the historic fields for now as they are used in some * drivers and reduce code changes for now. */ vap->iv_nstate = nstate = vap->iv_nstates[nstate_num]; arg = vap->iv_nstate_args[nstate_num]; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s:%d: running state update %s -> %s (%d)\n", __func__, __LINE__, ieee80211_state_name[vap->iv_state], ieee80211_state_name[nstate], npending); if (vap->iv_flags_ext & IEEE80211_FEXT_REINIT) { /* * We have been requested to drop back to the INIT before * proceeding to the new state. */ /* Deny any state changes while we are here. */ vap->iv_nstate = IEEE80211_S_INIT; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s arg %d -> %s arg %d\n", __func__, ieee80211_state_name[vap->iv_state], ieee80211_state_name[vap->iv_nstate], 0, ieee80211_state_name[nstate], arg); vap->iv_newstate(vap, vap->iv_nstate, 0); IEEE80211_LOCK_ASSERT(ic); vap->iv_flags_ext &= ~(IEEE80211_FEXT_REINIT | IEEE80211_FEXT_STATEWAIT); /* enqueue new state transition after cancel_scan() task */ ieee80211_new_state_locked(vap, nstate, arg); goto done; } ostate = vap->iv_state; if (nstate == IEEE80211_S_SCAN && ostate != IEEE80211_S_INIT) { /* * SCAN was forced; e.g. on beacon miss. Force other running * vap's to INIT state and mark them as waiting for the scan to * complete. This insures they don't interfere with our * scanning. Since we are single threaded the vaps can not * transition again while we are executing. * * XXX not always right, assumes ap follows sta */ markwaiting(vap); } IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s arg %d\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg); rc = vap->iv_newstate(vap, nstate, arg); IEEE80211_LOCK_ASSERT(ic); vap->iv_flags_ext &= ~IEEE80211_FEXT_STATEWAIT; if (rc != 0) { /* State transition failed */ KASSERT(rc != EINPROGRESS, ("iv_newstate was deferred")); KASSERT(nstate != IEEE80211_S_INIT, ("INIT state change failed")); IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s returned error %d\n", __func__, ieee80211_state_name[nstate], rc); goto done; } /* * Handle the case of a RUN->RUN transition occuring when STA + AP * VAPs occur on the same radio. * * The mark and wakeup waiting routines call iv_newstate() directly, * but they do not end up deferring state changes here. * Thus, although the VAP newstate method sees a transition * of RUN->INIT->RUN, the deferred path here only sees a RUN->RUN * transition. If OACTIVE is set then it is never cleared. * * So, if we're here and the state is RUN, just clear OACTIVE. * At some point if the markwaiting/wakeupwaiting paths end up * also invoking the deferred state updates then this will * be no-op code - and also if OACTIVE is finally retired, it'll * also be no-op code. */ if (nstate == IEEE80211_S_RUN) { /* * OACTIVE may be set on the vap if the upper layer * tried to transmit (e.g. IPv6 NDP) before we reach * RUN state. Clear it and restart xmit. * * Note this can also happen as a result of SLEEP->RUN * (i.e. coming out of power save mode). * * Historically this was done only for a state change * but is needed earlier; see next comment. The 2nd half * of the work is still only done in case of an actual * state change below. */ /* * Unblock the VAP queue; a RUN->RUN state can happen * on a STA+AP setup on the AP vap. See wakeupwaiting(). */ vap->iv_ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; /* * XXX TODO Kick-start a VAP queue - this should be a method! */ } /* No actual transition, skip post processing */ if (ostate == nstate) goto done; if (nstate == IEEE80211_S_RUN) { /* bring up any vaps waiting on us */ wakeupwaiting(vap); } else if (nstate == IEEE80211_S_INIT) { /* * Flush the scan cache if we did the last scan (XXX?) * and flush any frames on send queues from this vap. * Note the mgt q is used only for legacy drivers and * will go away shortly. */ ieee80211_scan_flush(vap); /* * XXX TODO: ic/vap queue flush */ } done: IEEE80211_UNLOCK(ic); } /* * Public interface for initiating a state machine change. * This routine single-threads the request and coordinates * the scheduling of multiple vaps for the purpose of selecting * an operating channel. Specifically the following scenarios * are handled: * o only one vap can be selecting a channel so on transition to * SCAN state if another vap is already scanning then * mark the caller for later processing and return without * doing anything (XXX? expectations by caller of synchronous operation) * o only one vap can be doing CAC of a channel so on transition to * CAC state if another vap is already scanning for radar then * mark the caller for later processing and return without * doing anything (XXX? expectations by caller of synchronous operation) * o if another vap is already running when a request is made * to SCAN then an operating channel has been chosen; bypass * the scan and just join the channel * * Note that the state change call is done through the iv_newstate * method pointer so any driver routine gets invoked. The driver * will normally call back into operating mode-specific * ieee80211_newstate routines (below) unless it needs to completely * bypass the state machine (e.g. because the firmware has it's * own idea how things should work). Bypassing the net80211 layer * is usually a mistake and indicates lack of proper integration * with the net80211 layer. */ int ieee80211_new_state_locked(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211vap *vp; enum ieee80211_state ostate; int nrunning, nscanning, nstate_num; IEEE80211_LOCK_ASSERT(ic); if (vap->iv_flags_ext & IEEE80211_FEXT_STATEWAIT) { if (vap->iv_nstate == IEEE80211_S_INIT || ((vap->iv_state == IEEE80211_S_INIT || (vap->iv_flags_ext & IEEE80211_FEXT_REINIT)) && vap->iv_nstate == IEEE80211_S_SCAN && nstate > IEEE80211_S_SCAN)) { /* * XXX The vap is being stopped/started, * do not allow any other state changes * until this is completed. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s:%d: %s -> %s (%s) transition discarded\n", __func__, __LINE__, ieee80211_state_name[vap->iv_state], ieee80211_state_name[nstate], ieee80211_state_name[vap->iv_nstate]); return -1; } } IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s:%d: starting state update %s -> %s (%s)\n", __func__, __LINE__, ieee80211_state_name[vap->iv_state], ieee80211_state_name[vap->iv_nstate], ieee80211_state_name[nstate]); nrunning = nscanning = 0; /* XXX can track this state instead of calculating */ TAILQ_FOREACH(vp, &ic->ic_vaps, iv_next) { if (vp != vap) { if (vp->iv_state >= IEEE80211_S_RUN) nrunning++; /* XXX doesn't handle bg scan */ /* NB: CAC+AUTH+ASSOC treated like SCAN */ else if (vp->iv_state > IEEE80211_S_INIT) nscanning++; } } /* * Look ahead for the "old state" at that point when the last queued * state transition is run. */ if (vap->iv_nstate_n == 0) { ostate = vap->iv_state; } else { nstate_num = (vap->iv_nstate_b + vap->iv_nstate_n - 1) % NET80211_IV_NSTATE_NUM; ostate = vap->iv_nstates[nstate_num]; } IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (arg %d) (nrunning %d nscanning %d)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg, nrunning, nscanning); switch (nstate) { case IEEE80211_S_SCAN: if (ostate == IEEE80211_S_INIT) { /* * INIT -> SCAN happens on initial bringup. */ KASSERT(!(nscanning && nrunning), ("%d scanning and %d running", nscanning, nrunning)); if (nscanning) { /* * Someone is scanning, defer our state * change until the work has completed. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: defer %s -> %s\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); vap->iv_flags_ext |= IEEE80211_FEXT_SCANWAIT; return 0; } if (nrunning) { /* * Someone is operating; just join the channel * they have chosen. */ /* XXX kill arg? */ /* XXX check each opmode, adhoc? */ if (vap->iv_opmode == IEEE80211_M_STA) nstate = IEEE80211_S_SCAN; else nstate = IEEE80211_S_RUN; #ifdef IEEE80211_DEBUG if (nstate != IEEE80211_S_SCAN) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: override, now %s -> %s\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); } #endif } } break; case IEEE80211_S_RUN: if (vap->iv_opmode == IEEE80211_M_WDS && (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY) && nscanning) { /* * Legacy WDS with someone else scanning; don't * go online until that completes as we should * follow the other vap to the channel they choose. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: defer %s -> %s (legacy WDS)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); vap->iv_flags_ext |= IEEE80211_FEXT_SCANWAIT; return 0; } if (vap->iv_opmode == IEEE80211_M_HOSTAP && IEEE80211_IS_CHAN_DFS(ic->ic_bsschan) && (vap->iv_flags_ext & IEEE80211_FEXT_DFS) && !IEEE80211_IS_CHAN_CACDONE(ic->ic_bsschan)) { /* * This is a DFS channel, transition to CAC state * instead of RUN. This allows us to initiate * Channel Availability Check (CAC) as specified * by 11h/DFS. */ nstate = IEEE80211_S_CAC; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: override %s -> %s (DFS)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); } break; case IEEE80211_S_INIT: /* cancel any scan in progress */ ieee80211_cancel_scan(vap); if (ostate == IEEE80211_S_INIT ) { /* XXX don't believe this */ /* INIT -> INIT. nothing to do */ vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANWAIT; } /* fall thru... */ default: break; } /* * Defer the state change to a thread. * We support up-to NET80211_IV_NSTATE_NUM pending state changes * using a separate task for each. Otherwise, if we enqueue * more than one state change they will be folded together, * npedning will be > 1 and we may run then out of sequence with * other events. * This is kind-of a hack after 10 years but we know how to provoke * these cases now (and seen them in the wild). */ nstate_num = _ieee80211_newstate_get_next_empty_slot(vap); if (nstate_num == -1) { /* * This is really bad and we should just go kaboom. * Instead drop it. No one checks the return code anyway. */ ic_printf(ic, "%s:%d: pending %s -> %s (now to %s) " "transition lost. %d/%d pending state changes:\n", __func__, __LINE__, ieee80211_state_name[vap->iv_state], ieee80211_state_name[vap->iv_nstate], ieee80211_state_name[nstate], _ieee80211_newstate_get_npending(vap), NET80211_IV_NSTATE_NUM); return (EAGAIN); } vap->iv_nstates[nstate_num] = nstate; vap->iv_nstate_args[nstate_num] = arg; vap->iv_flags_ext |= IEEE80211_FEXT_STATEWAIT; ieee80211_runtask(ic, &vap->iv_nstate_task[nstate_num]); return EINPROGRESS; } int ieee80211_new_state(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic = vap->iv_ic; int rc; IEEE80211_LOCK(ic); rc = ieee80211_new_state_locked(vap, nstate, arg); IEEE80211_UNLOCK(ic); return rc; } diff --git a/sys/net80211/ieee80211_radiotap.c b/sys/net80211/ieee80211_radiotap.c index 79e00106a780..e79aba550f50 100644 --- a/sys/net80211/ieee80211_radiotap.c +++ b/sys/net80211/ieee80211_radiotap.c @@ -1,374 +1,374 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2009 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 /* * IEEE 802.11 radiotap support. */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include static int radiotap_offset(struct ieee80211_radiotap_header *, int, int); void ieee80211_radiotap_attach(struct ieee80211com *ic, struct ieee80211_radiotap_header *th, int tlen, uint32_t tx_radiotap, struct ieee80211_radiotap_header *rh, int rlen, uint32_t rx_radiotap) { ieee80211_radiotap_attachv(ic, th, tlen, 0, tx_radiotap, rh, rlen, 0, rx_radiotap); } void ieee80211_radiotap_attachv(struct ieee80211com *ic, struct ieee80211_radiotap_header *th, int tlen, int n_tx_v, uint32_t tx_radiotap, struct ieee80211_radiotap_header *rh, int rlen, int n_rx_v, uint32_t rx_radiotap) { #define B(_v) (1<<(_v)) int off; th->it_len = htole16(roundup2(tlen, sizeof(uint32_t))); th->it_present = htole32(tx_radiotap); ic->ic_th = th; /* calculate offset to channel data */ off = -1; if (tx_radiotap & B(IEEE80211_RADIOTAP_CHANNEL)) off = radiotap_offset(th, n_tx_v, IEEE80211_RADIOTAP_CHANNEL); else if (tx_radiotap & B(IEEE80211_RADIOTAP_XCHANNEL)) off = radiotap_offset(th, n_tx_v, IEEE80211_RADIOTAP_XCHANNEL); if (off == -1) { ic_printf(ic, "%s: no tx channel, radiotap 0x%x\n", __func__, tx_radiotap); /* NB: we handle this case but data will have no chan spec */ } else ic->ic_txchan = ((uint8_t *) th) + off; rh->it_len = htole16(roundup2(rlen, sizeof(uint32_t))); rh->it_present = htole32(rx_radiotap); ic->ic_rh = rh; /* calculate offset to channel data */ off = -1; if (rx_radiotap & B(IEEE80211_RADIOTAP_CHANNEL)) off = radiotap_offset(rh, n_rx_v, IEEE80211_RADIOTAP_CHANNEL); else if (rx_radiotap & B(IEEE80211_RADIOTAP_XCHANNEL)) off = radiotap_offset(rh, n_rx_v, IEEE80211_RADIOTAP_XCHANNEL); if (off == -1) { ic_printf(ic, "%s: no rx channel, radiotap 0x%x\n", __func__, rx_radiotap); /* NB: we handle this case but data will have no chan spec */ } else ic->ic_rxchan = ((uint8_t *) rh) + off; #undef B } void ieee80211_radiotap_detach(struct ieee80211com *ic) { } void ieee80211_radiotap_vattach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_radiotap_header *th = ic->ic_th; if (th != NULL && ic->ic_rh != NULL) { /* radiotap DLT for raw 802.11 frames */ bpfattach2(vap->iv_ifp, DLT_IEEE802_11_RADIO, sizeof(struct ieee80211_frame) + le16toh(th->it_len), &vap->iv_rawbpf); } } void ieee80211_radiotap_vdetach(struct ieee80211vap *vap) { /* NB: bpfdetach is called by ether_ifdetach and claims all taps */ } static void set_channel(void *p, const struct ieee80211_channel *c) { struct { uint16_t freq; uint16_t flags; } *rc = p; rc->freq = htole16(c->ic_freq); rc->flags = htole16(c->ic_flags); } static void set_xchannel(void *p, const struct ieee80211_channel *c) { struct { uint32_t flags; uint16_t freq; uint8_t ieee; uint8_t maxpow; } *rc = p; rc->flags = htole32(c->ic_flags); rc->freq = htole16(c->ic_freq); rc->ieee = c->ic_ieee; rc->maxpow = c->ic_maxregpower; } /* * Update radiotap state on channel change. */ void ieee80211_radiotap_chan_change(struct ieee80211com *ic) { if (ic->ic_rxchan != NULL) { struct ieee80211_radiotap_header *rh = ic->ic_rh; if (rh->it_present & htole32(1<ic_rxchan, ic->ic_curchan); else if (rh->it_present & htole32(1<ic_rxchan, ic->ic_curchan); } if (ic->ic_txchan != NULL) { struct ieee80211_radiotap_header *th = ic->ic_th; if (th->it_present & htole32(1<ic_txchan, ic->ic_curchan); else if (th->it_present & htole32(1<ic_txchan, ic->ic_curchan); } } /* * Distribute radiotap data (+packet) to all monitor mode * vaps with an active tap other than vap0. */ static void spam_vaps(struct ieee80211vap *vap0, struct mbuf *m, struct ieee80211_radiotap_header *rh, int len) { struct ieee80211com *ic = vap0->iv_ic; struct ieee80211vap *vap; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { if (vap != vap0 && vap->iv_opmode == IEEE80211_M_MONITOR && (vap->iv_flags_ext & IEEE80211_FEXT_BPF) && vap->iv_state != IEEE80211_S_INIT) bpf_mtap2(vap->iv_rawbpf, rh, len, m); } } /* * Dispatch radiotap data for transmitted packet. */ void ieee80211_radiotap_tx(struct ieee80211vap *vap0, struct mbuf *m) { struct ieee80211com *ic = vap0->iv_ic; struct ieee80211_radiotap_header *th = ic->ic_th; int len; KASSERT(th != NULL, ("no tx radiotap header")); len = le16toh(th->it_len); if (vap0->iv_flags_ext & IEEE80211_FEXT_BPF) bpf_mtap2(vap0->iv_rawbpf, th, len, m); /* * Spam monitor mode vaps. */ if (ic->ic_montaps != 0) spam_vaps(vap0, m, th, len); } /* * Dispatch radiotap data for received packet. */ void ieee80211_radiotap_rx(struct ieee80211vap *vap0, struct mbuf *m) { struct ieee80211com *ic = vap0->iv_ic; struct ieee80211_radiotap_header *rh = ic->ic_rh; int len; KASSERT(rh != NULL, ("no rx radiotap header")); len = le16toh(rh->it_len); if (vap0->iv_flags_ext & IEEE80211_FEXT_BPF) bpf_mtap2(vap0->iv_rawbpf, rh, len, m); /* * Spam monitor mode vaps with unicast frames. Multicast * frames are handled by passing through ieee80211_input_all * which distributes copies to the monitor mode vaps. */ if (ic->ic_montaps != 0 && (m->m_flags & M_BCAST) == 0) spam_vaps(vap0, m, rh, len); } /* * Dispatch radiotap data for a packet received outside the normal * rx processing path; this is used, for example, to handle frames * received with errors that would otherwise be dropped. */ void ieee80211_radiotap_rx_all(struct ieee80211com *ic, struct mbuf *m) { struct ieee80211_radiotap_header *rh = ic->ic_rh; int len = le16toh(rh->it_len); struct ieee80211vap *vap; /* XXX locking? */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { if (ieee80211_radiotap_active_vap(vap) && vap->iv_state != IEEE80211_S_INIT) bpf_mtap2(vap->iv_rawbpf, rh, len, m); } } /* * Return the offset of the specified item in the radiotap * header description. If the item is not present or is not * known -1 is returned. */ static int radiotap_offset(struct ieee80211_radiotap_header *rh, int n_vendor_attributes, int item) { static const struct { size_t align, width; } items[] = { [IEEE80211_RADIOTAP_TSFT] = { .align = sizeof(uint64_t), .width = sizeof(uint64_t), }, [IEEE80211_RADIOTAP_FLAGS] = { .align = sizeof(uint8_t), .width = sizeof(uint8_t), }, [IEEE80211_RADIOTAP_RATE] = { .align = sizeof(uint8_t), .width = sizeof(uint8_t), }, [IEEE80211_RADIOTAP_CHANNEL] = { .align = sizeof(uint16_t), .width = 2*sizeof(uint16_t), }, [IEEE80211_RADIOTAP_FHSS] = { .align = sizeof(uint16_t), .width = sizeof(uint16_t), }, [IEEE80211_RADIOTAP_DBM_ANTSIGNAL] = { .align = sizeof(uint8_t), .width = sizeof(uint8_t), }, [IEEE80211_RADIOTAP_DBM_ANTNOISE] = { .align = sizeof(uint8_t), .width = sizeof(uint8_t), }, [IEEE80211_RADIOTAP_LOCK_QUALITY] = { .align = sizeof(uint16_t), .width = sizeof(uint16_t), }, [IEEE80211_RADIOTAP_TX_ATTENUATION] = { .align = sizeof(uint16_t), .width = sizeof(uint16_t), }, [IEEE80211_RADIOTAP_DB_TX_ATTENUATION] = { .align = sizeof(uint16_t), .width = sizeof(uint16_t), }, [IEEE80211_RADIOTAP_DBM_TX_POWER] = { .align = sizeof(uint8_t), .width = sizeof(uint8_t), }, [IEEE80211_RADIOTAP_ANTENNA] = { .align = sizeof(uint8_t), .width = sizeof(uint8_t), }, [IEEE80211_RADIOTAP_DB_ANTSIGNAL] = { .align = sizeof(uint8_t), .width = sizeof(uint8_t), }, [IEEE80211_RADIOTAP_DB_ANTNOISE] = { .align = sizeof(uint8_t), .width = sizeof(uint8_t), }, [IEEE80211_RADIOTAP_XCHANNEL] = { .align = sizeof(uint32_t), .width = 2*sizeof(uint32_t), }, [IEEE80211_RADIOTAP_MCS] = { .align = sizeof(uint8_t), .width = 3*sizeof(uint8_t), }, }; uint32_t present = le32toh(rh->it_present); int off, i; off = sizeof(struct ieee80211_radiotap_header); off += n_vendor_attributes * (sizeof(uint32_t)); for (i = 0; i < IEEE80211_RADIOTAP_EXT; i++) { if ((present & (1< le16toh(rh->it_len)) { /* NB: item does not fit in header data */ - printf("%s: item %d not in header data, " + net80211_printf("%s: item %d not in header data, " "off %d width %zu len %d\n", __func__, i, off, items[i].width, le16toh(rh->it_len)); return -1; } return off; } off += items[i].width; } return -1; } diff --git a/sys/net80211/ieee80211_scan.c b/sys/net80211/ieee80211_scan.c index 78ee78fdd74c..d54cde157c6c 100644 --- a/sys/net80211/ieee80211_scan.c +++ b/sys/net80211/ieee80211_scan.c @@ -1,699 +1,699 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2002-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 /* * IEEE 802.11 scanning support. */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /* XXX until it's implemented as attach ops */ #include #include /* * Roaming-related defaults. RSSI thresholds are as returned by the * driver (.5dBm). Transmit rate thresholds are IEEE rate codes (i.e * .5M units) or MCS. */ /* rssi thresholds */ #define ROAM_RSSI_11A_DEFAULT 14 /* 11a bss */ #define ROAM_RSSI_11B_DEFAULT 14 /* 11b bss */ #define ROAM_RSSI_11BONLY_DEFAULT 14 /* 11b-only bss */ /* transmit rate thresholds */ #define ROAM_RATE_11A_DEFAULT 2*12 /* 11a bss */ #define ROAM_RATE_11B_DEFAULT 2*5 /* 11b bss */ #define ROAM_RATE_11BONLY_DEFAULT 2*1 /* 11b-only bss */ #define ROAM_RATE_HALF_DEFAULT 2*6 /* half-width 11a/g bss */ #define ROAM_RATE_QUARTER_DEFAULT 2*3 /* quarter-width 11a/g bss */ #define ROAM_MCS_11N_DEFAULT (1 | IEEE80211_RATE_MCS) /* 11n bss */ #define ROAM_MCS_11AC_DEFAULT (1 | IEEE80211_RATE_MCS) /* 11ac bss; XXX not used yet */ void ieee80211_scan_attach(struct ieee80211com *ic) { /* * If there's no scan method pointer, attach the * swscan set as a default. */ if (ic->ic_scan_methods == NULL) ieee80211_swscan_attach(ic); else ic->ic_scan_methods->sc_attach(ic); } void ieee80211_scan_detach(struct ieee80211com *ic) { /* * Ideally we'd do the ss_ops detach call here; * but then sc_detach() would need to be split in two. * * I'll do that later. */ ic->ic_scan_methods->sc_detach(ic); } static const struct ieee80211_roamparam defroam[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_11A] = { .rssi = ROAM_RSSI_11A_DEFAULT, .rate = ROAM_RATE_11A_DEFAULT }, [IEEE80211_MODE_11G] = { .rssi = ROAM_RSSI_11B_DEFAULT, .rate = ROAM_RATE_11B_DEFAULT }, [IEEE80211_MODE_11B] = { .rssi = ROAM_RSSI_11BONLY_DEFAULT, .rate = ROAM_RATE_11BONLY_DEFAULT }, [IEEE80211_MODE_TURBO_A]= { .rssi = ROAM_RSSI_11A_DEFAULT, .rate = ROAM_RATE_11A_DEFAULT }, [IEEE80211_MODE_TURBO_G]= { .rssi = ROAM_RSSI_11A_DEFAULT, .rate = ROAM_RATE_11A_DEFAULT }, [IEEE80211_MODE_STURBO_A]={ .rssi = ROAM_RSSI_11A_DEFAULT, .rate = ROAM_RATE_11A_DEFAULT }, [IEEE80211_MODE_HALF] = { .rssi = ROAM_RSSI_11A_DEFAULT, .rate = ROAM_RATE_HALF_DEFAULT }, [IEEE80211_MODE_QUARTER]= { .rssi = ROAM_RSSI_11A_DEFAULT, .rate = ROAM_RATE_QUARTER_DEFAULT }, [IEEE80211_MODE_11NA] = { .rssi = ROAM_RSSI_11A_DEFAULT, .rate = ROAM_MCS_11N_DEFAULT }, [IEEE80211_MODE_11NG] = { .rssi = ROAM_RSSI_11B_DEFAULT, .rate = ROAM_MCS_11N_DEFAULT }, [IEEE80211_MODE_VHT_2GHZ] = { .rssi = ROAM_RSSI_11B_DEFAULT, .rate = ROAM_MCS_11AC_DEFAULT }, [IEEE80211_MODE_VHT_5GHZ] = { .rssi = ROAM_RSSI_11A_DEFAULT, .rate = ROAM_MCS_11AC_DEFAULT }, }; void ieee80211_scan_vattach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; int m; vap->iv_bgscanidle = (IEEE80211_BGSCAN_IDLE_DEFAULT*1000)/hz; vap->iv_bgscanintvl = IEEE80211_BGSCAN_INTVAL_DEFAULT*hz; vap->iv_scanvalid = IEEE80211_SCAN_VALID_DEFAULT*hz; vap->iv_roaming = IEEE80211_ROAMING_AUTO; memset(vap->iv_roamparms, 0, sizeof(vap->iv_roamparms)); for (m = IEEE80211_MODE_AUTO + 1; m < IEEE80211_MODE_MAX; m++) { if (isclr(ic->ic_modecaps, m)) continue; memcpy(&vap->iv_roamparms[m], &defroam[m], sizeof(defroam[m])); } ic->ic_scan_methods->sc_vattach(vap); } void ieee80211_scan_vdetach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_state *ss; IEEE80211_LOCK(ic); ss = ic->ic_scan; ic->ic_scan_methods->sc_vdetach(vap); if (ss != NULL && ss->ss_vap == vap) { if (ss->ss_ops != NULL) { ss->ss_ops->scan_detach(ss); ss->ss_ops = NULL; } ss->ss_vap = NULL; } IEEE80211_UNLOCK(ic); } /* * Simple-minded scanner module support. */ static const char *scan_modnames[IEEE80211_OPMODE_MAX] = { "wlan_scan_sta", /* IEEE80211_M_IBSS */ "wlan_scan_sta", /* IEEE80211_M_STA */ "wlan_scan_wds", /* IEEE80211_M_WDS */ "wlan_scan_sta", /* IEEE80211_M_AHDEMO */ "wlan_scan_ap", /* IEEE80211_M_HOSTAP */ "wlan_scan_monitor", /* IEEE80211_M_MONITOR */ "wlan_scan_sta", /* IEEE80211_M_MBSS */ }; static const struct ieee80211_scanner *scanners[IEEE80211_OPMODE_MAX]; const struct ieee80211_scanner * ieee80211_scanner_get(enum ieee80211_opmode mode) { if (mode >= IEEE80211_OPMODE_MAX) return NULL; if (scanners[mode] == NULL) ieee80211_load_module(scan_modnames[mode]); return scanners[mode]; } void ieee80211_scanner_register(enum ieee80211_opmode mode, const struct ieee80211_scanner *scan) { if (mode >= IEEE80211_OPMODE_MAX) return; scanners[mode] = scan; } void ieee80211_scanner_unregister(enum ieee80211_opmode mode, const struct ieee80211_scanner *scan) { if (mode >= IEEE80211_OPMODE_MAX) return; if (scanners[mode] == scan) scanners[mode] = NULL; } void ieee80211_scanner_unregister_all(const struct ieee80211_scanner *scan) { int m; for (m = 0; m < IEEE80211_OPMODE_MAX; m++) if (scanners[m] == scan) scanners[m] = NULL; } /* * Update common scanner state to reflect the current * operating mode. This is called when the state machine * is transitioned to RUN state w/o scanning--e.g. when * operating in monitor mode. The purpose of this is to * ensure later callbacks find ss_ops set to properly * reflect current operating mode. */ void ieee80211_scan_update_locked(struct ieee80211vap *vap, const struct ieee80211_scanner *scan) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_state *ss = ic->ic_scan; IEEE80211_LOCK_ASSERT(ic); #ifdef IEEE80211_DEBUG if (ss->ss_vap != vap || ss->ss_ops != scan) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: current scanner is <%s:%s>, switch to <%s:%s>\n", __func__, ss->ss_vap != NULL ? ieee80211_get_vap_ifname(ss->ss_vap) : "none", ss->ss_vap != NULL ? ieee80211_opmode_name[ss->ss_vap->iv_opmode] : "none", ieee80211_get_vap_ifname(vap), ieee80211_opmode_name[vap->iv_opmode]); } #endif ss->ss_vap = vap; if (ss->ss_ops != scan) { /* * Switch scanners; detach old, attach new. Special * case where a single scan module implements multiple * policies by using different scan ops but a common * core. We assume if the old and new attach methods * are identical then it's ok to just change ss_ops * and not flush the internal state of the module. */ if (scan == NULL || ss->ss_ops == NULL || ss->ss_ops->scan_attach != scan->scan_attach) { if (ss->ss_ops != NULL) ss->ss_ops->scan_detach(ss); if (scan != NULL && !scan->scan_attach(ss)) { /* XXX attach failure */ /* XXX stat+msg */ scan = NULL; } } ss->ss_ops = scan; } } #ifdef IEEE80211_DEBUG static void ieee80211_scan_dump_channels(const struct ieee80211_scan_state *ss) { struct ieee80211com *ic = ss->ss_ic; const char *sep; int i; sep = ""; for (i = ss->ss_next; i < ss->ss_last; i++) { const struct ieee80211_channel *c = ss->ss_chans[i]; - printf("%s%u%c", sep, ieee80211_chan2ieee(ic, c), + net80211_printf("%s%u%c", sep, ieee80211_chan2ieee(ic, c), ieee80211_channel_type_char(c)); sep = ", "; } } void ieee80211_scan_dump(struct ieee80211_scan_state *ss) { struct ieee80211vap *vap = ss->ss_vap; net80211_vap_printf(vap, "scan set "); ieee80211_scan_dump_channels(ss); net80211_vap_printf(vap, " dwell min %ums max %ums\n", ticks_to_msecs(ss->ss_mindwell), ticks_to_msecs(ss->ss_maxdwell)); } #endif /* IEEE80211_DEBUG */ void ieee80211_scan_copy_ssid(struct ieee80211vap *vap, struct ieee80211_scan_state *ss, int nssid, const struct ieee80211_scan_ssid ssids[]) { if (nssid > IEEE80211_SCAN_MAX_SSID) { /* XXX printf */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: too many ssid %d, ignoring all of them\n", __func__, nssid); return; } memcpy(ss->ss_ssid, ssids, nssid * sizeof(ssids[0])); ss->ss_nssid = nssid; } /* * Start a scan unless one is already going. */ int ieee80211_start_scan(struct ieee80211vap *vap, int flags, u_int duration, u_int mindwell, u_int maxdwell, u_int nssid, const struct ieee80211_scan_ssid ssids[]) { const struct ieee80211_scanner *scan; struct ieee80211com *ic = vap->iv_ic; scan = ieee80211_scanner_get(vap->iv_opmode); if (scan == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: no scanner support for %s mode\n", __func__, ieee80211_opmode_name[vap->iv_opmode]); /* XXX stat */ return 0; } return ic->ic_scan_methods->sc_start_scan(scan, vap, flags, duration, mindwell, maxdwell, nssid, ssids); } /* * Check the scan cache for an ap/channel to use; if that * fails then kick off a new scan. */ int ieee80211_check_scan(struct ieee80211vap *vap, int flags, u_int duration, u_int mindwell, u_int maxdwell, u_int nssid, const struct ieee80211_scan_ssid ssids[]) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_state *ss = ic->ic_scan; const struct ieee80211_scanner *scan; int result; scan = ieee80211_scanner_get(vap->iv_opmode); if (scan == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: no scanner support for %s mode\n", __func__, vap->iv_opmode); /* XXX stat */ return 0; } /* * Check if there's a list of scan candidates already. * XXX want more than the ap we're currently associated with */ IEEE80211_LOCK(ic); IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: %s scan, %s%s%s%s%s\n" , __func__ , flags & IEEE80211_SCAN_ACTIVE ? "active" : "passive" , flags & IEEE80211_SCAN_FLUSH ? "flush" : "append" , flags & IEEE80211_SCAN_NOPICK ? ", nopick" : "" , flags & IEEE80211_SCAN_NOJOIN ? ", nojoin" : "" , flags & IEEE80211_SCAN_PICK1ST ? ", pick1st" : "" , flags & IEEE80211_SCAN_ONCE ? ", once" : "" ); if (ss->ss_ops != scan) { /* XXX re-use cache contents? e.g. adhoc<->sta */ flags |= IEEE80211_SCAN_FLUSH; } /* * XXX TODO: separate things out a bit better. */ ieee80211_scan_update_locked(vap, scan); result = ic->ic_scan_methods->sc_check_scan(scan, vap, flags, duration, mindwell, maxdwell, nssid, ssids); IEEE80211_UNLOCK(ic); return (result); } /* * Check the scan cache for an ap/channel to use; if that fails * then kick off a scan using the current settings. */ int ieee80211_check_scan_current(struct ieee80211vap *vap) { return ieee80211_check_scan(vap, IEEE80211_SCAN_ACTIVE, IEEE80211_SCAN_FOREVER, 0, 0, vap->iv_des_nssid, vap->iv_des_ssid); } /* * Restart a previous scan. If the previous scan completed * then we start again using the existing channel list. */ int ieee80211_bg_scan(struct ieee80211vap *vap, int flags) { struct ieee80211com *ic = vap->iv_ic; const struct ieee80211_scanner *scan; // IEEE80211_UNLOCK_ASSERT(sc); /* * If the driver has not announced BGSCAN capabilities * or BGSCAN is disabled do not attempt to start a bg_scan. * IEEE80211_F_BGSCAN only gets set if IEEE80211_C_BGSCAN * was set by the driver, so no need to check for both here. */ if ((vap->iv_flags & IEEE80211_F_BGSCAN) == 0) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: BGSCAN not enabled; not starting bg_scan\n", __func__); return (0); } scan = ieee80211_scanner_get(vap->iv_opmode); if (scan == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: no scanner support for %s mode\n", __func__, vap->iv_opmode); /* XXX stat */ return 0; } /* * XXX TODO: pull apart the bgscan logic into whatever * belongs here and whatever belongs in the software * scanner. */ return (ic->ic_scan_methods->sc_bg_scan(scan, vap, flags)); } /* * Cancel any scan currently going on for the specified vap. */ void ieee80211_cancel_scan(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; ic->ic_scan_methods->sc_cancel_scan(vap); } /* * Cancel any scan currently going on. * * This is called during normal 802.11 data path to cancel * a scan so a newly arrived normal data packet can be sent. */ void ieee80211_cancel_anyscan(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; ic->ic_scan_methods->sc_cancel_anyscan(vap); } /* * Manually switch to the next channel in the channel list. * Provided for drivers that manage scanning themselves * (e.g. for firmware-based devices). */ void ieee80211_scan_next(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; ic->ic_scan_methods->sc_scan_next(vap); } /* * Manually stop a scan that is currently running. * Provided for drivers that are not able to scan single channels * (e.g. for firmware-based devices). */ void ieee80211_scan_done(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_state *ss; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: called\n", __func__); IEEE80211_LOCK(ic); ss = ic->ic_scan; ss->ss_next = ss->ss_last; /* all channels are complete */ ic->ic_scan_methods->sc_scan_done(vap); IEEE80211_UNLOCK(ic); } /* * Probe the current channel, if allowed, while scanning. * If the channel is not marked passive-only then send * a probe request immediately. Otherwise mark state and * listen for beacons on the channel; if we receive something * then we'll transmit a probe request. */ void ieee80211_probe_curchan(struct ieee80211vap *vap, bool force) { struct ieee80211com *ic = vap->iv_ic; if ((ic->ic_curchan->ic_flags & IEEE80211_CHAN_PASSIVE) && !force) { ic->ic_flags_ext |= IEEE80211_FEXT_PROBECHAN; return; } ic->ic_scan_methods->sc_scan_probe_curchan(vap, force); } #ifdef IEEE80211_DEBUG static void dump_country(const uint8_t *ie) { const struct ieee80211_country_ie *cie = (const struct ieee80211_country_ie *) ie; int i, nbands, schan, nchan; if (cie->len < 3) { - printf(" ", cie->len); + net80211_printf(" ", cie->len); return; } - printf(" country [%c%c%c", cie->cc[0], cie->cc[1], cie->cc[2]); + net80211_printf(" country [%c%c%c", cie->cc[0], cie->cc[1], cie->cc[2]); nbands = (cie->len - 3) / sizeof(cie->band[0]); for (i = 0; i < nbands; i++) { schan = cie->band[i].schan; nchan = cie->band[i].nchan; if (nchan != 1) - printf(" %u-%u,%u", schan, schan + nchan-1, + net80211_printf(" %u-%u,%u", schan, schan + nchan-1, cie->band[i].maxtxpwr); else - printf(" %u,%u", schan, cie->band[i].maxtxpwr); + net80211_printf(" %u,%u", schan, cie->band[i].maxtxpwr); } - printf("]"); + net80211_printf("]"); } void ieee80211_scan_dump_probe_beacon(uint8_t subtype, int isnew, const uint8_t mac[IEEE80211_ADDR_LEN], const struct ieee80211_scanparams *sp, int rssi) { - printf("[%s] %s%s on chan %u (bss chan %u) ", + net80211_printf("[%s] %s%s on chan %u (bss chan %u) ", ether_sprintf(mac), isnew ? "new " : "", ieee80211_mgt_subtype_name(subtype), sp->chan, sp->bchan); ieee80211_print_essid(sp->ssid + 2, sp->ssid[1]); - printf(" rssi %d\n", rssi); + net80211_printf(" rssi %d\n", rssi); if (isnew) { - printf("[%s] caps 0x%x bintval %u erp 0x%x", + net80211_printf("[%s] caps 0x%x bintval %u erp 0x%x", ether_sprintf(mac), sp->capinfo, sp->bintval, sp->erp); if (sp->country != NULL) dump_country(sp->country); - printf("\n"); + net80211_printf("\n"); } } #endif /* IEEE80211_DEBUG */ /* * Process a beacon or probe response frame. */ void ieee80211_add_scan(struct ieee80211vap *vap, struct ieee80211_channel *curchan, const struct ieee80211_scanparams *sp, const struct ieee80211_frame *wh, int subtype, int rssi, int noise) { struct ieee80211com *ic = vap->iv_ic; return (ic->ic_scan_methods->sc_add_scan(vap, curchan, sp, wh, subtype, rssi, noise)); } /* * Timeout/age scan cache entries; called from sta timeout * timer (XXX should be self-contained). */ void ieee80211_scan_timeout(struct ieee80211com *ic) { struct ieee80211_scan_state *ss = ic->ic_scan; if (ss->ss_ops != NULL) ss->ss_ops->scan_age(ss); } /* * Mark a scan cache entry after a successful associate. */ void ieee80211_scan_assoc_success(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) { struct ieee80211_scan_state *ss = vap->iv_ic->ic_scan; if (ss->ss_ops != NULL) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_SCAN, mac, "%s", __func__); ss->ss_ops->scan_assoc_success(ss, mac); } } /* * Demerit a scan cache entry after failing to associate. */ void ieee80211_scan_assoc_fail(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN], int reason) { struct ieee80211_scan_state *ss = vap->iv_ic->ic_scan; if (ss->ss_ops != NULL) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_SCAN, mac, "%s: reason %u", __func__, reason); ss->ss_ops->scan_assoc_fail(ss, mac, reason); } } /* * Iterate over the contents of the scan cache. */ void ieee80211_scan_iterate(struct ieee80211vap *vap, ieee80211_scan_iter_func *f, void *arg) { struct ieee80211_scan_state *ss = vap->iv_ic->ic_scan; if (ss->ss_ops != NULL) ss->ss_ops->scan_iterate(ss, f, arg); } /* * Flush the contents of the scan cache. */ void ieee80211_scan_flush(struct ieee80211vap *vap) { struct ieee80211_scan_state *ss = vap->iv_ic->ic_scan; if (ss->ss_ops != NULL && ss->ss_vap == vap) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s\n", __func__); ss->ss_ops->scan_flush(ss); } } /* * Check the scan cache for an ap/channel to use; if that * fails then kick off a new scan. */ struct ieee80211_channel * ieee80211_scan_pickchannel(struct ieee80211com *ic, int flags) { struct ieee80211_scan_state *ss = ic->ic_scan; IEEE80211_LOCK_ASSERT(ic); if (ss == NULL || ss->ss_ops == NULL || ss->ss_vap == NULL) { /* XXX printf? */ return NULL; } if (ss->ss_ops->scan_pickchan == NULL) { IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN, "%s: scan module does not support picking a channel, " "opmode %s\n", __func__, ss->ss_vap->iv_opmode); return NULL; } return ss->ss_ops->scan_pickchan(ss, flags); } diff --git a/sys/net80211/ieee80211_scan_sta.c b/sys/net80211/ieee80211_scan_sta.c index 69347a5d99cf..529552d3719a 100644 --- a/sys/net80211/ieee80211_scan_sta.c +++ b/sys/net80211/ieee80211_scan_sta.c @@ -1,1999 +1,1999 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2002-2009 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 /* * IEEE 802.11 station scanning support. */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_TDMA #include #endif #ifdef IEEE80211_SUPPORT_MESH #include #endif #include #include #include /* * Parameters for managing cache entries: * * o a station with STA_FAILS_MAX failures is not considered * when picking a candidate * o a station that hasn't had an update in STA_PURGE_SCANS * (background) scans is discarded * o after STA_FAILS_AGE seconds we clear the failure count */ #define STA_FAILS_MAX 2 /* assoc failures before ignored */ #define STA_FAILS_AGE (2*60) /* time before clearing fails (secs) */ #define STA_PURGE_SCANS 2 /* age for purging entries (scans) */ /* XXX tunable */ #define STA_RSSI_MIN 8 /* min acceptable rssi */ #define STA_RSSI_MAX 40 /* max rssi for comparison */ struct sta_entry { struct ieee80211_scan_entry base; TAILQ_ENTRY(sta_entry) se_list; LIST_ENTRY(sta_entry) se_hash; uint8_t se_fails; /* failure to associate count */ uint8_t se_seen; /* seen during current scan */ uint8_t se_notseen; /* not seen in previous scans */ uint8_t se_flags; #define STA_DEMOTE11B 0x01 /* match w/ demoted 11b chan */ uint32_t se_avgrssi; /* LPF rssi state */ unsigned long se_lastupdate; /* time of last update */ unsigned long se_lastfail; /* time of last failure */ unsigned long se_lastassoc; /* time of last association */ u_int se_scangen; /* iterator scan gen# */ u_int se_countrygen; /* gen# of last cc notify */ }; #define STA_HASHSIZE 32 /* simple hash is enough for variation of macaddr */ #define STA_HASH(addr) \ (((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % STA_HASHSIZE) #define MAX_IEEE_CHAN 256 /* max acceptable IEEE chan # */ CTASSERT(MAX_IEEE_CHAN >= 256); struct sta_table { ieee80211_scan_table_lock_t st_lock; /* on scan table */ TAILQ_HEAD(, sta_entry) st_entry; /* all entries */ LIST_HEAD(, sta_entry) st_hash[STA_HASHSIZE]; ieee80211_scan_iter_lock_t st_scanlock; /* on st_scaniter */ u_int st_scaniter; /* gen# for iterator */ u_int st_scangen; /* scan generation # */ int st_newscan; /* ap-related state */ int st_maxrssi[MAX_IEEE_CHAN]; }; static void sta_flush_table(struct sta_table *); /* * match_bss returns a bitmask describing if an entry is suitable * for use. If non-zero the entry was deemed not suitable and it's * contents explains why. The following flags are or'd to this * mask and can be used to figure out why the entry was rejected. */ #define MATCH_CHANNEL 0x00001 /* channel mismatch */ #define MATCH_CAPINFO 0x00002 /* capabilities mismatch, e.g. no ess */ #define MATCH_PRIVACY 0x00004 /* privacy mismatch */ #define MATCH_RATE 0x00008 /* rate set mismatch */ #define MATCH_SSID 0x00010 /* ssid mismatch */ #define MATCH_BSSID 0x00020 /* bssid mismatch */ #define MATCH_FAILS 0x00040 /* too many failed auth attempts */ #define MATCH_NOTSEEN 0x00080 /* not seen in recent scans */ #define MATCH_RSSI 0x00100 /* rssi deemed too low to use */ #define MATCH_CC 0x00200 /* country code mismatch */ #ifdef IEEE80211_SUPPORT_TDMA #define MATCH_TDMA_NOIE 0x00400 /* no TDMA ie */ #define MATCH_TDMA_NOTMASTER 0x00800 /* not TDMA master */ #define MATCH_TDMA_NOSLOT 0x01000 /* all TDMA slots occupied */ #define MATCH_TDMA_LOCAL 0x02000 /* local address */ #define MATCH_TDMA_VERSION 0x04000 /* protocol version mismatch */ #endif #define MATCH_MESH_NOID 0x10000 /* no MESHID ie */ #define MATCH_MESHID 0x20000 /* meshid mismatch */ static int match_bss(struct ieee80211vap *, const struct ieee80211_scan_state *, struct sta_entry *, int); static void adhoc_age(struct ieee80211_scan_state *); static __inline int isocmp(const uint8_t cc1[], const uint8_t cc2[]) { return (cc1[0] == cc2[0] && cc1[1] == cc2[1]); } /* number of references from net80211 layer */ static int nrefs = 0; /* * Module glue. */ IEEE80211_SCANNER_MODULE(sta, 1); /* * Attach prior to any scanning work. */ static int sta_attach(struct ieee80211_scan_state *ss) { struct sta_table *st; st = (struct sta_table *) IEEE80211_MALLOC(sizeof(struct sta_table), M_80211_SCAN, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (st == NULL) return 0; IEEE80211_SCAN_TABLE_LOCK_INIT(st, "scantable"); IEEE80211_SCAN_ITER_LOCK_INIT(st, "scangen"); TAILQ_INIT(&st->st_entry); ss->ss_priv = st; nrefs++; /* NB: we assume caller locking */ return 1; } /* * Cleanup any private state. */ static int sta_detach(struct ieee80211_scan_state *ss) { struct sta_table *st = ss->ss_priv; if (st != NULL) { sta_flush_table(st); IEEE80211_SCAN_TABLE_LOCK_DESTROY(st); IEEE80211_SCAN_ITER_LOCK_DESTROY(st); IEEE80211_FREE(st, M_80211_SCAN); KASSERT(nrefs > 0, ("imbalanced attach/detach")); nrefs--; /* NB: we assume caller locking */ } return 1; } /* * Flush all per-scan state. */ static int sta_flush(struct ieee80211_scan_state *ss) { struct sta_table *st = ss->ss_priv; IEEE80211_SCAN_TABLE_LOCK(st); sta_flush_table(st); IEEE80211_SCAN_TABLE_UNLOCK(st); ss->ss_last = 0; return 0; } /* * Flush all entries in the scan cache. */ static void sta_flush_table(struct sta_table *st) { struct sta_entry *se, *next; TAILQ_FOREACH_SAFE(se, &st->st_entry, se_list, next) { TAILQ_REMOVE(&st->st_entry, se, se_list); LIST_REMOVE(se, se_hash); ieee80211_ies_cleanup(&se->base.se_ies); IEEE80211_FREE(se, M_80211_SCAN); } memset(st->st_maxrssi, 0, sizeof(st->st_maxrssi)); } /* * Process a beacon or probe response frame; create an * entry in the scan cache or update any previous entry. */ static int sta_add(struct ieee80211_scan_state *ss, struct ieee80211_channel *curchan, const struct ieee80211_scanparams *sp, const struct ieee80211_frame *wh, int subtype, int rssi, int noise) { #define ISPROBE(_st) ((_st) == IEEE80211_FC0_SUBTYPE_PROBE_RESP) #define PICK1ST(_ss) \ ((ss->ss_flags & (IEEE80211_SCAN_PICK1ST | IEEE80211_SCAN_GOTPICK)) == \ IEEE80211_SCAN_PICK1ST) struct sta_table *st = ss->ss_priv; const uint8_t *macaddr = wh->i_addr2; struct ieee80211vap *vap = ss->ss_vap; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel *c; struct sta_entry *se; struct ieee80211_scan_entry *ise; int hash; hash = STA_HASH(macaddr); IEEE80211_SCAN_TABLE_LOCK(st); LIST_FOREACH(se, &st->st_hash[hash], se_hash) if (IEEE80211_ADDR_EQ(se->base.se_macaddr, macaddr)) goto found; se = (struct sta_entry *) IEEE80211_MALLOC(sizeof(struct sta_entry), M_80211_SCAN, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (se == NULL) { IEEE80211_SCAN_TABLE_UNLOCK(st); return 0; } se->se_scangen = st->st_scaniter-1; se->se_avgrssi = IEEE80211_RSSI_DUMMY_MARKER; IEEE80211_ADDR_COPY(se->base.se_macaddr, macaddr); TAILQ_INSERT_TAIL(&st->st_entry, se, se_list); LIST_INSERT_HEAD(&st->st_hash[hash], se, se_hash); found: ise = &se->base; /* XXX ap beaconing multiple ssid w/ same bssid */ if (sp->ssid[1] != 0 && (ISPROBE(subtype) || ise->se_ssid[1] == 0)) memcpy(ise->se_ssid, sp->ssid, 2+sp->ssid[1]); KASSERT(sp->rates[1] <= IEEE80211_RATE_MAXSIZE, ("rate set too large: %u", sp->rates[1])); memcpy(ise->se_rates, sp->rates, 2+sp->rates[1]); if (sp->xrates != NULL) { /* XXX validate xrates[1] */ KASSERT(sp->xrates[1] <= IEEE80211_RATE_MAXSIZE, ("xrate set too large: %u", sp->xrates[1])); memcpy(ise->se_xrates, sp->xrates, 2+sp->xrates[1]); } else ise->se_xrates[1] = 0; IEEE80211_ADDR_COPY(ise->se_bssid, wh->i_addr3); if ((sp->status & IEEE80211_BPARSE_OFFCHAN) == 0) { /* * Record rssi data using extended precision LPF filter. * * NB: use only on-channel data to insure we get a good * estimate of the signal we'll see when associated. */ IEEE80211_RSSI_LPF(se->se_avgrssi, rssi); ise->se_rssi = IEEE80211_RSSI_GET(se->se_avgrssi); ise->se_noise = noise; } memcpy(ise->se_tstamp.data, sp->tstamp, sizeof(ise->se_tstamp)); ise->se_intval = sp->bintval; ise->se_capinfo = sp->capinfo; #ifdef IEEE80211_SUPPORT_MESH if (sp->meshid != NULL && sp->meshid[1] != 0) memcpy(ise->se_meshid, sp->meshid, 2+sp->meshid[1]); #endif /* * Beware of overriding se_chan for frames seen * off-channel; this can cause us to attempt an * association on the wrong channel. */ if (sp->status & IEEE80211_BPARSE_OFFCHAN) { /* * Off-channel, locate the home/bss channel for the sta * using the value broadcast in the DSPARMS ie. We know * sp->chan has this value because it's used to calculate * IEEE80211_BPARSE_OFFCHAN. */ c = ieee80211_find_channel_byieee(ic, sp->chan, curchan->ic_flags); if (c != NULL) { ise->se_chan = c; } else if (ise->se_chan == NULL) { /* should not happen, pick something */ ise->se_chan = curchan; } } else ise->se_chan = curchan; /* VHT demotion */ if (IEEE80211_IS_CHAN_VHT(ise->se_chan) && sp->vhtcap == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_11N, "%s: demoting VHT->HT %d/0x%08x\n", __func__, ise->se_chan->ic_freq, ise->se_chan->ic_flags); /* Demote legacy networks to a non-VHT channel. */ c = ieee80211_find_channel(ic, ise->se_chan->ic_freq, ise->se_chan->ic_flags & ~IEEE80211_CHAN_VHT); KASSERT(c != NULL, ("no non-VHT channel %u", ise->se_chan->ic_ieee)); ise->se_chan = c; } /* HT demotion */ if (IEEE80211_IS_CHAN_HT(ise->se_chan) && sp->htcap == NULL) { /* Demote legacy networks to a non-HT channel. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_11N, "%s: demoting HT->legacy %d/0x%08x\n", __func__, ise->se_chan->ic_freq, ise->se_chan->ic_flags); c = ieee80211_find_channel(ic, ise->se_chan->ic_freq, ise->se_chan->ic_flags & ~IEEE80211_CHAN_HT); KASSERT(c != NULL, ("no legacy channel %u", ise->se_chan->ic_ieee)); ise->se_chan = c; } ise->se_fhdwell = sp->fhdwell; ise->se_fhindex = sp->fhindex; ise->se_erp = sp->erp; ise->se_timoff = sp->timoff; if (sp->tim != NULL) { const struct ieee80211_tim_ie *tim = (const struct ieee80211_tim_ie *) sp->tim; ise->se_dtimperiod = tim->tim_period; } if (sp->country != NULL) { const struct ieee80211_country_ie *cie = (const struct ieee80211_country_ie *) sp->country; /* * If 11d is enabled and we're attempting to join a bss * that advertises it's country code then compare our * current settings to what we fetched from the country ie. * If our country code is unspecified or different then * dispatch an event to user space that identifies the * country code so our regdomain config can be changed. */ /* XXX only for STA mode? */ if ((IEEE80211_IS_CHAN_11D(ise->se_chan) || (vap->iv_flags_ext & IEEE80211_FEXT_DOTD)) && (ic->ic_regdomain.country == CTRY_DEFAULT || !isocmp(cie->cc, ic->ic_regdomain.isocc))) { /* only issue one notify event per scan */ if (se->se_countrygen != st->st_scangen) { ieee80211_notify_country(vap, ise->se_bssid, cie->cc); se->se_countrygen = st->st_scangen; } } ise->se_cc[0] = cie->cc[0]; ise->se_cc[1] = cie->cc[1]; } /* NB: no need to setup ie ptrs; they are not (currently) used */ (void) ieee80211_ies_init(&ise->se_ies, sp->ies, sp->ies_len); /* clear failure count after STA_FAIL_AGE passes */ if (se->se_fails && (ticks - se->se_lastfail) > STA_FAILS_AGE*hz) { se->se_fails = 0; IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_SCAN, macaddr, "%s: fails %u", __func__, se->se_fails); } se->se_lastupdate = ticks; /* update time */ se->se_seen = 1; se->se_notseen = 0; KASSERT(sizeof(sp->bchan) == 1, ("bchan size")); if (rssi > st->st_maxrssi[sp->bchan]) st->st_maxrssi[sp->bchan] = rssi; IEEE80211_SCAN_TABLE_UNLOCK(st); /* * If looking for a quick choice and nothing's * been found check here. */ if (PICK1ST(ss) && match_bss(vap, ss, se, IEEE80211_MSG_SCAN) == 0) ss->ss_flags |= IEEE80211_SCAN_GOTPICK; return 1; #undef PICK1ST #undef ISPROBE } /* * Check if a channel is excluded by user request. */ static int isexcluded(struct ieee80211vap *vap, const struct ieee80211_channel *c) { return (isclr(vap->iv_ic->ic_chan_active, c->ic_ieee) || (vap->iv_des_chan != IEEE80211_CHAN_ANYC && c->ic_freq != vap->iv_des_chan->ic_freq)); } static struct ieee80211_channel * find11gchannel(struct ieee80211com *ic, int i, int freq) { struct ieee80211_channel *c; int j; /* * The normal ordering in the channel list is b channel * immediately followed by g so optimize the search for * this. We'll still do a full search just in case. */ for (j = i+1; j < ic->ic_nchans; j++) { c = &ic->ic_channels[j]; if (c->ic_freq == freq && IEEE80211_IS_CHAN_G(c)) return c; } for (j = 0; j < i; j++) { c = &ic->ic_channels[j]; if (c->ic_freq == freq && IEEE80211_IS_CHAN_G(c)) return c; } return NULL; } static const u_int chanflags[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = IEEE80211_CHAN_B, [IEEE80211_MODE_11A] = IEEE80211_CHAN_A, [IEEE80211_MODE_11B] = IEEE80211_CHAN_B, [IEEE80211_MODE_11G] = IEEE80211_CHAN_G, [IEEE80211_MODE_FH] = IEEE80211_CHAN_FHSS, /* check base channel */ [IEEE80211_MODE_TURBO_A] = IEEE80211_CHAN_A, [IEEE80211_MODE_TURBO_G] = IEEE80211_CHAN_G, [IEEE80211_MODE_STURBO_A] = IEEE80211_CHAN_ST, [IEEE80211_MODE_HALF] = IEEE80211_CHAN_HALF, [IEEE80211_MODE_QUARTER] = IEEE80211_CHAN_QUARTER, /* check legacy */ [IEEE80211_MODE_11NA] = IEEE80211_CHAN_A, [IEEE80211_MODE_11NG] = IEEE80211_CHAN_G, [IEEE80211_MODE_VHT_5GHZ] = IEEE80211_CHAN_A, [IEEE80211_MODE_VHT_2GHZ] = IEEE80211_CHAN_G, }; static void add_channels(struct ieee80211vap *vap, struct ieee80211_scan_state *ss, enum ieee80211_phymode mode, const uint16_t freq[], int nfreq) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel *c, *cg; u_int modeflags; int i; KASSERT(mode < nitems(chanflags), ("Unexpected mode %u", mode)); modeflags = chanflags[mode]; for (i = 0; i < nfreq; i++) { if (ss->ss_last >= IEEE80211_SCAN_MAX) break; c = ieee80211_find_channel(ic, freq[i], modeflags); if (c == NULL || isexcluded(vap, c)) continue; if (mode == IEEE80211_MODE_AUTO) { KASSERT(IEEE80211_IS_CHAN_B(c), ("%s: wrong channel for 'auto' mode %u / %u\n", __func__, c->ic_freq, c->ic_flags)); /* * XXX special-case 11b/g channels so we select * the g channel if both are present. */ if ((cg = find11gchannel(ic, i, c->ic_freq)) != NULL) c = cg; } ss->ss_chans[ss->ss_last++] = c; } } struct scanlist { uint16_t mode; uint16_t count; const uint16_t *list; }; static int checktable(const struct scanlist *scan, const struct ieee80211_channel *c) { int i; for (; scan->list != NULL; scan++) { for (i = 0; i < scan->count; i++) if (scan->list[i] == c->ic_freq) return 1; } return 0; } static int onscanlist(const struct ieee80211_scan_state *ss, const struct ieee80211_channel *c) { int i; for (i = 0; i < ss->ss_last; i++) if (ss->ss_chans[i] == c) return 1; return 0; } static void sweepchannels(struct ieee80211_scan_state *ss, struct ieee80211vap *vap, const struct scanlist table[]) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel *c; int i; for (i = 0; i < ic->ic_nchans; i++) { if (ss->ss_last >= IEEE80211_SCAN_MAX) break; c = &ic->ic_channels[i]; /* * Ignore dynamic turbo channels; we scan them * in normal mode (i.e. not boosted). Likewise * for HT/VHT channels, they get scanned using * legacy rates. */ if (IEEE80211_IS_CHAN_DTURBO(c) || IEEE80211_IS_CHAN_HT(c) || IEEE80211_IS_CHAN_VHT(c)) continue; /* * If a desired mode was specified, scan only * channels that satisfy that constraint. */ if (vap->iv_des_mode != IEEE80211_MODE_AUTO && vap->iv_des_mode != ieee80211_chan2mode(c)) continue; /* * Skip channels excluded by user request. */ if (isexcluded(vap, c)) continue; /* * Add the channel unless it is listed in the * fixed scan order tables. This insures we * don't sweep back in channels we filtered out * above. */ if (checktable(table, c)) continue; /* Add channel to scanning list. */ ss->ss_chans[ss->ss_last++] = c; } /* * Explicitly add any desired channel if: * - not already on the scan list * - allowed by any desired mode constraint * - there is space in the scan list * This allows the channel to be used when the filtering * mechanisms would otherwise elide it (e.g HT, turbo). */ c = vap->iv_des_chan; if (c != IEEE80211_CHAN_ANYC && !onscanlist(ss, c) && (vap->iv_des_mode == IEEE80211_MODE_AUTO || vap->iv_des_mode == ieee80211_chan2mode(c)) && ss->ss_last < IEEE80211_SCAN_MAX) ss->ss_chans[ss->ss_last++] = c; } static void makescanlist(struct ieee80211_scan_state *ss, struct ieee80211vap *vap, const struct scanlist table[]) { const struct scanlist *scan; enum ieee80211_phymode mode; ss->ss_last = 0; /* * Use the table of ordered channels to construct the list * of channels for scanning. Any channels in the ordered * list not in the master list will be discarded. */ for (scan = table; scan->list != NULL; scan++) { mode = scan->mode; switch (mode) { case IEEE80211_MODE_11B: if (vap->iv_des_mode == IEEE80211_MODE_11B) break; /* * The scan table marks 2.4Ghz channels as b * so if the desired mode is 11g / 11ng / 11acg, * then use the 11b channel list but upgrade the mode. * * NB: 11b -> AUTO lets add_channels upgrade an * 11b channel to 11g if available. */ if (vap->iv_des_mode == IEEE80211_MODE_AUTO || vap->iv_des_mode == IEEE80211_MODE_11G || vap->iv_des_mode == IEEE80211_MODE_11NG || vap->iv_des_mode == IEEE80211_MODE_VHT_2GHZ) { mode = vap->iv_des_mode; break; } continue; case IEEE80211_MODE_11A: /* Use 11a channel list for 11na / 11ac modes */ if (vap->iv_des_mode == IEEE80211_MODE_11NA || vap->iv_des_mode == IEEE80211_MODE_VHT_5GHZ) { mode = vap->iv_des_mode; break; } /* FALLTHROUGH */ default: /* * If a desired mode was specified, scan only * channels that satisfy that constraint. */ if (vap->iv_des_mode != IEEE80211_MODE_AUTO && vap->iv_des_mode != mode) continue; } #ifdef IEEE80211_F_XR /* XR does not operate on turbo channels */ if ((vap->iv_flags & IEEE80211_F_XR) && (mode == IEEE80211_MODE_TURBO_A || mode == IEEE80211_MODE_TURBO_G || mode == IEEE80211_MODE_STURBO_A)) continue; #endif /* * Add the list of the channels; any that are not * in the master channel list will be discarded. */ add_channels(vap, ss, mode, scan->list, scan->count); } /* * Add the channels from the ic that are not present * in the table. */ sweepchannels(ss, vap, table); } static const uint16_t rcl1[] = /* 8 FCC channel: 52, 56, 60, 64, 36, 40, 44, 48 */ { 5260, 5280, 5300, 5320, 5180, 5200, 5220, 5240 }; static const uint16_t rcl2[] = /* 4 MKK channels: 34, 38, 42, 46 */ { 5170, 5190, 5210, 5230 }; static const uint16_t rcl3[] = /* 2.4Ghz ch: 1,6,11,7,13 */ { 2412, 2437, 2462, 2442, 2472 }; static const uint16_t rcl4[] = /* 5 FCC channel: 149, 153, 161, 165 */ { 5745, 5765, 5785, 5805, 5825 }; static const uint16_t rcl7[] = /* 11 ETSI channel: 100,104,108,112,116,120,124,128,132,136,140 */ { 5500, 5520, 5540, 5560, 5580, 5600, 5620, 5640, 5660, 5680, 5700 }; static const uint16_t rcl8[] = /* 2.4Ghz ch: 2,3,4,5,8,9,10,12 */ { 2417, 2422, 2427, 2432, 2447, 2452, 2457, 2467 }; static const uint16_t rcl9[] = /* 2.4Ghz ch: 14 */ { 2484 }; static const uint16_t rcl10[] = /* Added Korean channels 2312-2372 */ { 2312, 2317, 2322, 2327, 2332, 2337, 2342, 2347, 2352, 2357, 2362, 2367, 2372 }; static const uint16_t rcl11[] = /* Added Japan channels in 4.9/5.0 spectrum */ { 5040, 5060, 5080, 4920, 4940, 4960, 4980 }; #ifdef ATH_TURBO_SCAN static const uint16_t rcl5[] = /* 3 static turbo channels */ { 5210, 5250, 5290 }; static const uint16_t rcl6[] = /* 2 static turbo channels */ { 5760, 5800 }; static const uint16_t rcl6x[] = /* 4 FCC3 turbo channels */ { 5540, 5580, 5620, 5660 }; static const uint16_t rcl12[] = /* 2.4Ghz Turbo channel 6 */ { 2437 }; static const uint16_t rcl13[] = /* dynamic Turbo channels */ { 5200, 5240, 5280, 5765, 5805 }; #endif /* ATH_TURBO_SCAN */ #define X(a) .count = sizeof(a)/sizeof(a[0]), .list = a static const struct scanlist staScanTable[] = { { IEEE80211_MODE_11B, X(rcl3) }, { IEEE80211_MODE_11A, X(rcl1) }, { IEEE80211_MODE_11A, X(rcl2) }, { IEEE80211_MODE_11B, X(rcl8) }, { IEEE80211_MODE_11B, X(rcl9) }, { IEEE80211_MODE_11A, X(rcl4) }, #ifdef ATH_TURBO_SCAN { IEEE80211_MODE_STURBO_A, X(rcl5) }, { IEEE80211_MODE_STURBO_A, X(rcl6) }, { IEEE80211_MODE_TURBO_A, X(rcl6x) }, { IEEE80211_MODE_TURBO_A, X(rcl13) }, #endif /* ATH_TURBO_SCAN */ { IEEE80211_MODE_11A, X(rcl7) }, { IEEE80211_MODE_11B, X(rcl10) }, { IEEE80211_MODE_11A, X(rcl11) }, #ifdef ATH_TURBO_SCAN { IEEE80211_MODE_TURBO_G, X(rcl12) }, #endif /* ATH_TURBO_SCAN */ { .list = NULL } }; /* * Start a station-mode scan by populating the channel list. */ static int sta_start(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; makescanlist(ss, vap, staScanTable); if (ss->ss_mindwell == 0) ss->ss_mindwell = msecs_to_ticks(20); /* 20ms */ if (ss->ss_maxdwell == 0) ss->ss_maxdwell = msecs_to_ticks(200); /* 200ms */ st->st_scangen++; st->st_newscan = 1; return 0; } /* * Restart a scan, typically a bg scan but can * also be a fg scan that came up empty. */ static int sta_restart(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; st->st_newscan = 1; return 0; } /* * Cancel an ongoing scan. */ static int sta_cancel(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { return 0; } /* * Demote any supplied 11g channel to 11b. There should * always be an 11b channel but we check anyway... */ static struct ieee80211_channel * demote11b(struct ieee80211vap *vap, struct ieee80211_channel *chan) { struct ieee80211_channel *c; if (IEEE80211_IS_CHAN_ANYG(chan) && vap->iv_des_mode == IEEE80211_MODE_AUTO) { c = ieee80211_find_channel(vap->iv_ic, chan->ic_freq, (chan->ic_flags &~ (IEEE80211_CHAN_PUREG | IEEE80211_CHAN_G)) | IEEE80211_CHAN_B); if (c != NULL) chan = c; } return chan; } static int maxrate(const struct ieee80211_scan_entry *se) { const struct ieee80211_ie_htcap *htcap = (const struct ieee80211_ie_htcap *) se->se_ies.htcap_ie; int rmax, r, i, txstream; uint16_t caps; uint8_t txparams; rmax = 0; if (htcap != NULL) { /* * HT station; inspect supported MCS and then adjust * rate by channel width. */ txparams = htcap->hc_mcsset[12]; if (txparams & 0x3) { /* * TX MCS parameters defined and not equal to RX, * extract the number of spartial streams and * map it to the highest MCS rate. */ txstream = ((txparams & 0xc) >> 2) + 1; i = txstream * 8 - 1; } else for (i = 31; i >= 0 && isclr(htcap->hc_mcsset, i); i--); if (i >= 0) { caps = le16dec(&htcap->hc_cap); if ((caps & IEEE80211_HTCAP_CHWIDTH40) && (caps & IEEE80211_HTCAP_SHORTGI40)) rmax = ieee80211_htrates[i].ht40_rate_400ns; else if (caps & IEEE80211_HTCAP_CHWIDTH40) rmax = ieee80211_htrates[i].ht40_rate_800ns; else if (caps & IEEE80211_HTCAP_SHORTGI20) rmax = ieee80211_htrates[i].ht20_rate_400ns; else rmax = ieee80211_htrates[i].ht20_rate_800ns; } } for (i = 0; i < se->se_rates[1]; i++) { r = se->se_rates[2+i] & IEEE80211_RATE_VAL; if (r > rmax) rmax = r; } for (i = 0; i < se->se_xrates[1]; i++) { r = se->se_xrates[2+i] & IEEE80211_RATE_VAL; if (r > rmax) rmax = r; } return rmax; } /* * Compare the capabilities of two entries and decide which is * more desirable (return >0 if a is considered better). Note * that we assume compatibility/usability has already been checked * so we don't need to (e.g. validate whether privacy is supported). * Used to select the best scan candidate for association in a BSS. * * TODO: should we take 11n, 11ac into account when selecting the * best? Right now it just compares frequency band and RSSI. */ static int sta_compare(const struct sta_entry *a, const struct sta_entry *b) { #define PREFER(_a,_b,_what) do { \ if (((_a) ^ (_b)) & (_what)) \ return ((_a) & (_what)) ? 1 : -1; \ } while (0) int maxa, maxb; int8_t rssia, rssib; int weight; /* privacy support */ PREFER(a->base.se_capinfo, b->base.se_capinfo, IEEE80211_CAPINFO_PRIVACY); /* compare count of previous failures */ weight = b->se_fails - a->se_fails; if (abs(weight) > 1) return weight; /* * Compare rssi. If the two are considered equivalent * then fallback to other criteria. We threshold the * comparisons to avoid selecting an ap purely by rssi * when both values may be good but one ap is otherwise * more desirable (e.g. an 11b-only ap with stronger * signal than an 11g ap). */ rssia = MIN(a->base.se_rssi, STA_RSSI_MAX); rssib = MIN(b->base.se_rssi, STA_RSSI_MAX); if (abs(rssib - rssia) < 5) { /* best/max rate preferred if signal level close enough XXX */ maxa = maxrate(&a->base); maxb = maxrate(&b->base); if (maxa != maxb) return maxa - maxb; /* XXX use freq for channel preference */ /* for now just prefer 5Ghz band to all other bands */ PREFER(IEEE80211_IS_CHAN_5GHZ(a->base.se_chan), IEEE80211_IS_CHAN_5GHZ(b->base.se_chan), 1); } /* all things being equal, use signal level */ return a->base.se_rssi - b->base.se_rssi; #undef PREFER } /* * Check rate set suitability and return the best supported rate. * XXX inspect MCS for HT */ static int check_rate(struct ieee80211vap *vap, const struct ieee80211_channel *chan, const struct ieee80211_scan_entry *se) { const struct ieee80211_rateset *srs; int i, j, nrs, r, okrate, badrate, fixedrate, ucastrate; const uint8_t *rs; okrate = badrate = 0; srs = ieee80211_get_suprates(vap->iv_ic, chan); nrs = se->se_rates[1]; rs = se->se_rates+2; /* XXX MCS */ ucastrate = vap->iv_txparms[ieee80211_chan2mode(chan)].ucastrate; fixedrate = IEEE80211_FIXED_RATE_NONE; again: for (i = 0; i < nrs; i++) { r = IEEE80211_RV(rs[i]); badrate = r; /* * Check any fixed rate is included. */ if (r == ucastrate) fixedrate = r; /* * Check against our supported rates. */ for (j = 0; j < srs->rs_nrates; j++) if (r == IEEE80211_RV(srs->rs_rates[j])) { if (r > okrate) /* NB: track max */ okrate = r; break; } if (j == srs->rs_nrates && (rs[i] & IEEE80211_RATE_BASIC)) { /* * Don't try joining a BSS, if we don't support * one of its basic rates. */ okrate = 0; goto back; } } if (rs == se->se_rates+2) { /* scan xrates too; sort of an algol68-style for loop */ nrs = se->se_xrates[1]; rs = se->se_xrates+2; goto again; } back: if (okrate == 0 || ucastrate != fixedrate) return badrate | IEEE80211_RATE_BASIC; else return IEEE80211_RV(okrate); } static __inline int match_id(const uint8_t *ie, const uint8_t *val, int len) { return (ie[1] == len && memcmp(ie+2, val, len) == 0); } static int match_ssid(const uint8_t *ie, int nssid, const struct ieee80211_scan_ssid ssids[]) { int i; for (i = 0; i < nssid; i++) { if (match_id(ie, ssids[i].ssid, ssids[i].len)) return 1; } return 0; } #ifdef IEEE80211_SUPPORT_TDMA static int tdma_isfull(const struct ieee80211_tdma_param *tdma) { int slot, slotcnt; slotcnt = tdma->tdma_slotcnt; for (slot = slotcnt-1; slot >= 0; slot--) if (isclr(tdma->tdma_inuse, slot)) return 0; return 1; } #endif /* IEEE80211_SUPPORT_TDMA */ /* * Test a scan candidate for suitability/compatibility. */ static int match_bss(struct ieee80211vap *vap, const struct ieee80211_scan_state *ss, struct sta_entry *se0, int debug) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_scan_entry *se = &se0->base; uint8_t rate; int fail; fail = 0; if (isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, se->se_chan))) fail |= MATCH_CHANNEL; /* * NB: normally the desired mode is used to construct * the channel list, but it's possible for the scan * cache to include entries for stations outside this * list so we check the desired mode here to weed them * out. */ if (vap->iv_des_mode != IEEE80211_MODE_AUTO && (se->se_chan->ic_flags & IEEE80211_CHAN_ALLTURBO) != chanflags[vap->iv_des_mode]) fail |= MATCH_CHANNEL; if (vap->iv_opmode == IEEE80211_M_IBSS) { if ((se->se_capinfo & IEEE80211_CAPINFO_IBSS) == 0) fail |= MATCH_CAPINFO; #ifdef IEEE80211_SUPPORT_TDMA } else if (vap->iv_opmode == IEEE80211_M_AHDEMO) { /* * Adhoc demo network setup shouldn't really be scanning * but just in case skip stations operating in IBSS or * BSS mode. */ if (se->se_capinfo & (IEEE80211_CAPINFO_IBSS|IEEE80211_CAPINFO_ESS)) fail |= MATCH_CAPINFO; /* * TDMA operation cannot coexist with a normal 802.11 network; * skip if IBSS or ESS capabilities are marked and require * the beacon have a TDMA ie present. */ if (vap->iv_caps & IEEE80211_C_TDMA) { const struct ieee80211_tdma_param *tdma = (const struct ieee80211_tdma_param *)se->se_ies.tdma_ie; const struct ieee80211_tdma_state *ts = vap->iv_tdma; if (tdma == NULL) fail |= MATCH_TDMA_NOIE; else if (tdma->tdma_version != ts->tdma_version) fail |= MATCH_TDMA_VERSION; else if (tdma->tdma_slot != 0) fail |= MATCH_TDMA_NOTMASTER; else if (tdma_isfull(tdma)) fail |= MATCH_TDMA_NOSLOT; #if 0 else if (ieee80211_local_address(se->se_macaddr)) fail |= MATCH_TDMA_LOCAL; #endif } #endif /* IEEE80211_SUPPORT_TDMA */ #ifdef IEEE80211_SUPPORT_MESH } else if (vap->iv_opmode == IEEE80211_M_MBSS) { const struct ieee80211_mesh_state *ms = vap->iv_mesh; /* * Mesh nodes have IBSS & ESS bits in capinfo turned off * and two special ie's that must be present. */ if (se->se_capinfo & (IEEE80211_CAPINFO_IBSS|IEEE80211_CAPINFO_ESS)) fail |= MATCH_CAPINFO; else if (se->se_meshid[0] != IEEE80211_ELEMID_MESHID) fail |= MATCH_MESH_NOID; else if (ms->ms_idlen != 0 && match_id(se->se_meshid, ms->ms_id, ms->ms_idlen)) fail |= MATCH_MESHID; #endif } else { if ((se->se_capinfo & IEEE80211_CAPINFO_ESS) == 0) fail |= MATCH_CAPINFO; /* * If 11d is enabled and we're attempting to join a bss * that advertises it's country code then compare our * current settings to what we fetched from the country ie. * If our country code is unspecified or different then do * not attempt to join the bss. We should have already * dispatched an event to user space that identifies the * new country code so our regdomain config should match. */ if ((IEEE80211_IS_CHAN_11D(se->se_chan) || (vap->iv_flags_ext & IEEE80211_FEXT_DOTD)) && se->se_cc[0] != 0 && (ic->ic_regdomain.country == CTRY_DEFAULT || !isocmp(se->se_cc, ic->ic_regdomain.isocc))) fail |= MATCH_CC; } if (vap->iv_flags & IEEE80211_F_PRIVACY) { if ((se->se_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) fail |= MATCH_PRIVACY; } else { /* XXX does this mean privacy is supported or required? */ if (se->se_capinfo & IEEE80211_CAPINFO_PRIVACY) fail |= MATCH_PRIVACY; } se0->se_flags &= ~STA_DEMOTE11B; rate = check_rate(vap, se->se_chan, se); if (rate & IEEE80211_RATE_BASIC) { fail |= MATCH_RATE; /* * An 11b-only ap will give a rate mismatch if there is an * OFDM fixed tx rate for 11g. Try downgrading the channel * in the scan list to 11b and retry the rate check. */ if (IEEE80211_IS_CHAN_ANYG(se->se_chan)) { rate = check_rate(vap, demote11b(vap, se->se_chan), se); if ((rate & IEEE80211_RATE_BASIC) == 0) { fail &= ~MATCH_RATE; se0->se_flags |= STA_DEMOTE11B; } } } else if (rate < 2*24) { /* * This is an 11b-only ap. Check the desired mode in * case that needs to be honored (mode 11g filters out * 11b-only ap's). Otherwise force any 11g channel used * in scanning to be demoted. * * NB: we cheat a bit here by looking at the max rate; * we could/should check the rates. */ if (!(vap->iv_des_mode == IEEE80211_MODE_AUTO || vap->iv_des_mode == IEEE80211_MODE_11B)) fail |= MATCH_RATE; else se0->se_flags |= STA_DEMOTE11B; } if (ss->ss_nssid != 0 && !match_ssid(se->se_ssid, ss->ss_nssid, ss->ss_ssid)) fail |= MATCH_SSID; if ((vap->iv_flags & IEEE80211_F_DESBSSID) && !IEEE80211_ADDR_EQ(vap->iv_des_bssid, se->se_bssid)) fail |= MATCH_BSSID; if (se0->se_fails >= STA_FAILS_MAX) fail |= MATCH_FAILS; if (se0->se_notseen >= STA_PURGE_SCANS) fail |= MATCH_NOTSEEN; if (se->se_rssi < STA_RSSI_MIN) fail |= MATCH_RSSI; #ifdef IEEE80211_DEBUG if (ieee80211_msg(vap, debug)) { - printf(" %c %s", + net80211_printf(" %c %s", fail & MATCH_FAILS ? '=' : fail & MATCH_NOTSEEN ? '^' : fail & MATCH_CC ? '$' : #ifdef IEEE80211_SUPPORT_TDMA fail & MATCH_TDMA_NOIE ? '&' : fail & MATCH_TDMA_VERSION ? 'v' : fail & MATCH_TDMA_NOTMASTER ? 's' : fail & MATCH_TDMA_NOSLOT ? 'f' : fail & MATCH_TDMA_LOCAL ? 'l' : #endif fail & MATCH_MESH_NOID ? 'm' : fail ? '-' : '+', ether_sprintf(se->se_macaddr)); - printf(" %s%c", ether_sprintf(se->se_bssid), + net80211_printf(" %s%c", ether_sprintf(se->se_bssid), fail & MATCH_BSSID ? '!' : ' '); - printf(" %3d%c", ieee80211_chan2ieee(ic, se->se_chan), + net80211_printf(" %3d%c", ieee80211_chan2ieee(ic, se->se_chan), fail & MATCH_CHANNEL ? '!' : ' '); - printf(" %+4d%c", se->se_rssi, fail & MATCH_RSSI ? '!' : ' '); - printf(" %2dM%c", (rate & IEEE80211_RATE_VAL) / 2, + net80211_printf(" %+4d%c", se->se_rssi, fail & MATCH_RSSI ? '!' : ' '); + net80211_printf(" %2dM%c", (rate & IEEE80211_RATE_VAL) / 2, fail & MATCH_RATE ? '!' : ' '); - printf(" %4s%c", + net80211_printf(" %4s%c", (se->se_capinfo & IEEE80211_CAPINFO_ESS) ? "ess" : (se->se_capinfo & IEEE80211_CAPINFO_IBSS) ? "ibss" : "", fail & MATCH_CAPINFO ? '!' : ' '); - printf(" %3s%c ", + net80211_printf(" %3s%c ", (se->se_capinfo & IEEE80211_CAPINFO_PRIVACY) ? "wep" : "no", fail & MATCH_PRIVACY ? '!' : ' '); ieee80211_print_essid(se->se_ssid+2, se->se_ssid[1]); - printf("%s\n", fail & (MATCH_SSID | MATCH_MESHID) ? "!" : ""); + net80211_printf("%s\n", fail & (MATCH_SSID | MATCH_MESHID) ? "!" : ""); } #endif return fail; } static void sta_update_notseen(struct sta_table *st) { struct sta_entry *se; IEEE80211_SCAN_TABLE_LOCK(st); TAILQ_FOREACH(se, &st->st_entry, se_list) { /* * If seen the reset and don't bump the count; * otherwise bump the ``not seen'' count. Note * that this insures that stations for which we * see frames while not scanning but not during * this scan will not be penalized. */ if (se->se_seen) se->se_seen = 0; else se->se_notseen++; } IEEE80211_SCAN_TABLE_UNLOCK(st); } static void sta_dec_fails(struct sta_table *st) { struct sta_entry *se; IEEE80211_SCAN_TABLE_LOCK(st); TAILQ_FOREACH(se, &st->st_entry, se_list) if (se->se_fails) se->se_fails--; IEEE80211_SCAN_TABLE_UNLOCK(st); } static struct sta_entry * select_bss(struct ieee80211_scan_state *ss, struct ieee80211vap *vap, int debug) { struct sta_table *st = ss->ss_priv; struct sta_entry *se, *selbs = NULL; IEEE80211_DPRINTF(vap, debug, " %s\n", "macaddr bssid chan rssi rate flag wep essid"); IEEE80211_SCAN_TABLE_LOCK(st); TAILQ_FOREACH(se, &st->st_entry, se_list) { ieee80211_ies_expand(&se->base.se_ies); if (match_bss(vap, ss, se, debug) == 0) { if (selbs == NULL) selbs = se; else if (sta_compare(se, selbs) > 0) selbs = se; } } IEEE80211_SCAN_TABLE_UNLOCK(st); return selbs; } /* * Pick an ap or ibss network to join or find a channel * to use to start an ibss network. */ static int sta_pick_bss(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; struct sta_entry *selbs; struct ieee80211_channel *chan; KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode %u", vap->iv_opmode)); if (st->st_newscan) { sta_update_notseen(st); st->st_newscan = 0; } if (ss->ss_flags & IEEE80211_SCAN_NOPICK) { /* * Manual/background scan, don't select+join the * bss, just return. The scanning framework will * handle notification that this has completed. */ ss->ss_flags &= ~IEEE80211_SCAN_NOPICK; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: nopick; return 1\n", __func__); return 1; } /* * Automatic sequencing; look for a candidate and * if found join the network. */ /* NB: unlocked read should be ok */ if (TAILQ_FIRST(&st->st_entry) == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: no scan candidate, join=%d, return 0\n", __func__, !! (ss->ss_flags & IEEE80211_SCAN_NOJOIN)); if (ss->ss_flags & IEEE80211_SCAN_NOJOIN) return 0; notfound: /* * If nothing suitable was found decrement * the failure counts so entries will be * reconsidered the next time around. We * really want to do this only for sta's * where we've previously had some success. */ sta_dec_fails(st); st->st_newscan = 1; return 0; /* restart scan */ } selbs = select_bss(ss, vap, IEEE80211_MSG_SCAN); if (ss->ss_flags & IEEE80211_SCAN_NOJOIN) return (selbs != NULL); if (selbs == NULL) goto notfound; chan = selbs->base.se_chan; if (selbs->se_flags & STA_DEMOTE11B) chan = demote11b(vap, chan); if (!ieee80211_sta_join(vap, chan, &selbs->base)) goto notfound; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: terminate scan; return 1\n", __func__); return 1; /* terminate scan */ } /* * Lookup an entry in the scan cache. We assume we're * called from the bottom half or such that we don't need * to block the bottom half so that it's safe to return * a reference to an entry w/o holding the lock on the table. */ static struct sta_entry * sta_lookup(struct sta_table *st, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct sta_entry *se; int hash = STA_HASH(macaddr); IEEE80211_SCAN_TABLE_LOCK(st); LIST_FOREACH(se, &st->st_hash[hash], se_hash) if (IEEE80211_ADDR_EQ(se->base.se_macaddr, macaddr)) break; IEEE80211_SCAN_TABLE_UNLOCK(st); return se; /* NB: unlocked */ } static void sta_roam_check(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni = vap->iv_bss; struct sta_table *st = ss->ss_priv; enum ieee80211_phymode mode; struct sta_entry *se, *selbs; uint8_t roamRate, curRate, ucastRate; int8_t roamRssi, curRssi; se = sta_lookup(st, ni->ni_macaddr); if (se == NULL) { /* XXX something is wrong */ return; } mode = ieee80211_chan2mode(ic->ic_bsschan); roamRate = vap->iv_roamparms[mode].rate; roamRssi = vap->iv_roamparms[mode].rssi; KASSERT(roamRate != 0 && roamRssi != 0, ("iv_roamparms are not" "initialized for %s mode!", ieee80211_phymode_name[mode])); ucastRate = vap->iv_txparms[mode].ucastrate; /* NB: the most up to date rssi is in the node, not the scan cache */ curRssi = ic->ic_node_getrssi(ni); if (ucastRate == IEEE80211_FIXED_RATE_NONE) { curRate = ieee80211_node_get_txrate_dot11rate(ni); IEEE80211_DPRINTF(vap, IEEE80211_MSG_ROAM, "%s: currssi %d currate %u roamrssi %d roamrate %u\n", __func__, curRssi, curRate, roamRssi, roamRate); } else { curRate = roamRate; /* NB: insure compare below fails */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_ROAM, "%s: currssi %d roamrssi %d\n", __func__, curRssi, roamRssi); } /* * Check if a new ap should be used and switch. * XXX deauth current ap */ if (curRate < roamRate || curRssi < roamRssi) { if (ieee80211_time_after(ticks, ic->ic_lastscan + vap->iv_scanvalid)) { /* * Scan cache contents are too old; force a scan now * if possible so we have current state to make a * decision with. We don't kick off a bg scan if * we're using dynamic turbo and boosted or if the * channel is busy. * XXX force immediate switch on scan complete */ if (!IEEE80211_IS_CHAN_DTURBO(ic->ic_curchan) && ((vap->iv_flags_ext & IEEE80211_FEXT_SCAN_OFFLOAD) || ieee80211_time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle))) ieee80211_bg_scan(vap, 0); return; } se->base.se_rssi = curRssi; selbs = select_bss(ss, vap, IEEE80211_MSG_ROAM); if (selbs != NULL && selbs != se) { struct ieee80211_channel *chan; IEEE80211_DPRINTF(vap, IEEE80211_MSG_ROAM | IEEE80211_MSG_DEBUG, "%s: ROAM: curRate %u, roamRate %u, " "curRssi %d, roamRssi %d\n", __func__, curRate, roamRate, curRssi, roamRssi); chan = selbs->base.se_chan; if (selbs->se_flags & STA_DEMOTE11B) chan = demote11b(vap, chan); (void) ieee80211_sta_join(vap, chan, &selbs->base); } } } /* * Age entries in the scan cache. * XXX also do roaming since it's convenient */ static void sta_age(struct ieee80211_scan_state *ss) { struct ieee80211vap *vap = ss->ss_vap; adhoc_age(ss); /* * If rate control is enabled check periodically to see if * we should roam from our current connection to one that * might be better. This only applies when we're operating * in sta mode and automatic roaming is set. * XXX defer if busy * XXX repeater station * XXX do when !bgscan? */ KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode %u", vap->iv_opmode)); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO && (vap->iv_flags & IEEE80211_F_BGSCAN) && vap->iv_state >= IEEE80211_S_RUN) /* XXX vap is implicit */ sta_roam_check(ss, vap); } /* * Iterate over the entries in the scan cache, invoking * the callback function on each one. */ static void sta_iterate(struct ieee80211_scan_state *ss, ieee80211_scan_iter_func *f, void *arg) { struct sta_table *st = ss->ss_priv; struct sta_entry *se; u_int gen; IEEE80211_SCAN_ITER_LOCK(st); gen = st->st_scaniter++; restart: IEEE80211_SCAN_TABLE_LOCK(st); TAILQ_FOREACH(se, &st->st_entry, se_list) { if (se->se_scangen != gen) { se->se_scangen = gen; /* update public state */ se->base.se_age = ticks - se->se_lastupdate; IEEE80211_SCAN_TABLE_UNLOCK(st); (*f)(arg, &se->base); goto restart; } } IEEE80211_SCAN_TABLE_UNLOCK(st); IEEE80211_SCAN_ITER_UNLOCK(st); } static void sta_assoc_fail(struct ieee80211_scan_state *ss, const uint8_t macaddr[IEEE80211_ADDR_LEN], int reason) { struct sta_table *st = ss->ss_priv; struct sta_entry *se; se = sta_lookup(st, macaddr); if (se != NULL) { se->se_fails++; se->se_lastfail = ticks; IEEE80211_NOTE_MAC(ss->ss_vap, IEEE80211_MSG_SCAN, macaddr, "%s: reason %u fails %u", __func__, reason, se->se_fails); } } static void sta_assoc_success(struct ieee80211_scan_state *ss, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct sta_table *st = ss->ss_priv; struct sta_entry *se; se = sta_lookup(st, macaddr); if (se != NULL) { #if 0 se->se_fails = 0; IEEE80211_NOTE_MAC(ss->ss_vap, IEEE80211_MSG_SCAN, macaddr, "%s: fails %u", __func__, se->se_fails); #endif se->se_lastassoc = ticks; } } static const struct ieee80211_scanner sta_default = { .scan_name = "default", .scan_attach = sta_attach, .scan_detach = sta_detach, .scan_start = sta_start, .scan_restart = sta_restart, .scan_cancel = sta_cancel, .scan_end = sta_pick_bss, .scan_flush = sta_flush, .scan_add = sta_add, .scan_age = sta_age, .scan_iterate = sta_iterate, .scan_assoc_fail = sta_assoc_fail, .scan_assoc_success = sta_assoc_success, }; IEEE80211_SCANNER_ALG(sta, IEEE80211_M_STA, sta_default); /* * Adhoc mode-specific support. */ static const uint16_t adhocWorld[] = /* 36, 40, 44, 48 */ { 5180, 5200, 5220, 5240 }; static const uint16_t adhocFcc3[] = /* 36, 40, 44, 48 145, 149, 153, 157, 161, 165 */ { 5180, 5200, 5220, 5240, 5725, 5745, 5765, 5785, 5805, 5825 }; static const uint16_t adhocMkk[] = /* 34, 38, 42, 46 */ { 5170, 5190, 5210, 5230 }; static const uint16_t adhoc11b[] = /* 10, 11 */ { 2457, 2462 }; static const struct scanlist adhocScanTable[] = { { IEEE80211_MODE_11B, X(adhoc11b) }, { IEEE80211_MODE_11A, X(adhocWorld) }, { IEEE80211_MODE_11A, X(adhocFcc3) }, { IEEE80211_MODE_11B, X(adhocMkk) }, { .list = NULL } }; #undef X /* * Start an adhoc-mode scan by populating the channel list. */ static int adhoc_start(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; makescanlist(ss, vap, adhocScanTable); if (ss->ss_mindwell == 0) ss->ss_mindwell = msecs_to_ticks(200); /* 200ms */ if (ss->ss_maxdwell == 0) ss->ss_maxdwell = msecs_to_ticks(200); /* 200ms */ st->st_scangen++; st->st_newscan = 1; return 0; } /* * Select a channel to start an adhoc network on. * The channel list was populated with appropriate * channels so select one that looks least occupied. */ static struct ieee80211_channel * adhoc_pick_channel(struct ieee80211_scan_state *ss, int flags) { struct sta_table *st = ss->ss_priv; struct sta_entry *se; struct ieee80211_channel *c, *bestchan; int i, bestrssi, maxrssi; bestchan = NULL; bestrssi = -1; IEEE80211_SCAN_TABLE_LOCK(st); for (i = 0; i < ss->ss_last; i++) { c = ss->ss_chans[i]; /* never consider a channel with radar */ if (IEEE80211_IS_CHAN_RADAR(c)) continue; /* skip channels disallowed by regulatory settings */ if (IEEE80211_IS_CHAN_NOADHOC(c)) continue; /* check channel attributes for band compatibility */ if (flags != 0 && (c->ic_flags & flags) != flags) continue; maxrssi = 0; TAILQ_FOREACH(se, &st->st_entry, se_list) { if (se->base.se_chan != c) continue; if (se->base.se_rssi > maxrssi) maxrssi = se->base.se_rssi; } if (bestchan == NULL || maxrssi < bestrssi) bestchan = c; } IEEE80211_SCAN_TABLE_UNLOCK(st); return bestchan; } /* * Pick an ibss network to join or find a channel * to use to start an ibss network. */ static int adhoc_pick_bss(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; struct sta_entry *selbs; struct ieee80211_channel *chan; struct ieee80211com *ic = vap->iv_ic; KASSERT(vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_AHDEMO || vap->iv_opmode == IEEE80211_M_MBSS, ("wrong opmode %u", vap->iv_opmode)); if (st->st_newscan) { sta_update_notseen(st); st->st_newscan = 0; } if (ss->ss_flags & IEEE80211_SCAN_NOPICK) { /* * Manual/background scan, don't select+join the * bss, just return. The scanning framework will * handle notification that this has completed. */ ss->ss_flags &= ~IEEE80211_SCAN_NOPICK; return 1; } /* * Automatic sequencing; look for a candidate and * if found join the network. */ /* NB: unlocked read should be ok */ if (TAILQ_FIRST(&st->st_entry) == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: no scan candidate\n", __func__); if (ss->ss_flags & IEEE80211_SCAN_NOJOIN) return 0; notfound: /* NB: never auto-start a tdma network for slot !0 */ #ifdef IEEE80211_SUPPORT_TDMA if (vap->iv_des_nssid && ((vap->iv_caps & IEEE80211_C_TDMA) == 0 || ieee80211_tdma_getslot(vap) == 0)) { #else if (vap->iv_des_nssid) { #endif /* * No existing adhoc network to join and we have * an ssid; start one up. If no channel was * specified, try to select a channel. */ if (vap->iv_des_chan == IEEE80211_CHAN_ANYC || IEEE80211_IS_CHAN_RADAR(vap->iv_des_chan)) { chan = adhoc_pick_channel(ss, 0); } else chan = vap->iv_des_chan; if (chan != NULL) { /* * Create a HT capable IBSS; the per-node * probe request/response will result in * "correct" rate control capabilities being * negotiated. */ chan = ieee80211_ht_adjust_channel(ic, chan, vap->iv_flags_ht); chan = ieee80211_vht_adjust_channel(ic, chan, vap->iv_vht_flags); ieee80211_create_ibss(vap, chan); return 1; } } /* * If nothing suitable was found decrement * the failure counts so entries will be * reconsidered the next time around. We * really want to do this only for sta's * where we've previously had some success. */ sta_dec_fails(st); st->st_newscan = 1; return 0; /* restart scan */ } selbs = select_bss(ss, vap, IEEE80211_MSG_SCAN); if (ss->ss_flags & IEEE80211_SCAN_NOJOIN) return (selbs != NULL); if (selbs == NULL) goto notfound; chan = selbs->base.se_chan; if (selbs->se_flags & STA_DEMOTE11B) chan = demote11b(vap, chan); /* * If HT is available, make it a possibility here. * The intent is to enable HT20/HT40 when joining a non-HT * IBSS node; we can then advertise HT IEs and speak HT * to any subsequent nodes that support it. */ chan = ieee80211_ht_adjust_channel(ic, chan, vap->iv_flags_ht); chan = ieee80211_vht_adjust_channel(ic, chan, vap->iv_vht_flags); if (!ieee80211_sta_join(vap, chan, &selbs->base)) goto notfound; return 1; /* terminate scan */ } /* * Age entries in the scan cache. */ static void adhoc_age(struct ieee80211_scan_state *ss) { struct sta_table *st = ss->ss_priv; struct sta_entry *se, *next; IEEE80211_SCAN_TABLE_LOCK(st); TAILQ_FOREACH_SAFE(se, &st->st_entry, se_list, next) { if (se->se_notseen > STA_PURGE_SCANS) { TAILQ_REMOVE(&st->st_entry, se, se_list); LIST_REMOVE(se, se_hash); ieee80211_ies_cleanup(&se->base.se_ies); IEEE80211_FREE(se, M_80211_SCAN); } } IEEE80211_SCAN_TABLE_UNLOCK(st); } static const struct ieee80211_scanner adhoc_default = { .scan_name = "default", .scan_attach = sta_attach, .scan_detach = sta_detach, .scan_start = adhoc_start, .scan_restart = sta_restart, .scan_cancel = sta_cancel, .scan_end = adhoc_pick_bss, .scan_flush = sta_flush, .scan_pickchan = adhoc_pick_channel, .scan_add = sta_add, .scan_age = adhoc_age, .scan_iterate = sta_iterate, .scan_assoc_fail = sta_assoc_fail, .scan_assoc_success = sta_assoc_success, }; IEEE80211_SCANNER_ALG(ibss, IEEE80211_M_IBSS, adhoc_default); IEEE80211_SCANNER_ALG(ahdemo, IEEE80211_M_AHDEMO, adhoc_default); static int ap_start(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; makescanlist(ss, vap, staScanTable); if (ss->ss_mindwell == 0) ss->ss_mindwell = msecs_to_ticks(200); /* 200ms */ if (ss->ss_maxdwell == 0) ss->ss_maxdwell = msecs_to_ticks(200); /* 200ms */ st->st_scangen++; st->st_newscan = 1; return 0; } /* * Cancel an ongoing scan. */ static int ap_cancel(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { return 0; } /* * Pick a quiet channel to use for ap operation. */ static struct ieee80211_channel * ap_pick_channel(struct ieee80211_scan_state *ss, int flags) { struct sta_table *st = ss->ss_priv; struct ieee80211_channel *bestchan = NULL; int i; /* XXX select channel more intelligently, e.g. channel spread, power */ /* NB: use scan list order to preserve channel preference */ for (i = 0; i < ss->ss_last; i++) { struct ieee80211_channel *chan = ss->ss_chans[i]; /* * If the channel is unoccupied the max rssi * should be zero; just take it. Otherwise * track the channel with the lowest rssi and * use that when all channels appear occupied. */ if (IEEE80211_IS_CHAN_RADAR(chan)) continue; if (IEEE80211_IS_CHAN_NOHOSTAP(chan)) continue; /* check channel attributes for band compatibility */ if (flags != 0 && (chan->ic_flags & flags) != flags) continue; KASSERT(sizeof(chan->ic_ieee) == 1, ("ic_chan size")); /* XXX channel have interference */ if (st->st_maxrssi[chan->ic_ieee] == 0) { /* XXX use other considerations */ return chan; } if (bestchan == NULL || st->st_maxrssi[chan->ic_ieee] < st->st_maxrssi[bestchan->ic_ieee]) bestchan = chan; } return bestchan; } /* * Pick a quiet channel to use for ap operation. */ static int ap_end(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel *bestchan, *chan; KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP, ("wrong opmode %u", vap->iv_opmode)); bestchan = ap_pick_channel(ss, 0); if (bestchan == NULL) { /* no suitable channel, should not happen */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: no suitable channel! (should not happen)\n", __func__); /* XXX print something? */ return 0; /* restart scan */ } /* * If this is a dynamic turbo channel, start with the unboosted one. */ if (IEEE80211_IS_CHAN_TURBO(bestchan)) { bestchan = ieee80211_find_channel(ic, bestchan->ic_freq, bestchan->ic_flags & ~IEEE80211_CHAN_TURBO); if (bestchan == NULL) { /* should never happen ?? */ return 0; } } if (ss->ss_flags & (IEEE80211_SCAN_NOPICK | IEEE80211_SCAN_NOJOIN)) { /* * Manual/background scan, don't select+join the * bss, just return. The scanning framework will * handle notification that this has completed. */ ss->ss_flags &= ~IEEE80211_SCAN_NOPICK; return 1; } chan = ieee80211_ht_adjust_channel(ic, bestchan, vap->iv_flags_ht); chan = ieee80211_vht_adjust_channel(ic, chan, vap->iv_vht_flags); ieee80211_create_ibss(vap, chan); return 1; } static const struct ieee80211_scanner ap_default = { .scan_name = "default", .scan_attach = sta_attach, .scan_detach = sta_detach, .scan_start = ap_start, .scan_restart = sta_restart, .scan_cancel = ap_cancel, .scan_end = ap_end, .scan_flush = sta_flush, .scan_pickchan = ap_pick_channel, .scan_add = sta_add, .scan_age = adhoc_age, .scan_iterate = sta_iterate, .scan_assoc_success = sta_assoc_success, .scan_assoc_fail = sta_assoc_fail, }; IEEE80211_SCANNER_ALG(ap, IEEE80211_M_HOSTAP, ap_default); #ifdef IEEE80211_SUPPORT_MESH /* * Pick an mbss network to join or find a channel * to use to start an mbss network. */ static int mesh_pick_bss(struct ieee80211_scan_state *ss, struct ieee80211vap *vap) { struct sta_table *st = ss->ss_priv; struct ieee80211_mesh_state *ms = vap->iv_mesh; struct sta_entry *selbs; struct ieee80211_channel *chan; KASSERT(vap->iv_opmode == IEEE80211_M_MBSS, ("wrong opmode %u", vap->iv_opmode)); if (st->st_newscan) { sta_update_notseen(st); st->st_newscan = 0; } if (ss->ss_flags & IEEE80211_SCAN_NOPICK) { /* * Manual/background scan, don't select+join the * bss, just return. The scanning framework will * handle notification that this has completed. */ ss->ss_flags &= ~IEEE80211_SCAN_NOPICK; return 1; } /* * Automatic sequencing; look for a candidate and * if found join the network. */ /* NB: unlocked read should be ok */ if (TAILQ_FIRST(&st->st_entry) == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: no scan candidate\n", __func__); if (ss->ss_flags & IEEE80211_SCAN_NOJOIN) return 0; notfound: if (ms->ms_idlen != 0) { /* * No existing mbss network to join and we have * a meshid; start one up. If no channel was * specified, try to select a channel. */ if (vap->iv_des_chan == IEEE80211_CHAN_ANYC || IEEE80211_IS_CHAN_RADAR(vap->iv_des_chan)) { struct ieee80211com *ic = vap->iv_ic; /* XXX VHT */ chan = adhoc_pick_channel(ss, 0); if (chan != NULL) { chan = ieee80211_ht_adjust_channel(ic, chan, vap->iv_flags_ht); chan = ieee80211_vht_adjust_channel(ic, chan, vap->iv_vht_flags); } } else chan = vap->iv_des_chan; if (chan != NULL) { ieee80211_create_ibss(vap, chan); return 1; } } /* * If nothing suitable was found decrement * the failure counts so entries will be * reconsidered the next time around. We * really want to do this only for sta's * where we've previously had some success. */ sta_dec_fails(st); st->st_newscan = 1; return 0; /* restart scan */ } selbs = select_bss(ss, vap, IEEE80211_MSG_SCAN); if (ss->ss_flags & IEEE80211_SCAN_NOJOIN) return (selbs != NULL); if (selbs == NULL) goto notfound; chan = selbs->base.se_chan; if (selbs->se_flags & STA_DEMOTE11B) chan = demote11b(vap, chan); if (!ieee80211_sta_join(vap, chan, &selbs->base)) goto notfound; return 1; /* terminate scan */ } static const struct ieee80211_scanner mesh_default = { .scan_name = "default", .scan_attach = sta_attach, .scan_detach = sta_detach, .scan_start = adhoc_start, .scan_restart = sta_restart, .scan_cancel = sta_cancel, .scan_end = mesh_pick_bss, .scan_flush = sta_flush, .scan_pickchan = adhoc_pick_channel, .scan_add = sta_add, .scan_age = adhoc_age, .scan_iterate = sta_iterate, .scan_assoc_fail = sta_assoc_fail, .scan_assoc_success = sta_assoc_success, }; IEEE80211_SCANNER_ALG(mesh, IEEE80211_M_MBSS, mesh_default); #endif /* IEEE80211_SUPPORT_MESH */ diff --git a/sys/net80211/ieee80211_sta.c b/sys/net80211/ieee80211_sta.c index 9fc2bf8b7f68..a41e3f5793dd 100644 --- a/sys/net80211/ieee80211_sta.c +++ b/sys/net80211/ieee80211_sta.c @@ -1,2065 +1,2064 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * 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. */ /* * IEEE 802.11 Station mode support. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #include #include #include static void sta_vattach(struct ieee80211vap *); static void sta_beacon_miss(struct ieee80211vap *); static int sta_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int sta_input(struct ieee80211_node *, struct mbuf *, const struct ieee80211_rx_stats *, int, int); static void sta_recv_mgmt(struct ieee80211_node *, struct mbuf *, int subtype, const struct ieee80211_rx_stats *, int rssi, int nf); static void sta_recv_ctl(struct ieee80211_node *, struct mbuf *, int subtype); void ieee80211_sta_attach(struct ieee80211com *ic) { ic->ic_vattach[IEEE80211_M_STA] = sta_vattach; } void ieee80211_sta_detach(struct ieee80211com *ic) { } static void sta_vdetach(struct ieee80211vap *vap) { } static void sta_vattach(struct ieee80211vap *vap) { vap->iv_newstate = sta_newstate; vap->iv_input = sta_input; vap->iv_recv_mgmt = sta_recv_mgmt; vap->iv_recv_ctl = sta_recv_ctl; vap->iv_opdetach = sta_vdetach; vap->iv_bmiss = sta_beacon_miss; } /* * Handle a beacon miss event. The common code filters out * spurious events that can happen when scanning and/or before * reaching RUN state. */ static void sta_beacon_miss(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK_ASSERT(ic); KASSERT((ic->ic_flags & IEEE80211_F_SCAN) == 0, ("scanning")); KASSERT(vap->iv_state >= IEEE80211_S_RUN, ("wrong state %s", ieee80211_state_name[vap->iv_state])); IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "beacon miss, mode %s state %s\n", ieee80211_opmode_name[vap->iv_opmode], ieee80211_state_name[vap->iv_state]); if (vap->iv_state == IEEE80211_S_CSA) { /* * A Channel Switch is pending; assume we missed the * beacon that would've completed the process and just * force the switch. If we made a mistake we'll not * find the AP on the new channel and fall back to a * normal scan. */ ieee80211_csa_completeswitch(ic); return; } if (++vap->iv_bmiss_count < vap->iv_bmiss_max) { /* * Send a directed probe req before falling back to a * scan; if we receive a response ic_bmiss_count will * be reset. Some cards mistakenly report beacon miss * so this avoids the expensive scan if the ap is * still there. */ ieee80211_send_probereq(vap->iv_bss, vap->iv_myaddr, vap->iv_bss->ni_bssid, vap->iv_bss->ni_bssid, vap->iv_bss->ni_essid, vap->iv_bss->ni_esslen); return; } callout_stop(&vap->iv_swbmiss); vap->iv_bmiss_count = 0; vap->iv_stats.is_beacon_miss++; if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { #ifdef IEEE80211_SUPPORT_SUPERG /* * If we receive a beacon miss interrupt when using * dynamic turbo, attempt to switch modes before * reassociating. */ if (IEEE80211_ATH_CAP(vap, vap->iv_bss, IEEE80211_NODE_TURBOP)) ieee80211_dturbo_switch(vap, ic->ic_bsschan->ic_flags ^ IEEE80211_CHAN_TURBO); #endif /* * Try to reassociate before scanning for a new ap. */ ieee80211_new_state(vap, IEEE80211_S_ASSOC, 1); } else { /* * Somebody else is controlling state changes (e.g. * a user-mode app) don't do anything that would * confuse them; just drop into scan mode so they'll * notified of the state change and given control. */ ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); } } /* * Handle deauth with reason. We retry only for * the cases where we might succeed. Otherwise * we downgrade the ap and scan. */ static void sta_authretry(struct ieee80211vap *vap, struct ieee80211_node *ni, int reason) { switch (reason) { case IEEE80211_STATUS_SUCCESS: /* NB: MLME assoc */ case IEEE80211_STATUS_TIMEOUT: case IEEE80211_REASON_ASSOC_EXPIRE: case IEEE80211_REASON_NOT_AUTHED: case IEEE80211_REASON_NOT_ASSOCED: case IEEE80211_REASON_ASSOC_LEAVE: case IEEE80211_REASON_ASSOC_NOT_AUTHED: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 1); break; default: ieee80211_scan_assoc_fail(vap, vap->iv_bss->ni_macaddr, reason); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) ieee80211_check_scan_current(vap); break; } } static void sta_swbmiss_start(struct ieee80211vap *vap) { if (vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS) { /* * Start s/w beacon miss timer for devices w/o * hardware support. We fudge a bit here since * we're doing this in software. */ vap->iv_swbmiss_period = IEEE80211_TU_TO_TICKS( 2 * vap->iv_bmissthreshold * vap->iv_bss->ni_intval); vap->iv_swbmiss_count = 0; callout_reset(&vap->iv_swbmiss, vap->iv_swbmiss_period, ieee80211_swbmiss, vap); } } /* * IEEE80211_M_STA vap state machine handler. * This routine handles the main states in the 802.11 protocol. */ static int sta_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; enum ieee80211_state ostate; IEEE80211_LOCK_ASSERT(ic); ostate = vap->iv_state; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (%d)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg); 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); switch (nstate) { case IEEE80211_S_INIT: switch (ostate) { case IEEE80211_S_SLEEP: /* XXX wakeup */ /* XXX driver hook to wakeup the hardware? */ case IEEE80211_S_RUN: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DISASSOC, IEEE80211_REASON_ASSOC_LEAVE); ieee80211_sta_leave(ni); break; case IEEE80211_S_ASSOC: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_AUTH_LEAVE); break; 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); } if (vap->iv_auth->ia_detach != NULL) vap->iv_auth->ia_detach(vap); break; case IEEE80211_S_SCAN: switch (ostate) { case IEEE80211_S_INIT: /* * Initiate a scan. We can come here as a result * of an IEEE80211_IOC_SCAN_REQ too in which case * the vap will be marked with IEEE80211_FEXT_SCANREQ * and the scan request parameters will be present * in iv_scanreq. Otherwise we do the default. */ if (vap->iv_flags_ext & IEEE80211_FEXT_SCANREQ) { ieee80211_check_scan(vap, vap->iv_scanreq_flags, vap->iv_scanreq_duration, vap->iv_scanreq_mindwell, vap->iv_scanreq_maxdwell, vap->iv_scanreq_nssid, vap->iv_scanreq_ssid); vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANREQ; } else ieee80211_check_scan_current(vap); break; case IEEE80211_S_SCAN: case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: /* * These can happen either because of a timeout * on an assoc/auth response or because of a * change in state that requires a reset. For * the former we're called with a non-zero arg * that is the cause for the failure; pass this * to the scan code so it can update state. * Otherwise trigger a new scan unless we're in * manual roaming mode in which case an application * must issue an explicit scan request. */ if (arg != 0) ieee80211_scan_assoc_fail(vap, vap->iv_bss->ni_macaddr, arg); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) ieee80211_check_scan_current(vap); break; case IEEE80211_S_SLEEP: /* beacon miss */ /* * XXX if in sleep we need to wakeup the hardware. */ /* FALLTHROUGH */ case IEEE80211_S_RUN: /* beacon miss */ /* * Beacon miss. Notify user space and if not * under control of a user application (roaming * manual) kick off a scan to re-connect. */ ieee80211_sta_leave(ni); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) ieee80211_check_scan_current(vap); break; default: goto invalid; } break; case IEEE80211_S_AUTH: switch (ostate) { case IEEE80211_S_INIT: case IEEE80211_S_SCAN: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 1); break; case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: switch (arg & 0xff) { case IEEE80211_FC0_SUBTYPE_AUTH: /* ??? */ IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 2); break; case IEEE80211_FC0_SUBTYPE_DEAUTH: sta_authretry(vap, ni, arg>>8); break; } break; case IEEE80211_S_SLEEP: case IEEE80211_S_RUN: switch (arg & 0xff) { case IEEE80211_FC0_SUBTYPE_AUTH: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 2); vap->iv_state = IEEE80211_S_RUN; /* stay RUN */ break; case IEEE80211_FC0_SUBTYPE_DEAUTH: ieee80211_sta_leave(ni); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { /* try to reauth */ IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 1); } break; } break; default: goto invalid; } break; case IEEE80211_S_ASSOC: switch (ostate) { case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_ASSOC_REQ, 0); break; case IEEE80211_S_SLEEP: /* cannot happen */ case IEEE80211_S_RUN: ieee80211_sta_leave(ni); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { IEEE80211_SEND_MGMT(ni, arg ? IEEE80211_FC0_SUBTYPE_REASSOC_REQ : IEEE80211_FC0_SUBTYPE_ASSOC_REQ, 0); } break; default: goto invalid; } break; case IEEE80211_S_RUN: if (vap->iv_flags & IEEE80211_F_WPA) { /* XXX validate prerequisites */ } switch (ostate) { case IEEE80211_S_RUN: case IEEE80211_S_CSA: break; case IEEE80211_S_AUTH: /* when join is done in fw */ case IEEE80211_S_ASSOC: #ifdef IEEE80211_DEBUG if (ieee80211_msg_debug(vap)) { ieee80211_note(vap, "%s with %s ssid ", (vap->iv_opmode == IEEE80211_M_STA ? "associated" : "synchronized"), ether_sprintf(ni->ni_bssid)); ieee80211_print_essid(vap->iv_bss->ni_essid, ni->ni_esslen); - printf(" channel %d start %uMbit/s\n", + net80211_printf(" channel %d start %uMbit/s\n", ieee80211_chan2ieee(ic, ic->ic_curchan), ieee80211_node_get_txrate_kbit(ni) / 1000); } #endif ieee80211_scan_assoc_success(vap, ni->ni_macaddr); ieee80211_notify_node_join(ni, arg == IEEE80211_FC0_SUBTYPE_ASSOC_RESP); break; case IEEE80211_S_SLEEP: /* Wake up from sleep */ vap->iv_sta_ps(vap, 0); break; default: goto invalid; } ieee80211_sync_curchan(ic); if (ostate != IEEE80211_S_RUN) sta_swbmiss_start(vap); /* * 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); /* * Fake association when joining an existing bss. * * Don't do this if we're doing SLEEP->RUN. */ if (ic->ic_newassoc != NULL && ostate != IEEE80211_S_SLEEP) ic->ic_newassoc(vap->iv_bss, (ostate != IEEE80211_S_RUN)); break; case IEEE80211_S_CSA: if (ostate != IEEE80211_S_RUN) goto invalid; break; case IEEE80211_S_SLEEP: sta_swbmiss_start(vap); vap->iv_sta_ps(vap, 1); break; default: invalid: IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: unexpected state transition %s -> %s\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); break; } return 0; } /* * Return non-zero if the frame is an echo of a multicast * frame sent by ourself. The dir is known to be DSTODS. */ static __inline int isdstods_mcastecho(struct ieee80211vap *vap, const struct ieee80211_frame *wh) { #define QWH4(wh) ((const struct ieee80211_qosframe_addr4 *)wh) #define WH4(wh) ((const struct ieee80211_frame_addr4 *)wh) const uint8_t *sa; KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode")); if (!IEEE80211_IS_MULTICAST(wh->i_addr3)) return 0; sa = IEEE80211_QOS_HAS_SEQ(wh) ? QWH4(wh)->i_addr4 : WH4(wh)->i_addr4; return IEEE80211_ADDR_EQ(sa, vap->iv_myaddr); #undef WH4 #undef QWH4 } /* * Return non-zero if the frame is an echo of a multicast * frame sent by ourself. The dir is known to be FROMDS. */ static __inline int isfromds_mcastecho(struct ieee80211vap *vap, const struct ieee80211_frame *wh) { KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode")); if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) return 0; return IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_myaddr); } /* * 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 0; } return 1; } /* * 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 sta_input(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { 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 = 1; /* mbuf need to be tapped. */ uint8_t dir, type, subtype, qos; uint8_t *bssid; int is_hw_decrypted = 0; int has_decrypted = 0; KASSERT(ni != NULL, ("%s: null node, mbuf %p", __func__, m)); /* Early init in case of early error case. */ type = -1; /* * 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 (or other shorter frames) w/o first * validating the data is present. */ wh = mtod(m, struct ieee80211_frame *); if (m->m_pkthdr.len < 2 || m->m_pkthdr.len < ieee80211_anyhdrsize(wh)) { 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 err; } if (!IEEE80211_IS_FC0_CHECK_VER(wh, IEEE80211_FC0_VERSION_0)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "wrong version, fc %02x:%02x", wh->i_fc[0], wh->i_fc[1]); vap->iv_stats.is_rx_badversion++; goto err; } /* * Some devices do hardware decryption all the way through * to pretending the frame wasn't encrypted in the first place. * So, tag it appropriately so it isn't discarded inappropriately. */ if ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_DECRYPTED)) is_hw_decrypted = 1; if (m->m_flags & M_AMPDU_MPDU) { /* * Fastpath for A-MPDU reorder q resubmission. Frames * w/ M_AMPDU_MPDU 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_MPDU flag and we can bypass most of * the normal processing. */ type = IEEE80211_FC0_TYPE_DATA; dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; subtype = IEEE80211_FC0_SUBTYPE_QOS_DATA; hdrspace = ieee80211_hdrspace(ic, wh); /* XXX optimize? */ goto resubmit_ampdu; } ni->ni_inact = ni->ni_inact_reload; 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; /* * Control frames are not folowing the header scheme of data and mgmt * frames so we do not apply extra checks here. * We probably should do checks on RA (+TA) where available for those * too, but for now do not drop them. */ if (type != IEEE80211_FC0_TYPE_CTL && (ic->ic_flags & IEEE80211_F_SCAN) == 0) { bssid = wh->i_addr2; if (!IEEE80211_ADDR_EQ(bssid, ni->ni_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; } /* * Some devices may be in a promiscuous mode * where they receive frames for multiple station * addresses. * * If we receive a data frame that isn't * destined to our VAP MAC, drop it. * * XXX TODO: This is only enforced when not scanning; * XXX it assumes a software-driven scan will put the NIC * XXX into a "no data frames" mode before setting this * XXX flag. Otherwise it may be possible that we'll still * XXX process data frames whilst scanning. */ if ((! IEEE80211_IS_MULTICAST(wh->i_addr1)) && (! IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr))) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, bssid, NULL, "not to cur sta: lladdr=%6D, addr1=%6D", vap->iv_myaddr, ":", wh->i_addr1, ":"); vap->iv_stats.is_rx_wrongbss++; goto out; } IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; if ( IEEE80211_HAS_SEQ(type, subtype) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { 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++; if (! ieee80211_check_rxseq(ni, wh, bssid, rxs)) goto out; } } 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 */ } /* * Handle 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 ((m->m_flags & M_AMPDU) && (dir == IEEE80211_FC1_DIR_FROMDS || dir == IEEE80211_FC1_DIR_DSTODS) && ieee80211_ampdu_reorder(ni, m, rxs) != 0) { m = NULL; goto out; } resubmit_ampdu: if (dir == IEEE80211_FC1_DIR_FROMDS) { if (ieee80211_vap_ifp_check_is_simplex(vap) && isfromds_mcastecho(vap, wh)) { /* * In IEEE802.11 network, multicast * packets sent from "me" are broadcast * from the AP; silently discard for * SIMPLEX interface. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "%s", "multicast echo"); vap->iv_stats.is_rx_mcastecho++; goto out; } if ((vap->iv_flags & IEEE80211_F_DWDS) && IEEE80211_IS_MULTICAST(wh->i_addr1)) { /* * DWDS sta's must drop 3-address mcast frames * as they will be sent separately as a 4-addr * frame. Accepting the 3-addr frame will * confuse the bridge into thinking the sending * sta is located at the end of WDS link. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "3-address data", "%s", "DWDS enabled"); vap->iv_stats.is_rx_mcastecho++; goto out; } } else if (dir == IEEE80211_FC1_DIR_DSTODS) { if ((vap->iv_flags & IEEE80211_F_DWDS) == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "4-address data", "%s", "DWDS not enabled"); vap->iv_stats.is_rx_wrongdir++; goto out; } if (ieee80211_vap_ifp_check_is_simplex(vap) && isdstods_mcastecho(vap, wh)) { /* * In IEEE802.11 network, multicast * packets sent from "me" are broadcast * from the AP; silently discard for * SIMPLEX interface. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "4-address data", "%s", "multicast echo"); vap->iv_stats.is_rx_mcastecho++; goto out; } } else { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "incorrect dir 0x%x", dir); vap->iv_stats.is_rx_wrongdir++; goto out; } /* * Handle privacy requirements for hardware decryption * devices. * * For those devices, a handful of things happen. * * + If IV has been stripped, then we can't run * ieee80211_crypto_decap() - none of the key * + If MIC has been stripped, we can't validate * MIC here. * + If MIC fails, then we need to communicate a * MIC failure up to the stack - but we don't know * which key was used. */ /* * 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 (is_hw_decrypted || IEEE80211_IS_PROTECTED(wh)) { 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; } if (ieee80211_crypto_decap(ni, m, hdrspace, &key) == 0) { /* 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_PROTECTED; has_decrypted = 1; } 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_DATA) qos = ieee80211_getqos(wh)[0]; else qos = 0; /* * Next up, any fragmentation. */ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { m = ieee80211_defrag(ni, m, hdrspace, has_decrypted); 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. * * Note: we can't do MIC stripping/verification if the * upper layer has stripped it. We have to check MIC * ourselves. So, key may be NULL, but we have to check * the RX status. */ if (!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 (ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); need_tap = 0; /* * Finally, strip the 802.11 header. */ m = ieee80211_decap(vap, m, hdrspace, qos); 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; } if (!(qos & IEEE80211_QOS_AMSDU)) eh = mtod(m, struct ether_header *); else eh = NULL; 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 == NULL || eh->ether_type != htons(ETHERTYPE_PAE)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "unauthorized or " "unknown port: ether type 0x%x len %u", eh == NULL ? -1 : 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) && ((has_decrypted == 0) && (m->m_flags & M_WEP) == 0) && (is_hw_decrypted == 0) && (eh == NULL || 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 { #ifdef IEEE80211_SUPPORT_SUPERG m = ieee80211_decap_fastframe(vap, ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; #endif } 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) && doprint(vap, subtype)) || ieee80211_msg_dumppkts(vap)) { net80211_vap_printf(vap, "received %s from %s rssi %d\n", ieee80211_mgt_subtype_name(subtype), ether_sprintf(wh->i_addr2), rssi); } #endif /* * Note: See above for hardware offload privacy requirements. * It also applies here. */ /* * Again, having encrypted flag set check would be good, but * then we have to also handle crypto_decap() like above. */ if (IEEE80211_IS_PROTECTED(wh)) { 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), "%s", "WEP set but not permitted"); vap->iv_stats.is_rx_mgtdiscard++; /* XXX */ goto out; } if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { /* * Discard encrypted frames when privacy is off. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "mgt", "%s", "WEP set but PRIVACY off"); vap->iv_stats.is_rx_noprivacy++; goto out; } hdrspace = ieee80211_hdrspace(ic, wh); /* * Again, if IV/MIC was stripped, then this whole * setup will fail. That's going to need some poking. */ if (ieee80211_crypto_decap(ni, m, hdrspace, &key) == 0) { /* NB: stats+msgs handled in crypto_decap */ goto out; } has_decrypted = 1; wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; } vap->iv_recv_mgmt(ni, m, subtype, rxs, rssi, nf); goto out; case IEEE80211_FC0_TYPE_CTL: vap->iv_stats.is_rx_ctl++; IEEE80211_NODE_STAT(ni, rx_ctrl); if (ieee80211_is_ctl_frame_for_vap(ni, m)) vap->iv_recv_ctl(ni, m, subtype); goto out; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "bad frame type 0x%x", type); /* should not come here */ break; } err: if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); out: if (m != NULL) { if (need_tap && ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); m_freem(m); } return type; } static void sta_auth_open(struct ieee80211_node *ni, struct ieee80211_frame *wh, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; 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 */ return; } 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++; vap->iv_stats.is_rx_authfail_code = status; ieee80211_new_state(vap, IEEE80211_S_SCAN, IEEE80211_SCAN_FAIL_STATUS); } else ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); } static void sta_auth_shared(struct ieee80211_node *ni, struct ieee80211_frame *wh, uint8_t *frm, uint8_t *efrm, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; uint8_t *challenge; /* * 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"); 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? */ 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++; 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++; 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++; goto bad; } default: break; } if (vap->iv_state != IEEE80211_S_AUTH) return; switch (seq) { case IEEE80211_AUTH_SHARED_PASS: if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } if (status != 0) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, wh, "shared key auth failed (reason %d)", status); vap->iv_stats.is_rx_auth_fail++; vap->iv_stats.is_rx_authfail_code = status; return; } ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); break; case IEEE80211_AUTH_SHARED_CHALLENGE: if (!ieee80211_alloc_challenge(ni)) return; /* 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++; return; } return; bad: /* * 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, IEEE80211_SCAN_FAIL_STATUS); } /* * Parse the WME IE for QoS and U-APSD information. * * Returns -1 if the IE isn't found, 1 if it's found. */ int ieee80211_parse_wmeie(uint8_t *frm, const struct ieee80211_frame *wh, struct ieee80211_node *ni) { u_int len = frm[1]; ni->ni_uapsd = 0; if (len < sizeof(struct ieee80211_wme_param)-2) { IEEE80211_DISCARD_IE(ni->ni_vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME, wh, "WME", "too short, len %u", len); return -1; } ni->ni_uapsd = frm[WME_CAPINFO_IE_OFFSET]; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_POWER | IEEE80211_MSG_ASSOC, ni, "U-APSD settings from STA: 0x%02x", ni->ni_uapsd); return 1; } int ieee80211_parse_wmeparams(struct ieee80211vap *vap, uint8_t *frm, const struct ieee80211_frame *wh, uint8_t *qosinfo) { struct ieee80211_wme_state *wme = &vap->iv_ic->ic_wme; u_int len = frm[1], qosinfo_count; int i; *qosinfo = 0; if (len < sizeof(struct ieee80211_wme_param)-2) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME, wh, "WME", "too short, len %u", len); return -1; } *qosinfo = frm[__offsetof(struct ieee80211_wme_param, param_qosInfo)]; qosinfo_count = *qosinfo & WME_QOSINFO_COUNT; /* XXX do proper check for wraparound */ if (qosinfo_count == wme->wme_wmeChanParams.cap_info) 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 = _IEEE80211_MASKSHIFT(frm[0], WME_PARAM_ACM); wmep->wmep_aifsn = _IEEE80211_MASKSHIFT(frm[0], WME_PARAM_AIFSN); wmep->wmep_logcwmin = _IEEE80211_MASKSHIFT(frm[1], WME_PARAM_LOGCWMIN); wmep->wmep_logcwmax = _IEEE80211_MASKSHIFT(frm[1], WME_PARAM_LOGCWMAX); wmep->wmep_txopLimit = le16dec(frm+2); IEEE80211_DPRINTF(vap, IEEE80211_MSG_WME, "%s: WME: %d: acm=%d aifsn=%d logcwmin=%d logcwmax=%d txopLimit=%d\n", __func__, i, wmep->wmep_acm, wmep->wmep_aifsn, wmep->wmep_logcwmin, wmep->wmep_logcwmax, wmep->wmep_txopLimit); frm += 4; } wme->wme_wmeChanParams.cap_info = qosinfo_count; return 1; } /* * Process 11h Channel Switch Announcement (CSA) ie. If this * is the first CSA then initiate the switch. Otherwise we * track state and trigger completion and/or cancel of the switch. * XXX should be public for IBSS use */ static void ieee80211_parse_csaparams(struct ieee80211vap *vap, uint8_t *frm, const struct ieee80211_frame *wh) { struct ieee80211com *ic = vap->iv_ic; const struct ieee80211_csa_ie *csa = (const struct ieee80211_csa_ie *) frm; KASSERT(vap->iv_state >= IEEE80211_S_RUN, ("state %s", ieee80211_state_name[vap->iv_state])); if (csa->csa_mode > 1) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, wh, "CSA", "invalid mode %u", csa->csa_mode); return; } IEEE80211_LOCK(ic); if ((ic->ic_flags & IEEE80211_F_CSAPENDING) == 0) { /* * Convert the channel number to a channel reference. We * try first to preserve turbo attribute of the current * channel then fallback. Note this will not work if the * CSA specifies a channel that requires a band switch (e.g. * 11a => 11g). This is intentional as 11h is defined only * for 5GHz/11a and because the switch does not involve a * reassociation, protocol state (capabilities, negotated * rates, etc) may/will be wrong. */ struct ieee80211_channel *c = ieee80211_find_channel_byieee(ic, csa->csa_newchan, (ic->ic_bsschan->ic_flags & IEEE80211_CHAN_ALLTURBO)); if (c == NULL) { c = ieee80211_find_channel_byieee(ic, csa->csa_newchan, (ic->ic_bsschan->ic_flags & IEEE80211_CHAN_ALL)); if (c == NULL) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, wh, "CSA", "invalid channel %u", csa->csa_newchan); goto done; } } #if IEEE80211_CSA_COUNT_MIN > 0 if (csa->csa_count < IEEE80211_CSA_COUNT_MIN) { /* * Require at least IEEE80211_CSA_COUNT_MIN count to * reduce the risk of being redirected by a fabricated * CSA. If a valid CSA is dropped we'll still get a * beacon miss when the AP leaves the channel so we'll * eventually follow to the new channel. * * NOTE: this violates the 11h spec that states that * count may be any value and if 0 then a switch * should happen asap. */ IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, wh, "CSA", "count %u too small, must be >= %u", csa->csa_count, IEEE80211_CSA_COUNT_MIN); goto done; } #endif ieee80211_csa_startswitch(ic, c, csa->csa_mode, csa->csa_count); } else { /* * Validate this ie against the initial CSA. We require * mode and channel not change and the count must be * monotonically decreasing. This may be pointless and * canceling the switch as a result may be too paranoid but * in the worst case if we drop out of CSA because of this * and the AP does move then we'll just end up taking a * beacon miss and scan to find the AP. * * XXX may want <= on count as we also process ProbeResp * frames and those may come in w/ the same count as the * previous beacon; but doing so leaves us open to a stuck * count until we add a dead-man timer */ if (!(csa->csa_count < ic->ic_csa_count && csa->csa_mode == ic->ic_csa_mode && csa->csa_newchan == ieee80211_chan2ieee(ic, ic->ic_csa_newchan))) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_DOTH, wh, "CSA ie mismatch, initial ie <%d,%d,%d>, " "this ie <%d,%d,%d>", ic->ic_csa_mode, ic->ic_csa_newchan, ic->ic_csa_count, csa->csa_mode, csa->csa_newchan, csa->csa_count); ieee80211_csa_cancelswitch(ic); } else { if (csa->csa_count <= 1) ieee80211_csa_completeswitch(ic); else ic->ic_csa_count = csa->csa_count; } } done: IEEE80211_UNLOCK(ic); } /* * Return non-zero if a background scan may be continued: * o bg scan is active * o no channel switch is pending * o there has not been any traffic recently * o no full-offload scan support (no need for explicitly continuing scan then) * * Note we do not check if there is an administrative enable; * this is only done to start the scan. We assume that any * change in state will be accompanied by a request to cancel * active scans which will otherwise cause this test to fail. */ static __inline int contbgscan(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; return ((ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN) && (ic->ic_flags & IEEE80211_F_CSAPENDING) == 0 && !(vap->iv_flags_ext & IEEE80211_FEXT_SCAN_OFFLOAD) && vap->iv_state == IEEE80211_S_RUN && /* XXX? */ ieee80211_time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle)); } /* * Return non-zero if a backgrond scan may be started: * o bg scanning is administratively enabled * o no channel switch is pending * o we are not boosted on a dynamic turbo channel * o there has not been a scan recently * o there has not been any traffic recently (don't check if full-offload scan) */ static __inline int startbgscan(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; return ((vap->iv_flags & IEEE80211_F_BGSCAN) && (ic->ic_flags & IEEE80211_F_CSAPENDING) == 0 && #ifdef IEEE80211_SUPPORT_SUPERG !IEEE80211_IS_CHAN_DTURBO(ic->ic_curchan) && #endif ieee80211_time_after(ticks, ic->ic_lastscan + vap->iv_bgscanintvl) && ((vap->iv_flags_ext & IEEE80211_FEXT_SCAN_OFFLOAD) || ieee80211_time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle))); } #ifdef notyet /* * Compare two quiet IEs and return if they are equivalent. * * The tbttcount isn't checked - that's not part of the configuration. */ static int compare_quiet_ie(const struct ieee80211_quiet_ie *q1, const struct ieee80211_quiet_ie *q2) { if (q1->period != q2->period) return (0); if (le16dec(&q1->duration) != le16dec(&q2->duration)) return (0); if (le16dec(&q1->offset) != le16dec(&q2->offset)) return (0); return (1); } #endif static void sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { #define ISREASSOC(_st) ((_st) == IEEE80211_FC0_SUBTYPE_REASSOC_RESP) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_channel *rxchan = ic->ic_curchan; struct ieee80211_frame *wh; int ht_state_change = 0, do_ht = 0; uint8_t *frm, *efrm; uint8_t *rates, *xrates, *wme, *htcap, *htinfo; uint8_t *vhtcap, *vhtopmode; uint8_t rate; uint8_t qosinfo; wh = mtod(m0, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; efrm = mtod(m0, uint8_t *) + m0->m_len; switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: case IEEE80211_FC0_SUBTYPE_BEACON: { struct ieee80211_scanparams scan; struct ieee80211_channel *c; /* * 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) * Frames otherwise received are discarded. */ if (!((ic->ic_flags & IEEE80211_F_SCAN) || ni->ni_associd)) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* Override RX channel as appropriate */ if (rxs != NULL) { c = ieee80211_lookup_channel_rxstatus(vap, rxs); if (c != NULL) rxchan = c; } /* XXX probe response in sta mode when !scanning? */ if (ieee80211_parse_beacon(ni, m0, rxchan, &scan) != 0) { if (! (ic->ic_flags & IEEE80211_F_SCAN)) vap->iv_stats.is_beacon_bad++; return; } /* * Count frame now that we know it's to be processed. */ if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) { vap->iv_stats.is_rx_beacon++; /* XXX remove */ IEEE80211_NODE_STAT(ni, rx_beacons); } else IEEE80211_NODE_STAT(ni, rx_proberesp); /* * 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 (ni->ni_associd != 0 && ((ic->ic_flags & IEEE80211_F_SCAN) == 0 || IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid))) { /* record tsf of last beacon */ memcpy(ni->ni_tstamp.data, scan.tstamp, sizeof(ni->ni_tstamp)); /* count beacon frame for s/w bmiss handling */ vap->iv_swbmiss_count++; vap->iv_bmiss_count = 0; if (ni->ni_erp != scan.erp) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "erp change: was 0x%x, now 0x%x", ni->ni_erp, scan.erp); if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) vap->iv_flags |= IEEE80211_F_USEPROT; else vap->iv_flags &= ~IEEE80211_F_USEPROT; ni->ni_erp = scan.erp; /* XXX statistic */ /* driver notification */ ieee80211_vap_update_erp_protmode(vap); } if ((ni->ni_capinfo ^ scan.capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "capabilities change: was 0x%x, now 0x%x", ni->ni_capinfo, scan.capinfo); /* * NB: we assume short preamble doesn't * change dynamically */ ieee80211_vap_set_shortslottime(vap, IEEE80211_IS_CHAN_A(ic->ic_bsschan) || (scan.capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); ni->ni_capinfo = (ni->ni_capinfo &~ IEEE80211_CAPINFO_SHORT_SLOTTIME) | (scan.capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME); /* 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; #ifdef IEEE80211_SUPPORT_SUPERG if (scan.ath != NULL) ieee80211_parse_athparams(ni, scan.ath, wh); #endif if (scan.htcap != NULL && scan.htinfo != NULL && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { /* XXX state changes? */ ieee80211_ht_updateparams(ni, scan.htcap, scan.htinfo); do_ht = 1; } if (scan.vhtcap != NULL && scan.vhtopmode != NULL && (vap->iv_vht_flags & IEEE80211_FVHT_VHT)) { /* XXX state changes? */ ieee80211_vht_updateparams(ni, scan.vhtcap, scan.vhtopmode); do_ht = 1; } if (do_ht) { if (ieee80211_ht_updateparams_final(ni, scan.htcap, scan.htinfo)) ht_state_change = 1; } /* * If we have a quiet time IE then report it up to * the driver. * * Otherwise, inform the driver that the quiet time * IE has disappeared - only do that once rather than * spamming it each time. */ if (scan.quiet) { ic->ic_set_quiet(ni, scan.quiet); ni->ni_quiet_ie_set = 1; memcpy(&ni->ni_quiet_ie, scan.quiet, sizeof(struct ieee80211_quiet_ie)); } else { if (ni->ni_quiet_ie_set == 1) ic->ic_set_quiet(ni, NULL); ni->ni_quiet_ie_set = 0; bzero(&ni->ni_quiet_ie, sizeof(struct ieee80211_quiet_ie)); } if (scan.tim != NULL) { struct ieee80211_tim_ie *tim = (struct ieee80211_tim_ie *) scan.tim; /* * XXX Check/debug this code; see if it's about * the right time to force the VAP awake if we * receive a frame destined for us? */ int aid = IEEE80211_AID(ni->ni_associd); int ix = aid / NBBY; int min = tim->tim_bitctl &~ 1; int max = tim->tim_len + min - 4; int tim_ucast = 0; #ifdef __notyet__ int tim_mcast = 0; #endif /* * Only do this for unicast traffic in the TIM * The multicast traffic notification for * the scan notification stuff should occur * differently. */ if (min <= ix && ix <= max && isset(tim->tim_bitmap - min, aid)) { tim_ucast = 1; } #ifdef __notyet__ /* * Do a separate notification * for the multicast bit being set. */ if (tim->tim_bitctl & 1) { tim_mcast = 1; } #endif /* * If the TIM indicates there's traffic for * us then get us out of STA mode powersave. */ if (tim_ucast == 1) { /* * Wake us out of SLEEP state if we're * in it; and if we're doing bgscan * then wake us out of STA powersave. */ ieee80211_sta_tim_notify(vap, 1); /* * This is preventing us from * continuing a bgscan; because it * tricks the contbgscan() * routine to think there's always * traffic for us. * * I think we need both an RX and * TX ic_lastdata field. */ ic->ic_lastdata = ticks; } ni->ni_dtim_count = tim->tim_count; ni->ni_dtim_period = tim->tim_period; } if (scan.csa != NULL && (vap->iv_flags & IEEE80211_F_DOTH)) ieee80211_parse_csaparams(vap, scan.csa, wh); else if (ic->ic_flags & IEEE80211_F_CSAPENDING) { /* * No CSA ie or 11h disabled, but a channel * switch is pending; drop out so we aren't * stuck in CSA state. If the AP really is * moving we'll get a beacon miss and scan. */ IEEE80211_LOCK(ic); ieee80211_csa_cancelswitch(ic); IEEE80211_UNLOCK(ic); } /* * 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, rxchan, &scan, wh, subtype, rssi, nf); } else if (contbgscan(vap)) { ieee80211_bg_scan(vap, 0); } else if (startbgscan(vap)) { vap->iv_stats.is_scan_bg++; #if 0 /* wakeup if we are sleeing */ ieee80211_set_pwrsave(vap, 0); #endif ieee80211_bg_scan(vap, 0); } /* * Put the station to sleep if we haven't seen * traffic in a while. */ IEEE80211_LOCK(ic); ieee80211_sta_ps_timer_check(vap); IEEE80211_UNLOCK(ic); /* * If we've had a channel width change (eg HT20<->HT40) * then schedule a delayed driver notification. */ if (ht_state_change) ieee80211_update_chw(ic); return; } /* * If scanning, just pass information to the scan module. */ if (ic->ic_flags & IEEE80211_F_SCAN) { if (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN) { /* * Actively scanning a channel marked passive; * send a probe request now that we know there * is 802.11 traffic present. * * XXX check if the beacon we recv'd gives * us what we need and suppress the probe req */ ieee80211_probe_curchan(vap, true); ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN; } ieee80211_add_scan(vap, rxchan, &scan, wh, subtype, rssi, nf); return; } break; } case IEEE80211_FC0_SUBTYPE_AUTH: { uint16_t algo, seq, status; /* * auth frame format * [2] algorithm * [2] sequence * [2] status * [tlv*] challenge */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6, return); algo = le16toh(*(uint16_t *)frm); seq = le16toh(*(uint16_t *)(frm + 2)); status = le16toh(*(uint16_t *)(frm + 4)); IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH, wh->i_addr2, "recv auth frame with algorithm %d seq %d", algo, seq); if (vap->iv_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; } if (algo == IEEE80211_AUTH_ALG_SHARED) sta_auth_shared(ni, wh, frm + 6, efrm, rssi, nf, seq, status); else if (algo == IEEE80211_AUTH_ALG_OPEN) sta_auth_open(ni, wh, rssi, nf, seq, status); else { IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "auth", "unsupported alg %d", algo); vap->iv_stats.is_rx_auth_unsupported++; return; } break; } case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: { uint16_t capinfo, associd; uint16_t status; if (vap->iv_state != IEEE80211_S_ASSOC) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* * asresp frame format * [2] capability information * [2] status * [2] association ID * [tlv] supported rates * [tlv] extended supported rates * [tlv] WME * [tlv] HT capabilities * [tlv] HT info */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6, return); ni = vap->iv_bss; capinfo = le16toh(*(uint16_t *)frm); frm += 2; status = le16toh(*(uint16_t *)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 */ return; } associd = le16toh(*(uint16_t *)frm); frm += 2; rates = xrates = wme = htcap = htinfo = NULL; vhtcap = vhtopmode = NULL; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); switch (*frm) { case IEEE80211_ELEMID_RATES: rates = frm; break; case IEEE80211_ELEMID_XRATES: xrates = frm; break; case IEEE80211_ELEMID_HTCAP: htcap = frm; break; case IEEE80211_ELEMID_HTINFO: htinfo = frm; break; case IEEE80211_ELEMID_VENDOR: if (iswmeoui(frm)) wme = frm; else if (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) { /* * Accept pre-draft HT ie's if the * standard ones have not been seen. */ if (ishtcapoui(frm)) { if (htcap == NULL) htcap = frm; } else if (ishtinfooui(frm)) { if (htinfo == NULL) htinfo = frm; } } /* XXX Atheros OUI support */ break; case IEEE80211_ELEMID_VHT_CAP: vhtcap = frm; break; case IEEE80211_ELEMID_VHT_OPMODE: vhtopmode = frm; break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE, return); if (xrates != NULL) IEEE80211_VERIFY_ELEMENT(xrates, IEEE80211_RATE_MAXSIZE - rates[1], return); rate = ieee80211_setup_rates(ni, rates, xrates, IEEE80211_F_JOIN | 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; } ni->ni_capinfo = capinfo; ni->ni_associd = associd; if (ni->ni_jointime == 0) ni->ni_jointime = time_uptime; 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; /* * Setup HT state according to the negotiation. * * NB: shouldn't need to check if HT use is enabled but some * ap's send back HT ie's even when we don't indicate we * are HT capable in our AssocReq. */ if (htcap != NULL && htinfo != NULL && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { ieee80211_ht_node_init(ni); ieee80211_ht_updateparams(ni, htcap, htinfo); if ((vhtcap != NULL) && (vhtopmode != NULL) & (vap->iv_vht_flags & IEEE80211_FVHT_VHT)) { /* * Log if we get a VHT assoc/reassoc response. * We aren't ready for 2GHz VHT support. */ if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) { - printf("%s: peer %6D: VHT on 2GHz, ignoring\n", - __func__, - ni->ni_macaddr, - ":"); + net80211_vap_printf(vap, + "%s: peer %6D: VHT on 2GHz, ignoring\n", + __func__, ni->ni_macaddr, ":"); } else { ieee80211_vht_node_init(ni); ieee80211_vht_updateparams(ni, vhtcap, vhtopmode); ieee80211_setup_vht_rates(ni); } } ieee80211_ht_updateparams_final(ni, htcap, htinfo); ieee80211_setup_htrates(ni, htcap, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, htinfo); ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); } /* * Always initialise FF/superg state; we can use this * for doing A-MSDU encapsulation as well. */ #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_ff_node_init(ni); #endif /* * 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)) { vap->iv_flags |= IEEE80211_F_SHPREAMBLE; vap->iv_flags &= ~IEEE80211_F_USEBARKER; } else { vap->iv_flags &= ~IEEE80211_F_SHPREAMBLE; vap->iv_flags |= IEEE80211_F_USEBARKER; } ieee80211_vap_set_shortslottime(vap, IEEE80211_IS_CHAN_A(ic->ic_curchan) || (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); ieee80211_vap_update_preamble(vap); /* * Honor ERP protection. * * NB: ni_erp should zero for non-11g operation. */ if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) vap->iv_flags |= IEEE80211_F_USEPROT; else vap->iv_flags &= ~IEEE80211_F_USEPROT; ieee80211_vap_update_erp_protmode(vap); IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, wh->i_addr2, "%sassoc success at aid %d: %s preamble, %s slot time%s%s%s%s%s%s%s%s%s", ISREASSOC(subtype) ? "re" : "", IEEE80211_NODE_AID(ni), vap->iv_flags&IEEE80211_F_SHPREAMBLE ? "short" : "long", vap->iv_flags&IEEE80211_F_SHSLOT ? "short" : "long", vap->iv_flags&IEEE80211_F_USEPROT ? ", protection" : "", ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : "", ni->ni_flags & IEEE80211_NODE_HT ? (ni->ni_chw == IEEE80211_STA_RX_BW_40 ? ", HT40" : ", HT20") : "", ni->ni_flags & IEEE80211_NODE_AMPDU ? " (+AMPDU)" : "", ni->ni_flags & IEEE80211_NODE_AMSDU ? " (+AMSDU)" : "", ni->ni_flags & IEEE80211_NODE_MIMO_RTS ? " (+SMPS-DYN)" : ni->ni_flags & IEEE80211_NODE_MIMO_PS ? " (+SMPS)" : "", ni->ni_flags & IEEE80211_NODE_RIFS ? " (+RIFS)" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF) ? ", fast-frames" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_TURBOP) ? ", turbo" : "" ); ieee80211_new_state(vap, IEEE80211_S_RUN, subtype); break; } case IEEE80211_FC0_SUBTYPE_DEAUTH: { uint16_t reason; if (vap->iv_state == IEEE80211_S_SCAN) { vap->iv_stats.is_rx_mgtdiscard++; return; } if (!IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)) { /* NB: can happen when in promiscuous mode */ vap->iv_stats.is_rx_mgtdiscard++; break; } /* * deauth frame format * [2] reason */ IEEE80211_VERIFY_LENGTH(efrm - frm, 2, return); reason = le16toh(*(uint16_t *)frm); vap->iv_stats.is_rx_deauth++; vap->iv_stats.is_rx_deauth_code = reason; IEEE80211_NODE_STAT(ni, rx_deauth); IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, "recv deauthenticate (reason: %d (%s))", reason, ieee80211_reason_to_string(reason)); ieee80211_new_state(vap, IEEE80211_S_AUTH, (reason << 8) | IEEE80211_FC0_SUBTYPE_DEAUTH); break; } case IEEE80211_FC0_SUBTYPE_DISASSOC: { uint16_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; } if (!IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)) { /* NB: can happen when in promiscuous mode */ vap->iv_stats.is_rx_mgtdiscard++; break; } /* * disassoc frame format * [2] reason */ IEEE80211_VERIFY_LENGTH(efrm - frm, 2, return); reason = le16toh(*(uint16_t *)frm); vap->iv_stats.is_rx_disassoc++; vap->iv_stats.is_rx_disassoc_code = reason; IEEE80211_NODE_STAT(ni, rx_disassoc); IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, "recv disassociate (reason: %d (%s))", reason, ieee80211_reason_to_string(reason)); ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); break; } case IEEE80211_FC0_SUBTYPE_ACTION: case IEEE80211_FC0_SUBTYPE_ACTION_NOACK: if (!IEEE80211_ADDR_EQ(vap->iv_myaddr, wh->i_addr1) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not for us"); vap->iv_stats.is_rx_mgtdiscard++; } else if (vap->iv_state != IEEE80211_S_RUN) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "wrong state %s", ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_rx_mgtdiscard++; } else { if (ieee80211_parse_action(ni, m0) == 0) (void)ic->ic_recv_action(ni, wh, frm, efrm); } break; case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: case IEEE80211_FC0_SUBTYPE_PROBE_REQ: case IEEE80211_FC0_SUBTYPE_TIMING_ADV: case IEEE80211_FC0_SUBTYPE_ATIM: IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not handled"); vap->iv_stats.is_rx_mgtdiscard++; 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 } static void sta_recv_ctl(struct ieee80211_node *ni, struct mbuf *m, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_BAR: ieee80211_recv_bar(ni, m); break; } } diff --git a/sys/net80211/ieee80211_superg.c b/sys/net80211/ieee80211_superg.c index fcf0c614b0a0..cd1516302561 100644 --- a/sys/net80211/ieee80211_superg.c +++ b/sys/net80211/ieee80211_superg.c @@ -1,1079 +1,1080 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2002-2009 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 #include "opt_wlan.h" #ifdef IEEE80211_SUPPORT_SUPERG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Atheros fast-frame encapsulation format. * FF max payload: * 802.2 + FFHDR + HPAD + 802.3 + 802.2 + 1500 + SPAD + 802.3 + 802.2 + 1500: * 8 + 4 + 4 + 14 + 8 + 1500 + 6 + 14 + 8 + 1500 * = 3066 */ /* fast frame header is 32-bits */ #define ATH_FF_PROTO 0x0000003f /* protocol */ #define ATH_FF_PROTO_S 0 #define ATH_FF_FTYPE 0x000000c0 /* frame type */ #define ATH_FF_FTYPE_S 6 #define ATH_FF_HLEN32 0x00000300 /* optional hdr length */ #define ATH_FF_HLEN32_S 8 #define ATH_FF_SEQNUM 0x001ffc00 /* sequence number */ #define ATH_FF_SEQNUM_S 10 #define ATH_FF_OFFSET 0xffe00000 /* offset to 2nd payload */ #define ATH_FF_OFFSET_S 21 #define ATH_FF_MAX_HDR_PAD 4 #define ATH_FF_MAX_SEP_PAD 6 #define ATH_FF_MAX_HDR 30 #define ATH_FF_PROTO_L2TUNNEL 0 /* L2 tunnel protocol */ #define ATH_FF_ETH_TYPE 0x88bd /* Ether type for encapsulated frames */ #define ATH_FF_SNAP_ORGCODE_0 0x00 #define ATH_FF_SNAP_ORGCODE_1 0x03 #define ATH_FF_SNAP_ORGCODE_2 0x7f #define ATH_FF_TXQMIN 2 /* min txq depth for staging */ #define ATH_FF_TXQMAX 50 /* maximum # of queued frames allowed */ #define ATH_FF_STAGEMAX 5 /* max waiting period for staged frame*/ #define ETHER_HEADER_COPY(dst, src) \ memcpy(dst, src, sizeof(struct ether_header)) static int ieee80211_ffppsmin = 2; /* pps threshold for ff aggregation */ SYSCTL_INT(_net_wlan, OID_AUTO, ffppsmin, CTLFLAG_RW, &ieee80211_ffppsmin, 0, "min packet rate before fast-frame staging"); static int ieee80211_ffagemax = -1; /* max time frames held on stage q */ SYSCTL_PROC(_net_wlan, OID_AUTO, ffagemax, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &ieee80211_ffagemax, 0, ieee80211_sysctl_msecs_ticks, "I", "max hold time for fast-frame staging (ms)"); static void ff_age_all(void *arg, int npending) { struct ieee80211com *ic = arg; /* XXX cache timer value somewhere (racy) */ ieee80211_ff_age_all(ic, ieee80211_ffagemax + 1); } void ieee80211_superg_attach(struct ieee80211com *ic) { struct ieee80211_superg *sg; IEEE80211_FF_LOCK_INIT(ic, ic->ic_name); sg = (struct ieee80211_superg *) IEEE80211_MALLOC( sizeof(struct ieee80211_superg), M_80211_VAP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (sg == NULL) { - printf("%s: cannot allocate SuperG state block\n", - __func__); + net80211_ic_printf(ic, + "%s: cannot allocate SuperG state block\n", __func__); return; } TIMEOUT_TASK_INIT(ic->ic_tq, &sg->ff_qtimer, 0, ff_age_all, ic); ic->ic_superg = sg; /* * Default to not being so aggressive for FF/AMSDU * aging, otherwise we may hold a frame around * for way too long before we expire it out. */ ieee80211_ffagemax = msecs_to_ticks(2); } void ieee80211_superg_detach(struct ieee80211com *ic) { if (ic->ic_superg != NULL) { struct timeout_task *qtask = &ic->ic_superg->ff_qtimer; while (taskqueue_cancel_timeout(ic->ic_tq, qtask, NULL) != 0) taskqueue_drain_timeout(ic->ic_tq, qtask); IEEE80211_FREE(ic->ic_superg, M_80211_VAP); ic->ic_superg = NULL; } IEEE80211_FF_LOCK_DESTROY(ic); } void ieee80211_superg_vattach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; if (ic->ic_superg == NULL) /* NB: can't do fast-frames w/o state */ vap->iv_caps &= ~IEEE80211_C_FF; if (vap->iv_caps & IEEE80211_C_FF) vap->iv_flags |= IEEE80211_F_FF; /* NB: we only implement sta mode */ if (vap->iv_opmode == IEEE80211_M_STA && (vap->iv_caps & IEEE80211_C_TURBOP)) vap->iv_flags |= IEEE80211_F_TURBOP; } void ieee80211_superg_vdetach(struct ieee80211vap *vap) { } #define ATH_OUI_BYTES 0x00, 0x03, 0x7f /* * Add a WME information element to a frame. */ uint8_t * ieee80211_add_ath(uint8_t *frm, uint8_t caps, ieee80211_keyix defkeyix) { static const struct ieee80211_ath_ie info = { .ath_id = IEEE80211_ELEMID_VENDOR, .ath_len = sizeof(struct ieee80211_ath_ie) - 2, .ath_oui = { ATH_OUI_BYTES }, .ath_oui_type = ATH_OUI_TYPE, .ath_oui_subtype= ATH_OUI_SUBTYPE, .ath_version = ATH_OUI_VERSION, }; struct ieee80211_ath_ie *ath = (struct ieee80211_ath_ie *) frm; memcpy(frm, &info, sizeof(info)); ath->ath_capability = caps; if (defkeyix != IEEE80211_KEYIX_NONE) { ath->ath_defkeyix[0] = (defkeyix & 0xff); ath->ath_defkeyix[1] = ((defkeyix >> 8) & 0xff); } else { ath->ath_defkeyix[0] = 0xff; ath->ath_defkeyix[1] = 0x7f; } return frm + sizeof(info); } #undef ATH_OUI_BYTES uint8_t * ieee80211_add_athcaps(uint8_t *frm, const struct ieee80211_node *bss) { const struct ieee80211vap *vap = bss->ni_vap; return ieee80211_add_ath(frm, vap->iv_flags & IEEE80211_F_ATHEROS, ((vap->iv_flags & IEEE80211_F_WPA) == 0 && bss->ni_authmode != IEEE80211_AUTH_8021X) ? vap->iv_def_txkey : IEEE80211_KEYIX_NONE); } void ieee80211_parse_ath(struct ieee80211_node *ni, uint8_t *ie) { const struct ieee80211_ath_ie *ath = (const struct ieee80211_ath_ie *) ie; ni->ni_ath_flags = ath->ath_capability; ni->ni_ath_defkeyix = le16dec(&ath->ath_defkeyix); } int ieee80211_parse_athparams(struct ieee80211_node *ni, uint8_t *frm, const struct ieee80211_frame *wh) { struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_ath_ie *ath; u_int len = frm[1]; int capschanged; uint16_t defkeyix; if (len < sizeof(struct ieee80211_ath_ie)-2) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_SUPERG, wh, "Atheros", "too short, len %u", len); return -1; } ath = (const struct ieee80211_ath_ie *)frm; capschanged = (ni->ni_ath_flags != ath->ath_capability); defkeyix = le16dec(ath->ath_defkeyix); if (capschanged || defkeyix != ni->ni_ath_defkeyix) { ni->ni_ath_flags = ath->ath_capability; ni->ni_ath_defkeyix = defkeyix; IEEE80211_NOTE(vap, IEEE80211_MSG_SUPERG, ni, "ath ie change: new caps 0x%x defkeyix 0x%x", ni->ni_ath_flags, ni->ni_ath_defkeyix); } if (IEEE80211_ATH_CAP(vap, ni, ATHEROS_CAP_TURBO_PRIME)) { uint16_t curflags, newflags; /* * Check for turbo mode switch. Calculate flags * for the new mode and effect the switch. */ newflags = curflags = vap->iv_ic->ic_bsschan->ic_flags; /* NB: BOOST is not in ic_flags, so get it from the ie */ if (ath->ath_capability & ATHEROS_CAP_BOOST) newflags |= IEEE80211_CHAN_TURBO; else newflags &= ~IEEE80211_CHAN_TURBO; if (newflags != curflags) ieee80211_dturbo_switch(vap, newflags); } return capschanged; } /* * Decap the encapsulated frame pair and dispatch the first * for delivery. The second frame is returned for delivery * via the normal path. */ struct mbuf * ieee80211_ff_decap(struct ieee80211_node *ni, struct mbuf *m) { #define FF_LLC_SIZE (sizeof(struct ether_header) + sizeof(struct llc)) struct ieee80211vap *vap = ni->ni_vap; struct llc *llc; uint32_t ath; struct mbuf *n; int framelen; /* NB: we assume caller does this check for us */ KASSERT(IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF), ("ff not negotiated")); /* * Check for fast-frame tunnel encapsulation. */ if (m->m_pkthdr.len < 3*FF_LLC_SIZE) return m; 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 NULL; } llc = (struct llc *)(mtod(m, uint8_t *) + sizeof(struct ether_header)); if (llc->llc_snap.ether_type != htons(ATH_FF_ETH_TYPE)) return m; m_adj(m, FF_LLC_SIZE); m_copydata(m, 0, sizeof(uint32_t), (caddr_t) &ath); if (_IEEE80211_MASKSHIFT(ath, ATH_FF_PROTO) != ATH_FF_PROTO_L2TUNNEL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "fast-frame", "unsupport tunnel protocol, header 0x%x", ath); vap->iv_stats.is_ff_badhdr++; m_freem(m); return NULL; } /* NB: skip header and alignment padding */ m_adj(m, roundup(sizeof(uint32_t) - 2, 4) + 2); vap->iv_stats.is_ff_decap++; /* * Decap the first frame, bust it apart from the * second and deliver; then decap the second frame * and return it to the caller for normal delivery. */ m = ieee80211_decap1(m, &framelen); if (m == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "fast-frame", "%s", "first decap failed"); vap->iv_stats.is_ff_tooshort++; return NULL; } n = m_split(m, framelen, IEEE80211_M_NOWAIT); if (n == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "fast-frame", "%s", "unable to split encapsulated frames"); vap->iv_stats.is_ff_split++; m_freem(m); /* NB: must reclaim */ return NULL; } /* XXX not right for WDS */ vap->iv_deliver_data(vap, ni, m); /* 1st of pair */ /* * Decap second frame. */ m_adj(n, roundup2(framelen, 4) - framelen); /* padding */ n = ieee80211_decap1(n, &framelen); if (n == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "fast-frame", "%s", "second decap failed"); vap->iv_stats.is_ff_tooshort++; } /* XXX verify framelen against mbuf contents */ return n; /* 2nd delivered by caller */ #undef FF_LLC_SIZE } /* * Fast frame encapsulation. There must be two packets * chained with m_nextpkt. We do header adjustment for * each, add the tunnel encapsulation, and then concatenate * the mbuf chains to form a single frame for transmission. */ struct mbuf * ieee80211_ff_encap(struct ieee80211vap *vap, struct mbuf *m1, int hdrspace, struct ieee80211_key *key) { struct mbuf *m2; struct ether_header eh1, eh2; struct llc *llc; struct mbuf *m; int pad; m2 = m1->m_nextpkt; if (m2 == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: only one frame\n", __func__); goto bad; } m1->m_nextpkt = NULL; /* * Adjust to include 802.11 header requirement. */ KASSERT(m1->m_len >= sizeof(eh1), ("no ethernet header!")); ETHER_HEADER_COPY(&eh1, mtod(m1, caddr_t)); m1 = ieee80211_mbuf_adjust(vap, hdrspace, key, m1); if (m1 == NULL) { - printf("%s: failed initial mbuf_adjust\n", __func__); + net80211_vap_printf(vap, "%s: failed initial mbuf_adjust\n", + __func__); /* NB: ieee80211_mbuf_adjust handles msgs+statistics */ m_freem(m2); goto bad; } /* * Copy second frame's Ethernet header out of line * and adjust for possible padding in case there isn't room * at the end of first frame. */ KASSERT(m2->m_len >= sizeof(eh2), ("no ethernet header!")); ETHER_HEADER_COPY(&eh2, mtod(m2, caddr_t)); m2 = ieee80211_mbuf_adjust(vap, 4, NULL, m2); if (m2 == NULL) { /* NB: ieee80211_mbuf_adjust handles msgs+statistics */ - printf("%s: failed second \n", __func__); + net80211_vap_printf(vap, "%s: failed second \n", __func__); goto bad; } /* * Now do tunnel encapsulation. First, each * frame gets a standard encapsulation. */ m1 = ieee80211_ff_encap1(vap, m1, &eh1); if (m1 == NULL) goto bad; m2 = ieee80211_ff_encap1(vap, m2, &eh2); if (m2 == NULL) goto bad; /* * Pad leading frame to a 4-byte boundary. If there * is space at the end of the first frame, put it * there; otherwise prepend to the front of the second * frame. We know doing the second will always work * because we reserve space above. We prefer appending * as this typically has better DMA alignment properties. */ for (m = m1; m->m_next != NULL; m = m->m_next) ; pad = roundup2(m1->m_pkthdr.len, 4) - m1->m_pkthdr.len; if (pad) { if (M_TRAILINGSPACE(m) < pad) { /* prepend to second */ m2->m_data -= pad; m2->m_len += pad; m2->m_pkthdr.len += pad; } else { /* append to first */ m->m_len += pad; m1->m_pkthdr.len += pad; } } /* * A-MSDU's are just appended; the "I'm A-MSDU!" bit is in the * QoS header. * * XXX optimize by prepending together */ m->m_next = m2; /* NB: last mbuf from above */ m1->m_pkthdr.len += m2->m_pkthdr.len; M_PREPEND(m1, sizeof(uint32_t)+2, IEEE80211_M_NOWAIT); if (m1 == NULL) { /* XXX cannot happen */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: no space for tunnel header\n", __func__); vap->iv_stats.is_tx_nobuf++; return NULL; } memset(mtod(m1, void *), 0, sizeof(uint32_t)+2); M_PREPEND(m1, sizeof(struct llc), IEEE80211_M_NOWAIT); if (m1 == NULL) { /* XXX cannot happen */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: no space for llc header\n", __func__); vap->iv_stats.is_tx_nobuf++; return NULL; } llc = mtod(m1, struct llc *); llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP; llc->llc_control = LLC_UI; llc->llc_snap.org_code[0] = ATH_FF_SNAP_ORGCODE_0; llc->llc_snap.org_code[1] = ATH_FF_SNAP_ORGCODE_1; llc->llc_snap.org_code[2] = ATH_FF_SNAP_ORGCODE_2; llc->llc_snap.ether_type = htons(ATH_FF_ETH_TYPE); vap->iv_stats.is_ff_encap++; return m1; bad: vap->iv_stats.is_ff_encapfail++; if (m1 != NULL) m_freem(m1); if (m2 != NULL) m_freem(m2); return NULL; } /* * A-MSDU encapsulation. * * This assumes just two frames for now, since we're borrowing the * same queuing code and infrastructure as fast-frames. * * There must be two packets chained with m_nextpkt. * We do header adjustment for each, and then concatenate the mbuf chains * to form a single frame for transmission. */ struct mbuf * ieee80211_amsdu_encap(struct ieee80211vap *vap, struct mbuf *m1, int hdrspace, struct ieee80211_key *key) { struct mbuf *m2; struct ether_header eh1, eh2; struct mbuf *m; int pad; m2 = m1->m_nextpkt; if (m2 == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: only one frame\n", __func__); goto bad; } m1->m_nextpkt = NULL; /* * Include A-MSDU header in adjusting header layout. */ KASSERT(m1->m_len >= sizeof(eh1), ("no ethernet header!")); ETHER_HEADER_COPY(&eh1, mtod(m1, caddr_t)); m1 = ieee80211_mbuf_adjust(vap, hdrspace + sizeof(struct llc) + sizeof(uint32_t) + sizeof(struct ether_header), key, m1); if (m1 == NULL) { /* NB: ieee80211_mbuf_adjust handles msgs+statistics */ m_freem(m2); goto bad; } /* * Copy second frame's Ethernet header out of line * and adjust for encapsulation headers. Note that * we make room for padding in case there isn't room * at the end of first frame. */ KASSERT(m2->m_len >= sizeof(eh2), ("no ethernet header!")); ETHER_HEADER_COPY(&eh2, mtod(m2, caddr_t)); m2 = ieee80211_mbuf_adjust(vap, 4, NULL, m2); if (m2 == NULL) { /* NB: ieee80211_mbuf_adjust handles msgs+statistics */ goto bad; } /* * Now do tunnel encapsulation. First, each * frame gets a standard encapsulation. */ m1 = ieee80211_ff_encap1(vap, m1, &eh1); if (m1 == NULL) goto bad; m2 = ieee80211_ff_encap1(vap, m2, &eh2); if (m2 == NULL) goto bad; /* * Pad leading frame to a 4-byte boundary. If there * is space at the end of the first frame, put it * there; otherwise prepend to the front of the second * frame. We know doing the second will always work * because we reserve space above. We prefer appending * as this typically has better DMA alignment properties. */ for (m = m1; m->m_next != NULL; m = m->m_next) ; pad = roundup2(m1->m_pkthdr.len, 4) - m1->m_pkthdr.len; if (pad) { if (M_TRAILINGSPACE(m) < pad) { /* prepend to second */ m2->m_data -= pad; m2->m_len += pad; m2->m_pkthdr.len += pad; } else { /* append to first */ m->m_len += pad; m1->m_pkthdr.len += pad; } } /* * Now, stick 'em together. */ m->m_next = m2; /* NB: last mbuf from above */ m1->m_pkthdr.len += m2->m_pkthdr.len; vap->iv_stats.is_amsdu_encap++; return m1; bad: vap->iv_stats.is_amsdu_encapfail++; if (m1 != NULL) m_freem(m1); if (m2 != NULL) m_freem(m2); return NULL; } static void ff_transmit(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; IEEE80211_TX_LOCK_ASSERT(ic); /* encap and xmit */ m = ieee80211_encap(vap, ni, m); if (m != NULL) (void) ieee80211_parent_xmitpkt(ic, m); else ieee80211_free_node(ni); } /* * Flush frames to device; note we re-use the linked list * the frames were stored on and use the sentinel (unchanged) * which may be non-NULL. */ static void ff_flush(struct mbuf *head, struct mbuf *last) { struct mbuf *m, *next; struct ieee80211_node *ni; struct ieee80211vap *vap; for (m = head; m != last; m = next) { next = m->m_nextpkt; m->m_nextpkt = NULL; ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; vap = ni->ni_vap; IEEE80211_NOTE(vap, IEEE80211_MSG_SUPERG, ni, "%s: flush frame, age %u", __func__, M_AGE_GET(m)); vap->iv_stats.is_ff_flush++; ff_transmit(ni, m); } } /* * Age frames on the staging queue. */ void ieee80211_ff_age(struct ieee80211com *ic, struct ieee80211_stageq *sq, int quanta) { struct mbuf *m, *head; struct ieee80211_node *ni; IEEE80211_FF_LOCK(ic); if (sq->depth == 0) { IEEE80211_FF_UNLOCK(ic); return; /* nothing to do */ } KASSERT(sq->head != NULL, ("stageq empty")); head = sq->head; while ((m = sq->head) != NULL && M_AGE_GET(m) < quanta) { int tid = WME_AC_TO_TID(M_WME_GETAC(m)); /* clear staging ref to frame */ ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; KASSERT(ni->ni_tx_superg[tid] == m, ("staging queue empty")); ni->ni_tx_superg[tid] = NULL; sq->head = m->m_nextpkt; sq->depth--; } if (m == NULL) sq->tail = NULL; else M_AGE_SUB(m, quanta); IEEE80211_FF_UNLOCK(ic); IEEE80211_TX_LOCK(ic); ff_flush(head, m); IEEE80211_TX_UNLOCK(ic); } static void stageq_add(struct ieee80211com *ic, struct ieee80211_stageq *sq, struct mbuf *m) { int age = ieee80211_ffagemax; IEEE80211_FF_LOCK_ASSERT(ic); if (sq->tail != NULL) { sq->tail->m_nextpkt = m; age -= M_AGE_GET(sq->head); } else { sq->head = m; struct timeout_task *qtask = &ic->ic_superg->ff_qtimer; taskqueue_enqueue_timeout(ic->ic_tq, qtask, age); } KASSERT(age >= 0, ("age %d", age)); M_AGE_SET(m, age); m->m_nextpkt = NULL; sq->tail = m; sq->depth++; } static void stageq_remove(struct ieee80211com *ic, struct ieee80211_stageq *sq, struct mbuf *mstaged) { struct mbuf *m, *mprev; IEEE80211_FF_LOCK_ASSERT(ic); mprev = NULL; for (m = sq->head; m != NULL; m = m->m_nextpkt) { if (m == mstaged) { if (mprev == NULL) sq->head = m->m_nextpkt; else mprev->m_nextpkt = m->m_nextpkt; if (sq->tail == m) sq->tail = mprev; sq->depth--; return; } mprev = m; } - printf("%s: packet not found\n", __func__); + net80211_ic_printf(ic, "%s: packet not found\n", __func__); } static uint32_t ff_approx_txtime(struct ieee80211_node *ni, const struct mbuf *m1, const struct mbuf *m2) { struct ieee80211_node_txrate txr; struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; uint32_t framelen; uint32_t frame_time; uint8_t dot11rate; /* * Approximate the frame length to be transmitted. A swag to add * the following maximal values to the skb payload: * - 32: 802.11 encap + CRC * - 24: encryption overhead (if wep bit) * - 4 + 6: fast-frame header and padding * - 16: 2 LLC FF tunnel headers * - 14: 1 802.3 FF tunnel header (mbuf already accounts for 2nd) */ framelen = m1->m_pkthdr.len + 32 + ATH_FF_MAX_HDR_PAD + ATH_FF_MAX_SEP_PAD + ATH_FF_MAX_HDR; if (vap->iv_flags & IEEE80211_F_PRIVACY) framelen += 24; if (m2 != NULL) framelen += m2->m_pkthdr.len; ieee80211_node_get_txrate(ni, &txr); switch (txr.type) { case IEEE80211_NODE_TXRATE_LEGACY: dot11rate = ieee80211_node_get_txrate_dot11rate(ni); frame_time = ieee80211_compute_duration(ic->ic_rt, framelen, dot11rate, 0); break; case IEEE80211_NODE_TXRATE_HT: /* TODO: check ht40/shortgi */ dot11rate = ieee80211_node_get_txrate_dot11rate(ni); frame_time = ieee80211_compute_duration_ht(framelen, dot11rate, IEEE80211_HT_RC_2_STREAMS(dot11rate), 0, /* isht40 */ 0); /* isshortgi */ break; case IEEE80211_NODE_TXRATE_VHT: /* TODO: there's no VHT frame length calculation just yet */ frame_time = 1000; /* 1ms */ break; case IEEE80211_NODE_TXRATE_UNDEFINED: /* TODO: proper error handling */ frame_time = 4000; /* 4ms */ break; } return (frame_time); } /* * Check if the supplied frame can be partnered with an existing * or pending frame. Return a reference to any frame that should be * sent on return; otherwise return NULL. */ struct mbuf * ieee80211_ff_check(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_superg *sg = ic->ic_superg; const int pri = M_WME_GETAC(m); struct ieee80211_stageq *sq; struct ieee80211_tx_ampdu *tap; struct mbuf *mstaged; uint32_t txtime, limit; IEEE80211_TX_UNLOCK_ASSERT(ic); IEEE80211_LOCK(ic); limit = IEEE80211_TXOP_TO_US( ic->ic_wme.wme_chanParams.cap_wmeParams[pri].wmep_txopLimit); IEEE80211_UNLOCK(ic); /* * Check if the supplied frame can be aggregated. * * NB: we allow EAPOL frames to be aggregated with other ucast traffic. * Do 802.1x EAPOL frames proceed in the clear? Then they couldn't * be aggregated with other types of frames when encryption is on? */ IEEE80211_FF_LOCK(ic); tap = &ni->ni_tx_ampdu[WME_AC_TO_TID(pri)]; mstaged = ni->ni_tx_superg[WME_AC_TO_TID(pri)]; /* XXX NOTE: reusing packet counter state from A-MPDU */ /* * XXX NOTE: this means we're double-counting; it should just * be done in ieee80211_output.c once for both superg and A-MPDU. */ ieee80211_txampdu_count_packet(tap); /* * When not in station mode never aggregate a multicast * frame; this insures, for example, that a combined frame * does not require multiple encryption keys. */ if (vap->iv_opmode != IEEE80211_M_STA && ETHER_IS_MULTICAST(mtod(m, struct ether_header *)->ether_dhost)) { /* XXX flush staged frame? */ IEEE80211_FF_UNLOCK(ic); return m; } /* * If there is no frame to combine with and the pps is * too low; then do not attempt to aggregate this frame. */ if (mstaged == NULL && ieee80211_txampdu_getpps(tap) < ieee80211_ffppsmin) { IEEE80211_FF_UNLOCK(ic); return m; } sq = &sg->ff_stageq[pri]; /* * Check the txop limit to insure the aggregate fits. */ if (limit != 0 && (txtime = ff_approx_txtime(ni, m, mstaged)) > limit) { /* * Aggregate too long, return to the caller for direct * transmission. In addition, flush any pending frame * before sending this one. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: txtime %u exceeds txop limit %u\n", __func__, txtime, limit); ni->ni_tx_superg[WME_AC_TO_TID(pri)] = NULL; if (mstaged != NULL) stageq_remove(ic, sq, mstaged); IEEE80211_FF_UNLOCK(ic); if (mstaged != NULL) { IEEE80211_TX_LOCK(ic); IEEE80211_NOTE(vap, IEEE80211_MSG_SUPERG, ni, "%s: flush staged frame", __func__); /* encap and xmit */ ff_transmit(ni, mstaged); IEEE80211_TX_UNLOCK(ic); } return m; /* NB: original frame */ } /* * An aggregation candidate. If there's a frame to partner * with then combine and return for processing. Otherwise * save this frame and wait for a partner to show up (or * the frame to be flushed). Note that staged frames also * hold their node reference. */ if (mstaged != NULL) { ni->ni_tx_superg[WME_AC_TO_TID(pri)] = NULL; stageq_remove(ic, sq, mstaged); IEEE80211_FF_UNLOCK(ic); IEEE80211_NOTE(vap, IEEE80211_MSG_SUPERG, ni, "%s: aggregate fast-frame", __func__); /* * Release the node reference; we only need * the one already in mstaged. */ KASSERT(mstaged->m_pkthdr.rcvif == (void *)ni, ("rcvif %p ni %p", mstaged->m_pkthdr.rcvif, ni)); ieee80211_free_node(ni); m->m_nextpkt = NULL; mstaged->m_nextpkt = m; mstaged->m_flags |= M_FF; /* NB: mark for encap work */ } else { KASSERT(ni->ni_tx_superg[WME_AC_TO_TID(pri)] == NULL, ("ni_tx_superg[]: %p", ni->ni_tx_superg[WME_AC_TO_TID(pri)])); ni->ni_tx_superg[WME_AC_TO_TID(pri)] = m; stageq_add(ic, sq, m); IEEE80211_FF_UNLOCK(ic); IEEE80211_NOTE(vap, IEEE80211_MSG_SUPERG, ni, "%s: stage frame, %u queued", __func__, sq->depth); /* NB: mstaged is NULL */ } return mstaged; } struct mbuf * ieee80211_amsdu_check(struct ieee80211_node *ni, struct mbuf *m) { /* * XXX TODO: actually enforce the node support * and HTCAP requirements for the maximum A-MSDU * size. */ /* First: software A-MSDU transmit? */ if (! ieee80211_amsdu_tx_ok(ni)) return (m); /* Next - EAPOL? Nope, don't aggregate; we don't QoS encap them */ if (m->m_flags & (M_EAPOL | M_MCAST | M_BCAST)) return (m); /* Next - needs to be a data frame, non-broadcast, etc */ if (ETHER_IS_MULTICAST(mtod(m, struct ether_header *)->ether_dhost)) return (m); return (ieee80211_ff_check(ni, m)); } void ieee80211_ff_node_init(struct ieee80211_node *ni) { /* * Clean FF state on re-associate. This handles the case * where a station leaves w/o notifying us and then returns * before node is reaped for inactivity. */ ieee80211_ff_node_cleanup(ni); } void ieee80211_ff_node_cleanup(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211_superg *sg = ic->ic_superg; struct mbuf *m, *next_m, *head; int tid; IEEE80211_FF_LOCK(ic); head = NULL; for (tid = 0; tid < WME_NUM_TID; tid++) { int ac = TID_TO_WME_AC(tid); /* * XXX Initialise the packet counter. * * This may be double-work for 11n stations; * but without it we never setup things. */ ieee80211_txampdu_init_pps(&ni->ni_tx_ampdu[tid]); m = ni->ni_tx_superg[tid]; if (m != NULL) { ni->ni_tx_superg[tid] = NULL; stageq_remove(ic, &sg->ff_stageq[ac], m); m->m_nextpkt = head; head = m; } } IEEE80211_FF_UNLOCK(ic); /* * Free mbufs, taking care to not dereference the mbuf after * we free it (hence grabbing m_nextpkt before we free it.) */ m = head; while (m != NULL) { next_m = m->m_nextpkt; m_freem(m); ieee80211_free_node(ni); m = next_m; } } /* * Switch between turbo and non-turbo operating modes. * Use the specified channel flags to locate the new * channel, update 802.11 state, and then call back into * the driver to effect the change. */ void ieee80211_dturbo_switch(struct ieee80211vap *vap, int newflags) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel *chan; chan = ieee80211_find_channel(ic, ic->ic_bsschan->ic_freq, newflags); if (chan == NULL) { /* XXX should not happen */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: no channel with freq %u flags 0x%x\n", __func__, ic->ic_bsschan->ic_freq, newflags); return; } IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: %s -> %s (freq %u flags 0x%x)\n", __func__, ieee80211_phymode_name[ieee80211_chan2mode(ic->ic_bsschan)], ieee80211_phymode_name[ieee80211_chan2mode(chan)], chan->ic_freq, chan->ic_flags); ic->ic_bsschan = chan; ic->ic_prevchan = ic->ic_curchan; ic->ic_curchan = chan; ic->ic_rt = ieee80211_get_ratetable(chan); ic->ic_set_channel(ic); ieee80211_radiotap_chan_change(ic); /* NB: do not need to reset ERP state 'cuz we're in sta mode */ } /* * Return the current ``state'' of an Atheros capbility. * If associated in station mode report the negotiated * setting. Otherwise report the current setting. */ static int getathcap(struct ieee80211vap *vap, int cap) { if (vap->iv_opmode == IEEE80211_M_STA && vap->iv_state == IEEE80211_S_RUN) return IEEE80211_ATH_CAP(vap, vap->iv_bss, cap) != 0; else return (vap->iv_flags & cap) != 0; } static int superg_ioctl_get80211(struct ieee80211vap *vap, struct ieee80211req *ireq) { switch (ireq->i_type) { case IEEE80211_IOC_FF: ireq->i_val = getathcap(vap, IEEE80211_F_FF); break; case IEEE80211_IOC_TURBOP: ireq->i_val = getathcap(vap, IEEE80211_F_TURBOP); break; default: return ENOSYS; } return 0; } IEEE80211_IOCTL_GET(superg, superg_ioctl_get80211); static int superg_ioctl_set80211(struct ieee80211vap *vap, struct ieee80211req *ireq) { switch (ireq->i_type) { case IEEE80211_IOC_FF: if (ireq->i_val) { if ((vap->iv_caps & IEEE80211_C_FF) == 0) return EOPNOTSUPP; vap->iv_flags |= IEEE80211_F_FF; } else vap->iv_flags &= ~IEEE80211_F_FF; return ENETRESET; case IEEE80211_IOC_TURBOP: if (ireq->i_val) { if ((vap->iv_caps & IEEE80211_C_TURBOP) == 0) return EOPNOTSUPP; vap->iv_flags |= IEEE80211_F_TURBOP; } else vap->iv_flags &= ~IEEE80211_F_TURBOP; return ENETRESET; default: return ENOSYS; } } IEEE80211_IOCTL_SET(superg, superg_ioctl_set80211); #endif /* IEEE80211_SUPPORT_SUPERG */ diff --git a/sys/net80211/ieee80211_tdma.c b/sys/net80211/ieee80211_tdma.c index 90ab2525e488..956158dbe767 100644 --- a/sys/net80211/ieee80211_tdma.c +++ b/sys/net80211/ieee80211_tdma.c @@ -1,830 +1,834 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting * Copyright (c) 2007-2009 Intel Corporation * 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. */ /* * IEEE 802.11 TDMA mode support. */ #include "opt_inet.h" #include "opt_tdma.h" #include "opt_wlan.h" #ifdef IEEE80211_SUPPORT_TDMA #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef TDMA_SLOTLEN_DEFAULT #define TDMA_SLOTLEN_DEFAULT 10*1000 /* 10ms */ #endif #ifndef TDMA_SLOTCNT_DEFAULT #define TDMA_SLOTCNT_DEFAULT 2 /* 2x (pt-to-pt) */ #endif #ifndef TDMA_BINTVAL_DEFAULT #define TDMA_BINTVAL_DEFAULT 5 /* 5x ~= 100TU beacon intvl */ #endif #ifndef TDMA_TXRATE_11B_DEFAULT #define TDMA_TXRATE_11B_DEFAULT 2*11 #endif #ifndef TDMA_TXRATE_11G_DEFAULT #define TDMA_TXRATE_11G_DEFAULT 2*24 #endif #ifndef TDMA_TXRATE_11A_DEFAULT #define TDMA_TXRATE_11A_DEFAULT 2*24 #endif #ifndef TDMA_TXRATE_TURBO_DEFAULT #define TDMA_TXRATE_TURBO_DEFAULT 2*24 #endif #ifndef TDMA_TXRATE_HALF_DEFAULT #define TDMA_TXRATE_HALF_DEFAULT 2*12 #endif #ifndef TDMA_TXRATE_QUARTER_DEFAULT #define TDMA_TXRATE_QUARTER_DEFAULT 2*6 #endif #ifndef TDMA_TXRATE_11NA_DEFAULT #define TDMA_TXRATE_11NA_DEFAULT (4 | IEEE80211_RATE_MCS) #endif #ifndef TDMA_TXRATE_11NG_DEFAULT #define TDMA_TXRATE_11NG_DEFAULT (4 | IEEE80211_RATE_MCS) #endif #define TDMA_VERSION_VALID(_version) \ (TDMA_VERSION_V2 <= (_version) && (_version) <= TDMA_VERSION) #define TDMA_SLOTCNT_VALID(_slotcnt) \ (2 <= (_slotcnt) && (_slotcnt) <= TDMA_MAXSLOTS) /* XXX magic constants */ #define TDMA_SLOTLEN_VALID(_slotlen) \ (2*100 <= (_slotlen) && (unsigned)(_slotlen) <= 0xfffff) /* XXX probably should set a max */ #define TDMA_BINTVAL_VALID(_bintval) (1 <= (_bintval)) /* * This code is not prepared to handle more than 2 slots. */ CTASSERT(TDMA_MAXSLOTS == 2); static void tdma_vdetach(struct ieee80211vap *vap); static int tdma_newstate(struct ieee80211vap *, enum ieee80211_state, int); static void tdma_beacon_miss(struct ieee80211vap *vap); static void tdma_recv_mgmt(struct ieee80211_node *, struct mbuf *, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf); static int tdma_update(struct ieee80211vap *vap, const struct ieee80211_tdma_param *tdma, struct ieee80211_node *ni, int pickslot); static int tdma_process_params(struct ieee80211_node *ni, const u_int8_t *ie, int rssi, int nf, const struct ieee80211_frame *wh); static void settxparms(struct ieee80211vap *vap, enum ieee80211_phymode mode, int rate) { if (isclr(vap->iv_ic->ic_modecaps, mode)) return; vap->iv_txparms[mode].ucastrate = rate; vap->iv_txparms[mode].mcastrate = rate; } static void setackpolicy(struct ieee80211com *ic, int noack) { struct ieee80211_wme_state *wme = &ic->ic_wme; int ac; for (ac = 0; ac < WME_NUM_AC; ac++) { wme->wme_chanParams.cap_wmeParams[ac].wmep_noackPolicy = noack; wme->wme_wmeChanParams.cap_wmeParams[ac].wmep_noackPolicy = noack; } } void ieee80211_tdma_vattach(struct ieee80211vap *vap) { struct ieee80211_tdma_state *ts; KASSERT(vap->iv_caps & IEEE80211_C_TDMA, ("not a tdma vap, caps 0x%x", vap->iv_caps)); ts = (struct ieee80211_tdma_state *) IEEE80211_MALLOC( sizeof(struct ieee80211_tdma_state), M_80211_VAP, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (ts == NULL) { - printf("%s: cannot allocate TDMA state block\n", __func__); + net80211_vap_printf(vap, + "%s: cannot allocate TDMA state block\n", __func__); /* NB: fall back to adhdemo mode */ vap->iv_caps &= ~IEEE80211_C_TDMA; return; } /* NB: default configuration is passive so no beacons */ ts->tdma_version = TDMA_VERSION; ts->tdma_slotlen = TDMA_SLOTLEN_DEFAULT; ts->tdma_slotcnt = TDMA_SLOTCNT_DEFAULT; ts->tdma_bintval = TDMA_BINTVAL_DEFAULT; ts->tdma_slot = 1; /* passive operation */ /* setup default fixed rates */ settxparms(vap, IEEE80211_MODE_11A, TDMA_TXRATE_11A_DEFAULT); settxparms(vap, IEEE80211_MODE_11B, TDMA_TXRATE_11B_DEFAULT); settxparms(vap, IEEE80211_MODE_11G, TDMA_TXRATE_11G_DEFAULT); settxparms(vap, IEEE80211_MODE_TURBO_A, TDMA_TXRATE_TURBO_DEFAULT); settxparms(vap, IEEE80211_MODE_TURBO_G, TDMA_TXRATE_TURBO_DEFAULT); settxparms(vap, IEEE80211_MODE_STURBO_A, TDMA_TXRATE_TURBO_DEFAULT); settxparms(vap, IEEE80211_MODE_11NA, TDMA_TXRATE_11NA_DEFAULT); settxparms(vap, IEEE80211_MODE_11NG, TDMA_TXRATE_11NG_DEFAULT); settxparms(vap, IEEE80211_MODE_HALF, TDMA_TXRATE_HALF_DEFAULT); settxparms(vap, IEEE80211_MODE_QUARTER, TDMA_TXRATE_QUARTER_DEFAULT); settxparms(vap, IEEE80211_MODE_VHT_2GHZ, TDMA_TXRATE_11NG_DEFAULT); settxparms(vap, IEEE80211_MODE_VHT_5GHZ, TDMA_TXRATE_11NA_DEFAULT); setackpolicy(vap->iv_ic, 1); /* disable ACK's */ ts->tdma_opdetach = vap->iv_opdetach; vap->iv_opdetach = tdma_vdetach; ts->tdma_newstate = vap->iv_newstate; vap->iv_newstate = tdma_newstate; vap->iv_bmiss = tdma_beacon_miss; ts->tdma_recv_mgmt = vap->iv_recv_mgmt; vap->iv_recv_mgmt = tdma_recv_mgmt; vap->iv_tdma = ts; } static void tdma_vdetach(struct ieee80211vap *vap) { struct ieee80211_tdma_state *ts = vap->iv_tdma; if (ts == NULL) { /* NB: should not have touched any ic state */ return; } ts->tdma_opdetach(vap); IEEE80211_FREE(vap->iv_tdma, M_80211_VAP); vap->iv_tdma = NULL; setackpolicy(vap->iv_ic, 0); /* enable ACK's */ } static void sta_leave(void *arg, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; if (ni != vap->iv_bss) ieee80211_node_leave(ni); } /* * TDMA state machine handler. */ static int tdma_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211_tdma_state *ts = vap->iv_tdma; struct ieee80211com *ic = vap->iv_ic; enum ieee80211_state ostate; int status; IEEE80211_LOCK_ASSERT(ic); ostate = vap->iv_state; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (%d)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg); if (vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS) callout_stop(&vap->iv_swbmiss); if (nstate == IEEE80211_S_SCAN && (ostate == IEEE80211_S_INIT || ostate == IEEE80211_S_RUN) && ts->tdma_slot != 0) { /* * Override adhoc behaviour when operating as a slave; * we need to scan even if the channel is locked. */ vap->iv_state = nstate; /* state transition */ ieee80211_cancel_scan(vap); /* background scan */ if (ostate == IEEE80211_S_RUN) { /* purge station table; entries are stale */ ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, sta_leave, NULL); } if (vap->iv_flags_ext & IEEE80211_FEXT_SCANREQ) { ieee80211_check_scan(vap, vap->iv_scanreq_flags, vap->iv_scanreq_duration, vap->iv_scanreq_mindwell, vap->iv_scanreq_maxdwell, vap->iv_scanreq_nssid, vap->iv_scanreq_ssid); vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANREQ; } else ieee80211_check_scan_current(vap); status = 0; } else { status = ts->tdma_newstate(vap, nstate, arg); } if (status == 0 && nstate == IEEE80211_S_RUN && ostate != IEEE80211_S_RUN && (vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS) && ts->tdma_slot != 0 && vap->iv_des_chan == IEEE80211_CHAN_ANYC) { /* * Start s/w beacon miss timer for slave devices w/o * hardware support. Note we do this only if we're * not locked to a channel (i.e. roam to follow the * master). The 2x is a fudge for our doing this in * software. */ vap->iv_swbmiss_period = IEEE80211_TU_TO_TICKS( 2 * vap->iv_bmissthreshold * ts->tdma_bintval * ((ts->tdma_slotcnt * ts->tdma_slotlen) / 1024)); vap->iv_swbmiss_count = 0; callout_reset(&vap->iv_swbmiss, vap->iv_swbmiss_period, ieee80211_swbmiss, vap); } return status; } static void tdma_beacon_miss(struct ieee80211vap *vap) { struct ieee80211_tdma_state *ts = vap->iv_tdma; IEEE80211_LOCK_ASSERT(vap->iv_ic); KASSERT((vap->iv_ic->ic_flags & IEEE80211_F_SCAN) == 0, ("scanning")); KASSERT(vap->iv_state == IEEE80211_S_RUN, ("wrong state %d", vap->iv_state)); IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_TDMA | IEEE80211_MSG_DEBUG, "beacon miss, mode %u state %s\n", vap->iv_opmode, ieee80211_state_name[vap->iv_state]); callout_stop(&vap->iv_swbmiss); if (ts->tdma_peer != NULL) { /* XXX? can this be null? */ ieee80211_notify_node_leave(vap->iv_bss); ts->tdma_peer = NULL; /* * Treat beacon miss like an associate failure wrt the * scan policy; this forces the entry in the scan cache * to be ignored after several tries. */ ieee80211_scan_assoc_fail(vap, vap->iv_bss->ni_macaddr, IEEE80211_STATUS_TIMEOUT); } #if 0 ts->tdma_inuse = 0; /* clear slot usage */ #endif ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); } static void tdma_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_tdma_state *ts = vap->iv_tdma; if (subtype == IEEE80211_FC0_SUBTYPE_BEACON && (ic->ic_flags & IEEE80211_F_SCAN) == 0) { struct ieee80211_frame *wh = mtod(m0, struct ieee80211_frame *); struct ieee80211_scanparams scan; /* XXX TODO: use rxstatus to determine off-channel beacons */ if (ieee80211_parse_beacon(ni, m0, ic->ic_curchan, &scan) != 0) return; if (scan.tdma == NULL) { /* * TDMA stations must beacon a TDMA ie; ignore * any other station. * XXX detect overlapping bss and change channel */ IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, wh, ieee80211_mgt_subtype_name(subtype), "%s", "no TDMA ie"); vap->iv_stats.is_rx_mgtdiscard++; return; } if (ni == vap->iv_bss && !IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) { /* * Fake up a node for this newly * discovered member of the IBSS. */ ni = ieee80211_add_neighbor(vap, wh, &scan); if (ni == NULL) { /* NB: stat kept for alloc failure */ return; } } /* * Check for state updates. */ if (IEEE80211_ADDR_EQ(wh->i_addr3, ni->ni_bssid)) { /* * Count frame now that we know it's to be processed. */ vap->iv_stats.is_rx_beacon++; IEEE80211_NODE_STAT(ni, rx_beacons); /* * Record tsf of last beacon. NB: this must be * done before calling tdma_process_params * as deeper routines reference it. */ memcpy(&ni->ni_tstamp.data, scan.tstamp, sizeof(ni->ni_tstamp.data)); /* * Count beacon frame for s/w bmiss handling. */ vap->iv_swbmiss_count++; /* * Process tdma ie. The contents are used to sync * the slot timing, reconfigure the bss, etc. */ (void) tdma_process_params(ni, scan.tdma, rssi, nf, wh); return; } /* * NB: defer remaining work to the adhoc code; this causes * 2x parsing of the frame but should happen infrequently */ } ts->tdma_recv_mgmt(ni, m0, subtype, rxs, rssi, nf); } /* * Update TDMA state on receipt of a beacon frame with * a TDMA information element. The sender's identity * is provided so we can track who our peer is. If pickslot * is non-zero we scan the slot allocation state in the ie * to locate a free slot for our use. */ static int tdma_update(struct ieee80211vap *vap, const struct ieee80211_tdma_param *tdma, struct ieee80211_node *ni, int pickslot) { struct ieee80211_tdma_state *ts = vap->iv_tdma; int slot, slotlen, update; KASSERT(vap->iv_caps & IEEE80211_C_TDMA, ("not a tdma vap, caps 0x%x", vap->iv_caps)); update = 0; if (tdma->tdma_slotcnt != ts->tdma_slotcnt) { if (!TDMA_SLOTCNT_VALID(tdma->tdma_slotcnt)) { if (ppsratecheck(&ts->tdma_lastprint, &ts->tdma_fails, 1)) - printf("%s: bad slot cnt %u\n", - __func__, tdma->tdma_slotcnt); + net80211_vap_printf(vap, + "%s: bad slot cnt %u\n", __func__, + tdma->tdma_slotcnt); return 0; } update |= TDMA_UPDATE_SLOTCNT; } slotlen = le16toh(tdma->tdma_slotlen) * 100; if (slotlen != ts->tdma_slotlen) { if (!TDMA_SLOTLEN_VALID(slotlen)) { if (ppsratecheck(&ts->tdma_lastprint, &ts->tdma_fails, 1)) - printf("%s: bad slot len %u\n", - __func__, slotlen); + net80211_vap_printf(vap, + "%s: bad slot len %u\n", __func__, slotlen); return 0; } update |= TDMA_UPDATE_SLOTLEN; } if (tdma->tdma_bintval != ts->tdma_bintval) { if (!TDMA_BINTVAL_VALID(tdma->tdma_bintval)) { if (ppsratecheck(&ts->tdma_lastprint, &ts->tdma_fails, 1)) - printf("%s: bad beacon interval %u\n", - __func__, tdma->tdma_bintval); + net80211_vap_printf(vap, + "%s: bad beacon interval %u\n", __func__, + tdma->tdma_bintval); return 0; } update |= TDMA_UPDATE_BINTVAL; } slot = ts->tdma_slot; if (pickslot) { /* * Pick unoccupied slot. Note we never choose slot 0. */ for (slot = tdma->tdma_slotcnt-1; slot > 0; slot--) if (isclr(tdma->tdma_inuse, slot)) break; if (slot <= 0) { - printf("%s: no free slot, slotcnt %u inuse: 0x%x\n", - __func__, tdma->tdma_slotcnt, - tdma->tdma_inuse[0]); + net80211_vap_printf(vap, + "%s: no free slot, slotcnt %u inuse: 0x%x\n", + __func__, tdma->tdma_slotcnt, + tdma->tdma_inuse[0]); /* XXX need to do something better */ return 0; } if (slot != ts->tdma_slot) update |= TDMA_UPDATE_SLOT; } if (ni != ts->tdma_peer) { /* update everything */ update = TDMA_UPDATE_SLOT | TDMA_UPDATE_SLOTCNT | TDMA_UPDATE_SLOTLEN | TDMA_UPDATE_BINTVAL; } if (update) { /* * New/changed parameters; update runtime state. */ /* XXX overwrites user parameters */ if (update & TDMA_UPDATE_SLOTCNT) ts->tdma_slotcnt = tdma->tdma_slotcnt; if (update & TDMA_UPDATE_SLOTLEN) ts->tdma_slotlen = slotlen; if (update & TDMA_UPDATE_SLOT) ts->tdma_slot = slot; if (update & TDMA_UPDATE_BINTVAL) ts->tdma_bintval = tdma->tdma_bintval; /* mark beacon to be updated before next xmit */ ieee80211_beacon_notify(vap, IEEE80211_BEACON_TDMA); IEEE80211_DPRINTF(vap, IEEE80211_MSG_TDMA, "%s: slot %u slotcnt %u slotlen %u us bintval %u\n", __func__, ts->tdma_slot, ts->tdma_slotcnt, ts->tdma_slotlen, ts->tdma_bintval); } /* * Notify driver. Note we can be called before * entering RUN state if we scanned and are * joining an existing bss. In that case do not * call the driver because not all necessary state * has been setup. The next beacon will dtrt. */ if (vap->iv_state == IEEE80211_S_RUN) vap->iv_ic->ic_tdma_update(ni, tdma, update); /* * Dispatch join event on first beacon from new master. */ if (ts->tdma_peer != ni) { if (ts->tdma_peer != NULL) ieee80211_notify_node_leave(vap->iv_bss); ieee80211_notify_node_join(ni, 1); /* NB: no reference, we just use the address */ ts->tdma_peer = ni; } return 1; } /* * Process received TDMA parameters. */ static int tdma_process_params(struct ieee80211_node *ni, const u_int8_t *ie, int rssi, int nf, const struct ieee80211_frame *wh) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_tdma_state *ts = vap->iv_tdma; const struct ieee80211_tdma_param *tdma = (const struct ieee80211_tdma_param *) ie; u_int len = ie[1]; KASSERT(vap->iv_caps & IEEE80211_C_TDMA, ("not a tdma vap, caps 0x%x", vap->iv_caps)); if (len < sizeof(*tdma) - 2) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_TDMA, wh, "tdma", "too short, len %u", len); return IEEE80211_REASON_IE_INVALID; } if (tdma->tdma_version != ts->tdma_version) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_TDMA, wh, "tdma", "bad version %u (ours %u)", tdma->tdma_version, ts->tdma_version); return IEEE80211_REASON_IE_INVALID; } /* * NB: ideally we'd check against tdma_slotcnt, but that * would require extra effort so do this easy check that * covers the work below; more stringent checks are done * before we make more extensive use of the ie contents. */ if (tdma->tdma_slot >= TDMA_MAXSLOTS) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_TDMA, wh, "tdma", "invalid slot %u", tdma->tdma_slot); return IEEE80211_REASON_IE_INVALID; } /* * Can reach here while scanning, update * operational state only in RUN state. */ if (vap->iv_state == IEEE80211_S_RUN) { if (tdma->tdma_slot != ts->tdma_slot && isclr(ts->tdma_inuse, tdma->tdma_slot)) { IEEE80211_NOTE(vap, IEEE80211_MSG_TDMA, ni, "discovered in slot %u", tdma->tdma_slot); setbit(ts->tdma_inuse, tdma->tdma_slot); /* XXX dispatch event only when operating as master */ if (ts->tdma_slot == 0) ieee80211_notify_node_join(ni, 1); } setbit(ts->tdma_active, tdma->tdma_slot); if (tdma->tdma_slot == ts->tdma_slot-1) { /* * Slave tsf synchronization to station * just before us in the schedule. The driver * is responsible for copying the timestamp * of the received beacon into our beacon * frame so the sender can calculate round * trip time. We cannot do that here because * we don't know how to update our beacon frame. */ (void) tdma_update(vap, tdma, ni, 0); /* XXX reschedule swbmiss timer on parameter change */ } else if (tdma->tdma_slot == ts->tdma_slot+1) { uint64_t tstamp; #if 0 uint32_t rstamp = (uint32_t) le64toh(rs->tsf); int32_t rtt; #endif /* * Use returned timstamp to calculate the * roundtrip time. */ memcpy(&tstamp, tdma->tdma_tstamp, 8); #if 0 /* XXX use only 15 bits of rstamp */ rtt = rstamp - (le64toh(tstamp) & 0x7fff); if (rtt < 0) rtt += 0x7fff; /* XXX hack to quiet normal use */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOT1X, "tdma rtt %5u [rstamp %5u tstamp %llu]\n", rtt, rstamp, (unsigned long long) le64toh(tstamp)); #endif } else if (tdma->tdma_slot == ts->tdma_slot && le64toh(ni->ni_tstamp.tsf) > vap->iv_bss->ni_tstamp.tsf) { /* * Station using the same slot as us and has * been around longer than us; we must move. * Note this can happen if stations do not * see each other while scanning. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_TDMA, "slot %u collision rxtsf %llu tsf %llu\n", tdma->tdma_slot, (unsigned long long) le64toh(ni->ni_tstamp.tsf), vap->iv_bss->ni_tstamp.tsf); setbit(ts->tdma_inuse, tdma->tdma_slot); (void) tdma_update(vap, tdma, ni, 1); } } return 0; } int ieee80211_tdma_getslot(struct ieee80211vap *vap) { struct ieee80211_tdma_state *ts = vap->iv_tdma; KASSERT(vap->iv_caps & IEEE80211_C_TDMA, ("not a tdma vap, caps 0x%x", vap->iv_caps)); return ts->tdma_slot; } /* * Parse a TDMA ie on station join and use it to setup node state. */ void ieee80211_parse_tdma(struct ieee80211_node *ni, const uint8_t *ie) { struct ieee80211vap *vap = ni->ni_vap; if (vap->iv_caps & IEEE80211_C_TDMA) { const struct ieee80211_tdma_param *tdma = (const struct ieee80211_tdma_param *)ie; struct ieee80211_tdma_state *ts = vap->iv_tdma; /* * Adopt TDMA configuration when joining an * existing network. */ setbit(ts->tdma_inuse, tdma->tdma_slot); (void) tdma_update(vap, tdma, ni, 1); /* * Propagate capabilities based on the local * configuration and the remote station's advertised * capabilities. In particular this permits us to * enable use of QoS to disable ACK's. */ if ((vap->iv_flags & IEEE80211_F_WME) && ni->ni_ies.wme_ie != NULL) ni->ni_flags |= IEEE80211_NODE_QOS; } } #define TDMA_OUI_BYTES 0x00, 0x03, 0x7f /* * Add a TDMA parameters element to a frame. */ uint8_t * ieee80211_add_tdma(uint8_t *frm, struct ieee80211vap *vap) { #define ADDSHORT(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = (v) >> 8; \ frm += 2; \ } while (0) static const struct ieee80211_tdma_param param = { .tdma_id = IEEE80211_ELEMID_VENDOR, .tdma_len = sizeof(struct ieee80211_tdma_param) - 2, .tdma_oui = { TDMA_OUI_BYTES }, .tdma_type = TDMA_OUI_TYPE, .tdma_subtype = TDMA_SUBTYPE_PARAM, .tdma_version = TDMA_VERSION, }; const struct ieee80211_tdma_state *ts = vap->iv_tdma; uint16_t slotlen; KASSERT(vap->iv_caps & IEEE80211_C_TDMA, ("not a tdma vap, caps 0x%x", vap->iv_caps)); memcpy(frm, ¶m, sizeof(param)); frm += __offsetof(struct ieee80211_tdma_param, tdma_slot); *frm++ = ts->tdma_slot; *frm++ = ts->tdma_slotcnt; /* NB: convert units to fit in 16-bits */ slotlen = ts->tdma_slotlen / 100; /* 100us units */ ADDSHORT(frm, slotlen); *frm++ = ts->tdma_bintval; *frm++ = ts->tdma_inuse[0]; frm += 10; /* pad+timestamp */ return frm; #undef ADDSHORT } #undef TDMA_OUI_BYTES /* * Update TDMA state at TBTT. */ void ieee80211_tdma_update_beacon(struct ieee80211vap *vap, struct ieee80211_beacon_offsets *bo) { struct ieee80211_tdma_state *ts = vap->iv_tdma; KASSERT(vap->iv_caps & IEEE80211_C_TDMA, ("not a tdma vap, caps 0x%x", vap->iv_caps)); if (isset(bo->bo_flags, IEEE80211_BEACON_TDMA)) { (void) ieee80211_add_tdma(bo->bo_tdma, vap); clrbit(bo->bo_flags, IEEE80211_BEACON_TDMA); } if (ts->tdma_slot != 0) /* only on master */ return; if (ts->tdma_count <= 0) { /* * Time to update the mask of active/inuse stations. * We track stations that we've received a beacon * frame from and update this mask periodically. * This allows us to miss a few beacons before marking * a slot free for re-use. */ ts->tdma_inuse[0] = ts->tdma_active[0]; ts->tdma_active[0] = 0x01; /* update next time 'round */ /* XXX use notify framework */ setbit(bo->bo_flags, IEEE80211_BEACON_TDMA); /* NB: use s/w beacon miss threshold; may be too high */ ts->tdma_count = vap->iv_bmissthreshold-1; } else ts->tdma_count--; } static int tdma_ioctl_get80211(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211_tdma_state *ts = vap->iv_tdma; if ((vap->iv_caps & IEEE80211_C_TDMA) == 0) return ENOSYS; switch (ireq->i_type) { case IEEE80211_IOC_TDMA_SLOT: ireq->i_val = ts->tdma_slot; break; case IEEE80211_IOC_TDMA_SLOTCNT: ireq->i_val = ts->tdma_slotcnt; break; case IEEE80211_IOC_TDMA_SLOTLEN: ireq->i_val = ts->tdma_slotlen; break; case IEEE80211_IOC_TDMA_BINTERVAL: ireq->i_val = ts->tdma_bintval; break; default: return ENOSYS; } return 0; } IEEE80211_IOCTL_GET(tdma, tdma_ioctl_get80211); static int tdma_ioctl_set80211(struct ieee80211vap *vap, struct ieee80211req *ireq) { struct ieee80211_tdma_state *ts = vap->iv_tdma; if ((vap->iv_caps & IEEE80211_C_TDMA) == 0) return ENOSYS; switch (ireq->i_type) { case IEEE80211_IOC_TDMA_SLOT: if (!(0 <= ireq->i_val && ireq->i_val <= ts->tdma_slotcnt)) return EINVAL; if (ireq->i_val != ts->tdma_slot) { ts->tdma_slot = ireq->i_val; goto restart; } break; case IEEE80211_IOC_TDMA_SLOTCNT: if (!TDMA_SLOTCNT_VALID(ireq->i_val)) return EINVAL; if (ireq->i_val != ts->tdma_slotcnt) { ts->tdma_slotcnt = ireq->i_val; goto restart; } break; case IEEE80211_IOC_TDMA_SLOTLEN: /* * XXX * 150 insures at least 1/8 TU * 0xfffff is the max duration for bursting * (implict by way of 16-bit data type for i_val) */ if (!TDMA_SLOTLEN_VALID(ireq->i_val)) return EINVAL; if (ireq->i_val != ts->tdma_slotlen) { ts->tdma_slotlen = ireq->i_val; goto restart; } break; case IEEE80211_IOC_TDMA_BINTERVAL: if (!TDMA_BINTVAL_VALID(ireq->i_val)) return EINVAL; if (ireq->i_val != ts->tdma_bintval) { ts->tdma_bintval = ireq->i_val; goto restart; } break; default: return ENOSYS; } return 0; restart: ieee80211_beacon_notify(vap, IEEE80211_BEACON_TDMA); return ERESTART; } IEEE80211_IOCTL_SET(tdma, tdma_ioctl_set80211); #endif /* IEEE80211_SUPPORT_TDMA */ diff --git a/sys/net80211/ieee80211_vht.c b/sys/net80211/ieee80211_vht.c index eb4ea615da75..e91977f1ef98 100644 --- a/sys/net80211/ieee80211_vht.c +++ b/sys/net80211/ieee80211_vht.c @@ -1,1128 +1,1133 @@ /*- * Copyright (c) 2017 Adrian Chadd * 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. */ /* * IEEE 802.11ac-2013 protocol support. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ADDSHORT(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = (v) >> 8; \ frm += 2; \ } while (0) #define ADDWORD(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = ((v) >> 8) & 0xff; \ frm[2] = ((v) >> 16) & 0xff; \ frm[3] = ((v) >> 24) & 0xff; \ frm += 4; \ } while (0) /* * Immediate TODO: * * + handle WLAN_ACTION_VHT_OPMODE_NOTIF and other VHT action frames * + ensure vhtinfo/vhtcap parameters correctly use the negotiated * capabilities and ratesets * + group ID management operation */ /* * XXX TODO: handle WLAN_ACTION_VHT_OPMODE_NOTIF * * Look at mac80211/vht.c:ieee80211_vht_handle_opmode() for further details. */ static int vht_recv_action_placeholder(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { #ifdef IEEE80211_DEBUG ieee80211_note(ni->ni_vap, "%s: called; fc=0x%.2x/0x%.2x", __func__, wh->i_fc[0], wh->i_fc[1]); #endif return (0); } static int vht_send_action_placeholder(struct ieee80211_node *ni, int category, int action, void *arg0) { #ifdef IEEE80211_DEBUG ieee80211_note(ni->ni_vap, "%s: called; category=%d, action=%d", __func__, category, action); #endif return (EINVAL); } static void ieee80211_vht_init(void) { ieee80211_recv_action_register(IEEE80211_ACTION_CAT_VHT, WLAN_ACTION_VHT_COMPRESSED_BF, vht_recv_action_placeholder); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_VHT, WLAN_ACTION_VHT_GROUPID_MGMT, vht_recv_action_placeholder); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_VHT, WLAN_ACTION_VHT_OPMODE_NOTIF, vht_recv_action_placeholder); ieee80211_send_action_register(IEEE80211_ACTION_CAT_VHT, WLAN_ACTION_VHT_COMPRESSED_BF, vht_send_action_placeholder); ieee80211_send_action_register(IEEE80211_ACTION_CAT_VHT, WLAN_ACTION_VHT_GROUPID_MGMT, vht_send_action_placeholder); ieee80211_send_action_register(IEEE80211_ACTION_CAT_VHT, WLAN_ACTION_VHT_OPMODE_NOTIF, vht_send_action_placeholder); } SYSINIT(wlan_vht, SI_SUB_DRIVERS, SI_ORDER_FIRST, ieee80211_vht_init, NULL); void ieee80211_vht_attach(struct ieee80211com *ic) { } void ieee80211_vht_detach(struct ieee80211com *ic) { } void ieee80211_vht_vattach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; if (! IEEE80211_CONF_VHT(ic)) return; vap->iv_vht_cap.vht_cap_info = ic->ic_vht_cap.vht_cap_info; vap->iv_vhtextcaps = ic->ic_vhtextcaps; /* XXX assume VHT80 support; should really check vhtcaps */ vap->iv_vht_flags = IEEE80211_FVHT_VHT | IEEE80211_FVHT_USEVHT40 | IEEE80211_FVHT_USEVHT80; if (IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_IS_160MHZ(vap->iv_vht_cap.vht_cap_info)) vap->iv_vht_flags |= IEEE80211_FVHT_USEVHT160; if (IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_IS_160_80P80MHZ(vap->iv_vht_cap.vht_cap_info)) vap->iv_vht_flags |= IEEE80211_FVHT_USEVHT80P80; memcpy(&vap->iv_vht_cap.supp_mcs, &ic->ic_vht_cap.supp_mcs, sizeof(struct ieee80211_vht_mcs_info)); } void ieee80211_vht_vdetach(struct ieee80211vap *vap) { } #if 0 static void vht_announce(struct ieee80211com *ic, enum ieee80211_phymode mode) { } #endif static int vht_mcs_to_num(int m) { switch (m) { case IEEE80211_VHT_MCS_SUPPORT_0_7: return (7); case IEEE80211_VHT_MCS_SUPPORT_0_8: return (8); case IEEE80211_VHT_MCS_SUPPORT_0_9: return (9); default: return (0); } } void ieee80211_vht_announce(struct ieee80211com *ic) { int i, tx, rx; if (! IEEE80211_CONF_VHT(ic)) return; /* Channel width */ ic_printf(ic, "[VHT] Channel Widths: 20MHz, 40MHz, 80MHz%s%s\n", (IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_IS_160MHZ(ic->ic_vht_cap.vht_cap_info)) ? ", 160MHz" : "", (IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_IS_160_80P80MHZ(ic->ic_vht_cap.vht_cap_info)) ? ", 80+80MHz" : ""); /* Features */ ic_printf(ic, "[VHT] Features: %b\n", ic->ic_vht_cap.vht_cap_info, IEEE80211_VHTCAP_BITS); /* For now, just 5GHz VHT. Worry about 2GHz VHT later */ for (i = 0; i < 8; i++) { /* Each stream is 2 bits */ tx = (ic->ic_vht_cap.supp_mcs.tx_mcs_map >> (2*i)) & 0x3; rx = (ic->ic_vht_cap.supp_mcs.rx_mcs_map >> (2*i)) & 0x3; if (tx == 3 && rx == 3) continue; ic_printf(ic, "[VHT] NSS %d: TX MCS 0..%d, RX MCS 0..%d\n", i + 1, vht_mcs_to_num(tx), vht_mcs_to_num(rx)); } } void ieee80211_vht_node_init(struct ieee80211_node *ni) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: called", __func__); ni->ni_flags |= IEEE80211_NODE_VHT; } void ieee80211_vht_node_cleanup(struct ieee80211_node *ni) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: called", __func__); ni->ni_flags &= ~IEEE80211_NODE_VHT; ni->ni_vhtcap = 0; bzero(&ni->ni_vht_mcsinfo, sizeof(struct ieee80211_vht_mcs_info)); } /* * Parse an 802.11ac VHT operation IE. * * 802.11-2020 9.4.2.158 (VHT Operation element) */ void ieee80211_parse_vhtopmode(struct ieee80211_node *ni, const uint8_t *ie) { /* vht operation */ ni->ni_vht_chanwidth = ie[2]; ni->ni_vht_chan1 = ie[3]; ni->ni_vht_chan2 = ie[4]; ni->ni_vht_basicmcs = le16dec(ie + 5); #if 0 - printf("%s: chan1=%d, chan2=%d, chanwidth=%d, basicmcs=0x%04x\n", + net80211_vap_printf(ni->ni_vap, + "%s: chan1=%d, chan2=%d, chanwidth=%d, basicmcs=0x%04x\n", __func__, ni->ni_vht_chan1, ni->ni_vht_chan2, ni->ni_vht_chanwidth, ni->ni_vht_basicmcs); #endif } /* * Parse an 802.11ac VHT capability IE. * * 802.11-2020 9.4.2.157 (VHT Capabilities element) */ void ieee80211_parse_vhtcap(struct ieee80211_node *ni, const uint8_t *ie) { /* vht capability */ ni->ni_vhtcap = le32dec(ie + 2); /* suppmcs */ ni->ni_vht_mcsinfo.rx_mcs_map = le16dec(ie + 6); ni->ni_vht_mcsinfo.rx_highest = le16dec(ie + 8); ni->ni_vht_mcsinfo.tx_mcs_map = le16dec(ie + 10); ni->ni_vht_mcsinfo.tx_highest = le16dec(ie + 12); } int ieee80211_vht_updateparams(struct ieee80211_node *ni, const uint8_t *vhtcap_ie, const uint8_t *vhtop_ie) { //printf("%s: called\n", __func__); ieee80211_parse_vhtcap(ni, vhtcap_ie); ieee80211_parse_vhtopmode(ni, vhtop_ie); return (0); } /** * @brief calculate the supported MCS rates for this node * * This is called once a node has finished association / * joined a BSS. The vhtcap / vhtop IEs are from the * peer. The transmit rate tables need to be combined * together to setup the list of available rates. * * This must be called after the ieee80211_node VHT fields * have been parsed / populated by either ieee80211_vht_updateparams() or * ieee80211_parse_vhtcap(), * * This does not take into account the channel bandwidth, * which (a) may change during operation, and (b) depends * upon packet to packet rate transmission selection. * There are various rate combinations which are not * available in various channel widths and those will * need to be masked off separately. * * (See 802.11-2020 21.5 Parameters for VHT-MCSs for the * tables and supported rates.) * * ALSO: i need to do some filtering based on the HT set too. * (That should be done here too, and in the negotiation, sigh.) * (See 802.11-2016 10.7.12.3 Additional rate selection constraints * for VHT PPDUs) * * @param ni struct ieee80211_node to configure */ void ieee80211_setup_vht_rates(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; uint32_t val, val1, val2; uint16_t tx_mcs_map = 0; int i; /* * Merge our tx_mcs_map with the peer rx_mcs_map to determine what * can be actually transmitted to the peer. */ for (i = 0; i < 8; i++) { /* * Merge the two together; remember that 0..2 is in order * of increasing MCS support, but 3 equals * IEEE80211_VHT_MCS_NOT_SUPPORTED so must "win". */ val1 = (vap->iv_vht_cap.supp_mcs.tx_mcs_map >> (i*2)) & 0x3; val2 = (ni->ni_vht_mcsinfo.rx_mcs_map >> (i*2)) & 0x3; val = MIN(val1, val2); if (val1 == IEEE80211_VHT_MCS_NOT_SUPPORTED || val2 == IEEE80211_VHT_MCS_NOT_SUPPORTED) val = IEEE80211_VHT_MCS_NOT_SUPPORTED; tx_mcs_map |= (val << (i*2)); } /* Store the TX MCS map somewhere in the node that can be used */ ni->ni_vht_tx_map = tx_mcs_map; } void ieee80211_vht_timeout(struct ieee80211vap *vap) { } void ieee80211_vht_node_join(struct ieee80211_node *ni) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: called", __func__); } void ieee80211_vht_node_leave(struct ieee80211_node *ni) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: called", __func__); } /* * Calculate the VHTCAP IE for a given node. * * This includes calculating the capability intersection based on the * current operating mode and intersection of the TX/RX MCS maps. * * The standard only makes it clear about MCS rate negotiation * and MCS basic rates (which must be a subset of the general * negotiated rates). It doesn't make it clear that the AP should * figure out the minimum functional overlap with the STA and * support that. * * Note: this is in host order, not in 802.11 endian order. * * TODO: ensure I re-read 9.7.11 Rate Selection for VHT STAs. * * TODO: investigate what we should negotiate for MU-MIMO beamforming * options. * * opmode is '1' for "vhtcap as if I'm a STA", 0 otherwise. */ void ieee80211_vht_get_vhtcap_ie(struct ieee80211_node *ni, struct ieee80211_vht_cap *vhtcap, int opmode) { struct ieee80211vap *vap = ni->ni_vap; // struct ieee80211com *ic = vap->iv_ic; uint32_t val, val1, val2; uint32_t new_vhtcap; int i; /* * Capabilities - it depends on whether we are a station * or not. */ new_vhtcap = 0; /* * Station - use our desired configuration based on * local config, local device bits and the already-learnt * vhtcap/vhtinfo IE in the node. */ /* Limit MPDU size to the smaller of the two */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_MAX_MPDU_MASK); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_MAX_MPDU_MASK); } val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_MAX_MPDU_MASK); /* Limit supp channel config */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_MASK); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_MASK); } if ((val2 == 2) && ((vap->iv_vht_flags & IEEE80211_FVHT_USEVHT80P80) == 0)) val2 = 1; if ((val2 == 1) && ((vap->iv_vht_flags & IEEE80211_FVHT_USEVHT160) == 0)) val2 = 0; val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_MASK); /* RX LDPC */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_RXLDPC); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_RXLDPC); } val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_RXLDPC); /* Short-GI 80 */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SHORT_GI_80); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_SHORT_GI_80); } val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SHORT_GI_80); /* Short-GI 160 */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SHORT_GI_160); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_SHORT_GI_160); } val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SHORT_GI_160); /* * STBC is slightly more complicated. * * In non-STA mode, we just announce our capabilities and that * is that. * * In STA mode, we should calculate our capabilities based on * local capabilities /and/ what the remote says. So: * * + Only TX STBC if we support it and the remote supports RX STBC; * + Only announce RX STBC if we support it and the remote supports * TX STBC; * + RX STBC should be the minimum of local and remote RX STBC; */ /* TX STBC */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_TXSTBC); if (opmode == 1) { /* STA mode - enable it only if node RXSTBC is non-zero */ val2 = !! _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_RXSTBC_MASK); } val = MIN(val1, val2); if ((vap->iv_vht_flags & IEEE80211_FVHT_STBC_TX) == 0) val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_TXSTBC); /* RX STBC1..4 */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_RXSTBC_MASK); if (opmode == 1) { /* STA mode - enable it only if node TXSTBC is non-zero */ val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_TXSTBC); } val = MIN(val1, val2); if ((vap->iv_vht_flags & IEEE80211_FVHT_STBC_RX) == 0) val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_RXSTBC_MASK); /* * Finally - if RXSTBC is 0, then don't enable TXSTBC. * Strictly speaking a device can TXSTBC and not RXSTBC, but * it would be silly. */ if (val == 0) new_vhtcap &= ~IEEE80211_VHTCAP_TXSTBC; /* * Some of these fields require other fields to exist. * So before using it, the parent field needs to be checked * otherwise the overridden value may be wrong. * * For example, if SU beamformee is set to 0, then BF STS * needs to be 0. */ /* SU Beamformer capable */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE); } val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE); /* SU Beamformee capable */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE); } val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE); /* Beamformee STS capability - only if SU beamformee capable */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_BEAMFORMEE_STS_MASK); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_BEAMFORMEE_STS_MASK); } val = MIN(val1, val2); if ((new_vhtcap & IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE) == 0) val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_BEAMFORMEE_STS_MASK); /* Sounding dimensions - only if SU beamformer capable */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SOUNDING_DIMENSIONS_MASK); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_SOUNDING_DIMENSIONS_MASK); val = MIN(val1, val2); if ((new_vhtcap & IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE) == 0) val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SOUNDING_DIMENSIONS_MASK); /* * MU Beamformer capable - only if SU BFF capable, MU BFF capable * and STA (not AP) */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_MU_BEAMFORMER_CAPABLE); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_MU_BEAMFORMER_CAPABLE); val = MIN(val1, val2); if ((new_vhtcap & IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE) == 0) val = 0; if (opmode != 1) /* Only enable for STA mode */ val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE); /* * MU Beamformee capable - only if SU BFE capable, MU BFE capable * and AP (not STA) */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_MU_BEAMFORMEE_CAPABLE); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_MU_BEAMFORMEE_CAPABLE); val = MIN(val1, val2); if ((new_vhtcap & IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE) == 0) val = 0; if (opmode != 0) /* Only enable for AP mode */ val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE); /* VHT TXOP PS */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_VHT_TXOP_PS); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_VHT_TXOP_PS); val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_VHT_TXOP_PS); /* HTC_VHT */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_HTC_VHT); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_HTC_VHT); val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_HTC_VHT); /* A-MPDU length max */ /* XXX TODO: we need a userland config knob for this */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK); val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK); /* * Link adaptation is only valid if HTC-VHT capable is 1. * Otherwise, always set it to 0. */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_VHT_LINK_ADAPTATION_VHT_MASK); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_VHT_LINK_ADAPTATION_VHT_MASK); val = MIN(val1, val2); if ((new_vhtcap & IEEE80211_VHTCAP_HTC_VHT) == 0) val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_VHT_LINK_ADAPTATION_VHT_MASK); /* * The following two options are 0 if the pattern may change, 1 if it * does not change. So, downgrade to the higher value. */ /* RX antenna pattern */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_RX_ANTENNA_PATTERN); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_RX_ANTENNA_PATTERN); val = MAX(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_RX_ANTENNA_PATTERN); /* TX antenna pattern */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_TX_ANTENNA_PATTERN); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_TX_ANTENNA_PATTERN); val = MAX(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_TX_ANTENNA_PATTERN); /* * MCS set - again, we announce what we want to use * based on configuration, device capabilities and * already-learnt vhtcap/vhtinfo IE information. */ /* MCS set - start with whatever the device supports */ vhtcap->supp_mcs.rx_mcs_map = vap->iv_vht_cap.supp_mcs.rx_mcs_map; vhtcap->supp_mcs.rx_highest = 0; vhtcap->supp_mcs.tx_mcs_map = vap->iv_vht_cap.supp_mcs.tx_mcs_map; vhtcap->supp_mcs.tx_highest = 0; vhtcap->vht_cap_info = new_vhtcap; /* * Now, if we're a STA, mask off whatever the AP doesn't support. * Ie, we continue to state we can receive whatever we can do, * but we only announce that we will transmit rates that meet * the AP requirement. * * Note: 0 - MCS0..7; 1 - MCS0..8; 2 - MCS0..9; 3 = not supported. * We can't just use MIN() because '3' means "no", so special case it. */ if (opmode) { for (i = 0; i < 8; i++) { val1 = (vhtcap->supp_mcs.tx_mcs_map >> (i*2)) & 0x3; val2 = (ni->ni_vht_mcsinfo.tx_mcs_map >> (i*2)) & 0x3; val = MIN(val1, val2); if (val1 == 3 || val2 == 3) val = 3; vhtcap->supp_mcs.tx_mcs_map &= ~(0x3 << (i*2)); vhtcap->supp_mcs.tx_mcs_map |= (val << (i*2)); } } } /* * Add a VHTCAP field. * * If in station mode, we announce what we would like our * desired configuration to be. * * Else, we announce our capabilities based on our current * configuration. */ uint8_t * ieee80211_add_vhtcap(uint8_t *frm, struct ieee80211_node *ni) { struct ieee80211_vht_cap vhtcap; ieee80211_vht_get_vhtcap_ie(ni, &vhtcap, 1); frm[0] = IEEE80211_ELEMID_VHT_CAP; frm[1] = sizeof(vhtcap); frm += 2; /* 32-bit VHT capability */ ADDWORD(frm, vhtcap.vht_cap_info); /* suppmcs */ ADDSHORT(frm, vhtcap.supp_mcs.rx_mcs_map); ADDSHORT(frm, vhtcap.supp_mcs.rx_highest); ADDSHORT(frm, vhtcap.supp_mcs.tx_mcs_map); ADDSHORT(frm, vhtcap.supp_mcs.tx_highest); return (frm); } /* * Non-associated probe requests. Add VHT capabilities based on * the current channel configuration. No BSS yet. */ uint8_t * ieee80211_add_vhtcap_ch(uint8_t *frm, struct ieee80211vap *vap, struct ieee80211_channel *c) { struct ieee80211_vht_cap *vhtcap; memset(frm, 0, 2 + sizeof(*vhtcap)); frm[0] = IEEE80211_ELEMID_VHT_CAP; frm[1] = sizeof(*vhtcap); frm += 2; /* 32-bit VHT capability */ ADDWORD(frm, vap->iv_vht_cap.vht_cap_info); /* supp_mcs */ ADDSHORT(frm, vap->iv_vht_cap.supp_mcs.rx_mcs_map); ADDSHORT(frm, vap->iv_vht_cap.supp_mcs.rx_highest); ADDSHORT(frm, vap->iv_vht_cap.supp_mcs.tx_mcs_map); ADDSHORT(frm, vap->iv_vht_cap.supp_mcs.tx_highest); return (frm); } static uint8_t -ieee80211_vht_get_chwidth_ie(struct ieee80211_channel *c) +ieee80211_vht_get_chwidth_ie(const struct ieee80211vap *vap, + const struct ieee80211_channel *c) { /* * XXX TODO: look at the node configuration as * well? */ if (IEEE80211_IS_CHAN_VHT80P80(c)) return IEEE80211_VHT_CHANWIDTH_80P80MHZ; if (IEEE80211_IS_CHAN_VHT160(c)) return IEEE80211_VHT_CHANWIDTH_160MHZ; if (IEEE80211_IS_CHAN_VHT80(c)) return IEEE80211_VHT_CHANWIDTH_80MHZ; if (IEEE80211_IS_CHAN_VHT40(c)) return IEEE80211_VHT_CHANWIDTH_USE_HT; if (IEEE80211_IS_CHAN_VHT20(c)) return IEEE80211_VHT_CHANWIDTH_USE_HT; /* We shouldn't get here */ - printf("%s: called on a non-VHT channel (freq=%d, flags=0x%08x\n", + net80211_vap_printf(vap, + "%s: called on a non-VHT channel (freq=%d, flags=0x%08x\n", __func__, (int) c->ic_freq, c->ic_flags); return IEEE80211_VHT_CHANWIDTH_USE_HT; } /* * Note: this just uses the current channel information; * it doesn't use the node info after parsing. * * XXX TODO: need to make the basic MCS set configurable. * XXX TODO: read 802.11-2013 to determine what to set * chwidth to when scanning. I have a feeling * it isn't involved in scanning and we shouldn't * be sending it; and I don't yet know what to set * it to for IBSS or hostap where the peer may be * a completely different channel width to us. */ uint8_t * ieee80211_add_vhtinfo(uint8_t *frm, struct ieee80211_node *ni) { frm[0] = IEEE80211_ELEMID_VHT_OPMODE; frm[1] = sizeof(struct ieee80211_vht_operation); frm += 2; /* 8-bit chanwidth */ - *frm++ = ieee80211_vht_get_chwidth_ie(ni->ni_chan); + *frm++ = ieee80211_vht_get_chwidth_ie(ni->ni_vap, ni->ni_chan); /* 8-bit freq1 */ *frm++ = ni->ni_chan->ic_vht_ch_freq1; /* 8-bit freq2 */ *frm++ = ni->ni_chan->ic_vht_ch_freq2; /* 16-bit basic MCS set - just MCS0..7 for NSS=1 for now */ ADDSHORT(frm, 0xfffc); return (frm); } void ieee80211_vht_update_cap(struct ieee80211_node *ni, const uint8_t *vhtcap_ie, const uint8_t *vhtop_ie) { ieee80211_parse_vhtcap(ni, vhtcap_ie); ieee80211_parse_vhtopmode(ni, vhtop_ie); } static struct ieee80211_channel * findvhtchan(struct ieee80211com *ic, struct ieee80211_channel *c, int vhtflags) { return (ieee80211_find_channel(ic, c->ic_freq, (c->ic_flags & ~IEEE80211_CHAN_VHT) | vhtflags)); } /* * Handle channel promotion to VHT, similar to ieee80211_ht_adjust_channel(). */ struct ieee80211_channel * ieee80211_vht_adjust_channel(struct ieee80211com *ic, struct ieee80211_channel *chan, int flags) { struct ieee80211_channel *c; /* First case - handle channel demotion - if VHT isn't set */ if ((flags & IEEE80211_FVHT_MASK) == 0) { #if 0 - printf("%s: demoting channel %d/0x%08x\n", __func__, + net80211_ic_printf(ic, + "%s: demoting channel %d/0x%08x\n", __func__, chan->ic_ieee, chan->ic_flags); #endif c = ieee80211_find_channel(ic, chan->ic_freq, chan->ic_flags & ~IEEE80211_CHAN_VHT); if (c == NULL) c = chan; #if 0 - printf("%s: .. to %d/0x%08x\n", __func__, + net80211_ic_printf(ic, "%s: .. to %d/0x%08x\n", __func__, c->ic_ieee, c->ic_flags); #endif return (c); } /* * We can upgrade to VHT - attempt to do so * * Note: we don't clear the HT flags, these are the hints * for HT40U/HT40D when selecting VHT40 or larger channels. */ c = NULL; if ((c == NULL) && (flags & IEEE80211_FVHT_USEVHT160)) c = findvhtchan(ic, chan, IEEE80211_CHAN_VHT160); if ((c == NULL) && (flags & IEEE80211_FVHT_USEVHT80P80)) c = findvhtchan(ic, chan, IEEE80211_CHAN_VHT80P80); if ((c == NULL) && (flags & IEEE80211_FVHT_USEVHT80)) c = findvhtchan(ic, chan, IEEE80211_CHAN_VHT80); if ((c == NULL) && (flags & IEEE80211_FVHT_USEVHT40)) c = findvhtchan(ic, chan, IEEE80211_CHAN_VHT40U); if ((c == NULL) && (flags & IEEE80211_FVHT_USEVHT40)) c = findvhtchan(ic, chan, IEEE80211_CHAN_VHT40D); /* * If we get here, VHT20 is always possible because we checked * for IEEE80211_FVHT_VHT above. */ if (c == NULL) c = findvhtchan(ic, chan, IEEE80211_CHAN_VHT20); if (c != NULL) chan = c; #if 0 - printf("%s: selected %d/0x%08x\n", __func__, c->ic_ieee, c->ic_flags); + net80211_ic_printf(ic, "%s: selected %d/0x%08x\n", __func__, + c->ic_ieee, c->ic_flags); #endif return (chan); } /* * Calculate the VHT operation IE for a given node. * * This includes calculating the suitable channel width/parameters * and basic MCS set. * * TODO: ensure I read 9.7.11 Rate Selection for VHT STAs. * TODO: ensure I read 10.39.7 - BSS Basic VHT-MCS and NSS set operation. */ void ieee80211_vht_get_vhtinfo_ie(struct ieee80211_node *ni, struct ieee80211_vht_operation *vhtop, int opmode) { - printf("%s: called; TODO!\n", __func__); + net80211_vap_printf(ni->ni_vap, "%s: called; TODO!\n", __func__); } /* * Return true if VHT rates can be used for the given node. */ bool ieee80211_vht_check_tx_vht(const struct ieee80211_node *ni) { const struct ieee80211vap *vap; const struct ieee80211_channel *bss_chan; if (ni == NULL || ni->ni_chan == IEEE80211_CHAN_ANYC || ni->ni_vap == NULL || ni->ni_vap->iv_bss == NULL) return (false); vap = ni->ni_vap; bss_chan = vap->iv_bss->ni_chan; if (bss_chan == IEEE80211_CHAN_ANYC) return (false); return (IEEE80211_IS_CHAN_VHT(ni->ni_chan)); } /* * Return true if VHT40 rates can be transmitted to the given node. * * This verifies that the BSS is VHT40 capable and the current * node channel width is 40MHz. */ static bool ieee80211_vht_check_tx_vht40(const struct ieee80211_node *ni) { struct ieee80211vap *vap; struct ieee80211_channel *bss_chan; if (!ieee80211_vht_check_tx_vht(ni)) return (false); vap = ni->ni_vap; bss_chan = vap->iv_bss->ni_chan; return (IEEE80211_IS_CHAN_VHT40(bss_chan) && IEEE80211_IS_CHAN_VHT40(ni->ni_chan) && (ni->ni_chw == IEEE80211_STA_RX_BW_40)); } /* * Return true if VHT80 rates can be transmitted to the given node. * * This verifies that the BSS is VHT80 capable and the current * node channel width is 80MHz. */ static bool ieee80211_vht_check_tx_vht80(const struct ieee80211_node *ni) { struct ieee80211vap *vap; struct ieee80211_channel *bss_chan; if (!ieee80211_vht_check_tx_vht(ni)) return (false); vap = ni->ni_vap; bss_chan = vap->iv_bss->ni_chan; /* * ni_chw represents 20MHz or 40MHz from the HT * TX width action frame / HT channel negotiation. * If a HT TX width action frame sets it to 20MHz * then reject doing 80MHz. */ return (IEEE80211_IS_CHAN_VHT80(bss_chan) && IEEE80211_IS_CHAN_VHT80(ni->ni_chan) && (ni->ni_chw != IEEE80211_STA_RX_BW_20)); } /* * Return true if VHT 160 rates can be transmitted to the given node. * * This verifies that the BSS is VHT80+80 or VHT160 capable and the current * node channel width is 80+80MHz or 160MHz. */ static bool ieee80211_vht_check_tx_vht160(const struct ieee80211_node *ni) { struct ieee80211vap *vap; struct ieee80211_channel *bss_chan; if (!ieee80211_vht_check_tx_vht(ni)) return (false); vap = ni->ni_vap; bss_chan = vap->iv_bss->ni_chan; /* * ni_chw represents 20MHz or 40MHz from the HT * TX width action frame / HT channel negotiation. * If a HT TX width action frame sets it to 20MHz * then reject doing 160MHz. */ if (ni->ni_chw == IEEE80211_STA_RX_BW_20) return (false); if (IEEE80211_IS_CHAN_VHT160(bss_chan) && IEEE80211_IS_CHAN_VHT160(ni->ni_chan)) return (true); if (IEEE80211_IS_CHAN_VHT80P80(bss_chan) && IEEE80211_IS_CHAN_VHT80P80(ni->ni_chan)) return (true); return (false); } /** * @brief Check if the given transmit bandwidth is available to the given node * * This checks that the node and BSS both allow the given bandwidth, * and that the current node bandwidth (which can dynamically change) * also allows said bandwidth. * * This relies on the channels having the flags for the narrower * channels as well - eg a VHT160 channel will have the CHAN_VHT80, * CHAN_VHT40, CHAN_VHT flags also set. * * @param ni the ieee80211_node to check * @param bw the required bandwidth to check * * @returns true if it is allowed, false otherwise */ bool ieee80211_vht_check_tx_bw(const struct ieee80211_node *ni, enum ieee80211_sta_rx_bw bw) { switch (bw) { case IEEE80211_STA_RX_BW_20: return (ieee80211_vht_check_tx_vht(ni)); case IEEE80211_STA_RX_BW_40: return (ieee80211_vht_check_tx_vht40(ni)); case IEEE80211_STA_RX_BW_80: return (ieee80211_vht_check_tx_vht80(ni)); case IEEE80211_STA_RX_BW_160: return (ieee80211_vht_check_tx_vht160(ni)); case IEEE80211_STA_RX_BW_320: return (false); default: return (false); } } /** * @brief Check if the given VHT bw/nss/mcs combination is valid * for the give node. * * This checks whether the given VHT bw/nss/mcs is valid based on * the negotiated rate mask in the node. * * @param ni struct ieee80211_node node to check * @param bw channel bandwidth to check * @param nss NSS * @param mcs MCS * @returns True if this combination is available, false otherwise. */ bool ieee80211_vht_node_check_tx_valid_mcs(const struct ieee80211_node *ni, enum ieee80211_sta_rx_bw bw, uint8_t nss, uint8_t mcs) { uint8_t mc; /* Validate arguments */ if (nss < 1 || nss > 8) return (false); if (mcs > 9) return (false); /* Check our choice of rate is actually valid */ if (!ieee80211_phy_vht_validate_mcs(bw, nss, mcs)) return (false); /* * Next, check if the MCS rate is available for the * given NSS. */ mc = ni->ni_vht_tx_map >> (2*(nss-1)) & 0x3; switch (mc) { case IEEE80211_VHT_MCS_NOT_SUPPORTED: /* Not supported at this NSS */ return (false); case IEEE80211_VHT_MCS_SUPPORT_0_9: return (mcs <= 9); case IEEE80211_VHT_MCS_SUPPORT_0_8: return (mcs <= 8); case IEEE80211_VHT_MCS_SUPPORT_0_7: return (mcs <= 7); default: return (false); } }