diff --git a/sys/compat/linuxkpi/common/src/linux_80211.c b/sys/compat/linuxkpi/common/src/linux_80211.c index ffc28e2685de..dba95ce6fb50 100644 --- a/sys/compat/linuxkpi/common/src/linux_80211.c +++ b/sys/compat/linuxkpi/common/src/linux_80211.c @@ -1,5330 +1,5327 @@ /*- * Copyright (c) 2020-2023 The FreeBSD Foundation * Copyright (c) 2020-2022 Bjoern A. Zeeb * * This software was developed by Björn Zeeb 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. */ /* * Public functions are called linuxkpi_*(). * Internal (static) functions are called lkpi_*(). * * The internal structures holding metadata over public structures are also * called lkpi_xxx (usually with a member at the end called xxx). * Note: we do not replicate the structure names but the general variable names * for these (e.g., struct hw -> struct lkpi_hw, struct sta -> struct lkpi_sta). * There are macros to access one from the other. * We call the internal versions lxxx (e.g., hw -> lhw, sta -> lsta). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LINUXKPI_NET80211 #include #include #include "linux_80211.h" #define LKPI_80211_WME /* #define LKPI_80211_HW_CRYPTO */ static MALLOC_DEFINE(M_LKPI80211, "lkpi80211", "LinuxKPI 80211 compat"); /* XXX-BZ really want this and others in queue.h */ #define TAILQ_ELEM_INIT(elm, field) do { \ (elm)->field.tqe_next = NULL; \ (elm)->field.tqe_prev = NULL; \ } while (0) /* -------------------------------------------------------------------------- */ /* Keep public for as long as header files are using it too. */ int linuxkpi_debug_80211; #ifdef LINUXKPI_DEBUG_80211 SYSCTL_DECL(_compat_linuxkpi); SYSCTL_NODE(_compat_linuxkpi, OID_AUTO, 80211, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "LinuxKPI 802.11 compatibility layer"); SYSCTL_INT(_compat_linuxkpi_80211, OID_AUTO, debug, CTLFLAG_RWTUN, &linuxkpi_debug_80211, 0, "LinuxKPI 802.11 debug level"); #define UNIMPLEMENTED if (linuxkpi_debug_80211 & D80211_TODO) \ printf("XXX-TODO %s:%d: UNIMPLEMENTED\n", __func__, __LINE__) #define TRACEOK() if (linuxkpi_debug_80211 & D80211_TRACEOK) \ printf("XXX-TODO %s:%d: TRACEPOINT\n", __func__, __LINE__) #else #define UNIMPLEMENTED do { } while (0) #define TRACEOK() do { } while (0) #endif /* #define PREP_TX_INFO_DURATION (IEEE80211_TRANS_WAIT * 1000) */ #ifndef PREP_TX_INFO_DURATION #define PREP_TX_INFO_DURATION 0 /* Let the driver do its thing. */ #endif /* This is DSAP | SSAP | CTRL | ProtoID/OrgCode{3}. */ const uint8_t rfc1042_header[6] = { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00 }; /* IEEE 802.11-05/0257r1 */ const uint8_t bridge_tunnel_header[6] = { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0xf8 }; /* IEEE 802.11e Table 20i-UP-to-AC mappings. */ static const uint8_t ieee80211e_up_to_ac[] = { IEEE80211_AC_BE, IEEE80211_AC_BK, IEEE80211_AC_BK, IEEE80211_AC_BE, IEEE80211_AC_VI, IEEE80211_AC_VI, IEEE80211_AC_VO, IEEE80211_AC_VO, #if 0 IEEE80211_AC_VO, /* We treat MGMT as TID 8, which is set as AC_VO */ #endif }; const struct cfg80211_ops linuxkpi_mac80211cfgops = { /* * XXX TODO need a "glue layer" to link cfg80211 ops to * mac80211 and to the driver or net80211. * Can we pass some on 1:1? Need to compare the (*f)(). */ }; static struct lkpi_sta *lkpi_find_lsta_by_ni(struct lkpi_vif *, struct ieee80211_node *); static void lkpi_80211_txq_task(void *, int); static void lkpi_ieee80211_free_skb_mbuf(void *); #ifdef LKPI_80211_WME static int lkpi_wme_update(struct lkpi_hw *, struct ieee80211vap *, bool); #endif static void lkpi_lsta_dump(struct lkpi_sta *lsta, struct ieee80211_node *ni, const char *_f, int _l) { #ifdef LINUXKPI_DEBUG_80211 if ((linuxkpi_debug_80211 & D80211_TRACE_STA) == 0) return; if (lsta == NULL) return; printf("%s:%d lsta %p ni %p sta %p\n", _f, _l, lsta, ni, &lsta->sta); if (ni != NULL) ieee80211_dump_node(NULL, ni); printf("\ttxq_task txq len %d mtx\n", mbufq_len(&lsta->txq)); printf("\tkc %p state %d added_to_drv %d in_mgd %d\n", lsta->kc, lsta->state, lsta->added_to_drv, lsta->in_mgd); #endif } static void lkpi_lsta_remove(struct lkpi_sta *lsta, struct lkpi_vif *lvif) { struct ieee80211_node *ni; IMPROVE("XXX-BZ remove tqe_prev check once ni-sta-state-sync is fixed"); ni = lsta->ni; LKPI_80211_LVIF_LOCK(lvif); if (lsta->lsta_entry.tqe_prev != NULL) TAILQ_REMOVE(&lvif->lsta_head, lsta, lsta_entry); LKPI_80211_LVIF_UNLOCK(lvif); lsta->ni = NULL; ni->ni_drv_data = NULL; if (ni != NULL) ieee80211_free_node(ni); IMPROVE("more from lkpi_ic_node_free() should happen here."); free(lsta, M_LKPI80211); } static struct lkpi_sta * lkpi_lsta_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN], struct ieee80211_hw *hw, struct ieee80211_node *ni) { struct lkpi_sta *lsta; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct ieee80211_sta *sta; int band, i, tid; lsta = malloc(sizeof(*lsta) + hw->sta_data_size, M_LKPI80211, M_NOWAIT | M_ZERO); if (lsta == NULL) return (NULL); lsta->added_to_drv = false; lsta->state = IEEE80211_STA_NOTEXIST; #if 0 /* * This needs to be done in node_init() as ieee80211_alloc_node() * will initialise the refcount after us. */ lsta->ni = ieee80211_ref_node(ni); #endif /* The back-pointer "drv_data" to net80211_node let's us get lsta. */ ni->ni_drv_data = lsta; lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); sta = LSTA_TO_STA(lsta); IEEE80211_ADDR_COPY(sta->addr, mac); /* TXQ */ for (tid = 0; tid < nitems(sta->txq); tid++) { struct lkpi_txq *ltxq; /* We are not limiting ourselves to hw.queues here. */ ltxq = malloc(sizeof(*ltxq) + hw->txq_data_size, M_LKPI80211, M_NOWAIT | M_ZERO); if (ltxq == NULL) goto cleanup; /* iwlwifi//mvm/sta.c::tid_to_mac80211_ac[] */ if (tid == IEEE80211_NUM_TIDS) { if (!ieee80211_hw_check(hw, STA_MMPDU_TXQ)) { free(ltxq, M_LKPI80211); continue; } IMPROVE("AP/if we support non-STA here too"); ltxq->txq.ac = IEEE80211_AC_VO; } else { ltxq->txq.ac = ieee80211e_up_to_ac[tid & 7]; } ltxq->seen_dequeue = false; ltxq->stopped = false; ltxq->txq.vif = vif; ltxq->txq.tid = tid; ltxq->txq.sta = sta; TAILQ_ELEM_INIT(ltxq, txq_entry); skb_queue_head_init(<xq->skbq); sta->txq[tid] = <xq->txq; } /* Deflink information. */ for (band = 0; band < NUM_NL80211_BANDS; band++) { struct ieee80211_supported_band *supband; supband = hw->wiphy->bands[band]; if (supband == NULL) continue; for (i = 0; i < supband->n_bitrates; i++) { IMPROVE("Further supband->bitrates[i]* checks?"); /* or should we get them from the ni? */ sta->deflink.supp_rates[band] |= BIT(i); } } sta->deflink.smps_mode = IEEE80211_SMPS_OFF; IMPROVE("ht, vht, he, ... bandwidth, smps_mode, .."); /* bandwidth = IEEE80211_STA_RX_... */ /* Link configuration. */ IEEE80211_ADDR_COPY(sta->deflink.addr, sta->addr); sta->link[0] = &sta->deflink; for (i = 1; i < nitems(sta->link); i++) { IMPROVE("more links; only link[0] = deflink currently."); } /* Deferred TX path. */ mtx_init(&lsta->txq_mtx, "lsta_txq", NULL, MTX_DEF); TASK_INIT(&lsta->txq_task, 0, lkpi_80211_txq_task, lsta); mbufq_init(&lsta->txq, IFQ_MAXLEN); return (lsta); cleanup: for (; tid >= 0; tid--) free(sta->txq[tid], M_LKPI80211); free(lsta, M_LKPI80211); return (NULL); } static enum nl80211_band lkpi_net80211_chan_to_nl80211_band(struct ieee80211_channel *c) { if (IEEE80211_IS_CHAN_2GHZ(c)) return (NL80211_BAND_2GHZ); else if (IEEE80211_IS_CHAN_5GHZ(c)) return (NL80211_BAND_5GHZ); #ifdef __notyet__ else if () return (NL80211_BAND_6GHZ); else if () return (NL80211_BAND_60GHZ); else if (IEEE80211_IS_CHAN_GSM(c)) return (NL80211_BAND_XXX); #endif else panic("%s: unsupported band. c %p flags %#x\n", __func__, c, c->ic_flags); } static uint32_t lkpi_nl80211_band_to_net80211_band(enum nl80211_band band) { /* XXX-BZ this is just silly; net80211 is too convoluted. */ /* IEEE80211_CHAN_A / _G / .. doesn't really work either. */ switch (band) { case NL80211_BAND_2GHZ: return (IEEE80211_CHAN_2GHZ); break; case NL80211_BAND_5GHZ: return (IEEE80211_CHAN_5GHZ); break; case NL80211_BAND_60GHZ: break; case NL80211_BAND_6GHZ: break; default: panic("%s: unsupported band %u\n", __func__, band); break; } IMPROVE(); return (0x00); } #if 0 static enum ieee80211_ac_numbers lkpi_ac_net_to_l80211(int ac) { switch (ac) { case WME_AC_VO: return (IEEE80211_AC_VO); case WME_AC_VI: return (IEEE80211_AC_VI); case WME_AC_BE: return (IEEE80211_AC_BE); case WME_AC_BK: return (IEEE80211_AC_BK); default: printf("%s: invalid WME_AC_* input: ac = %d\n", __func__, ac); return (IEEE80211_AC_BE); } } #endif static enum nl80211_iftype lkpi_opmode_to_vif_type(enum ieee80211_opmode opmode) { switch (opmode) { case IEEE80211_M_IBSS: return (NL80211_IFTYPE_ADHOC); break; case IEEE80211_M_STA: return (NL80211_IFTYPE_STATION); break; case IEEE80211_M_WDS: return (NL80211_IFTYPE_WDS); break; case IEEE80211_M_HOSTAP: return (NL80211_IFTYPE_AP); break; case IEEE80211_M_MONITOR: return (NL80211_IFTYPE_MONITOR); break; case IEEE80211_M_MBSS: return (NL80211_IFTYPE_MESH_POINT); break; case IEEE80211_M_AHDEMO: /* FALLTHROUGH */ default: printf("ERROR: %s: unsupported opmode %d\n", __func__, opmode); /* FALLTHROUGH */ } return (NL80211_IFTYPE_UNSPECIFIED); } #ifdef LKPI_80211_HW_CRYPTO static uint32_t lkpi_l80211_to_net80211_cyphers(uint32_t wlan_cipher_suite) { switch (wlan_cipher_suite) { case WLAN_CIPHER_SUITE_WEP40: return (IEEE80211_CRYPTO_WEP); case WLAN_CIPHER_SUITE_TKIP: return (IEEE80211_CRYPTO_TKIP); case WLAN_CIPHER_SUITE_CCMP: return (IEEE80211_CIPHER_AES_CCM); case WLAN_CIPHER_SUITE_WEP104: return (IEEE80211_CRYPTO_WEP); case WLAN_CIPHER_SUITE_AES_CMAC: case WLAN_CIPHER_SUITE_GCMP: case WLAN_CIPHER_SUITE_GCMP_256: case WLAN_CIPHER_SUITE_CCMP_256: case WLAN_CIPHER_SUITE_BIP_GMAC_128: case WLAN_CIPHER_SUITE_BIP_GMAC_256: case WLAN_CIPHER_SUITE_BIP_CMAC_256: printf("%s: unsupported WLAN Cipher Suite %#08x | %u\n", __func__, wlan_cipher_suite >> 8, wlan_cipher_suite & 0xff); break; default: printf("%s: unknown WLAN Cipher Suite %#08x | %u\n", __func__, wlan_cipher_suite >> 8, wlan_cipher_suite & 0xff); } return (0); } static uint32_t lkpi_net80211_to_l80211_cipher_suite(uint32_t cipher, uint8_t keylen) { switch (cipher) { case IEEE80211_CIPHER_TKIP: return (WLAN_CIPHER_SUITE_TKIP); case IEEE80211_CIPHER_AES_CCM: return (WLAN_CIPHER_SUITE_CCMP); case IEEE80211_CIPHER_WEP: if (keylen < 8) return (WLAN_CIPHER_SUITE_WEP40); else return (WLAN_CIPHER_SUITE_WEP104); break; case IEEE80211_CIPHER_AES_OCB: case IEEE80211_CIPHER_TKIPMIC: case IEEE80211_CIPHER_CKIP: case IEEE80211_CIPHER_NONE: printf("%s: unsupported cipher %#010x\n", __func__, cipher); break; default: printf("%s: unknown cipher %#010x\n", __func__, cipher); }; return (0); } #endif #ifdef __notyet__ static enum ieee80211_sta_state lkpi_net80211_state_to_sta_state(enum ieee80211_state state) { /* * XXX-BZ The net80211 states are "try to ..", the lkpi8011 states are * "done". Also ASSOC/AUTHORIZED are both "RUN" then? */ switch (state) { case IEEE80211_S_INIT: return (IEEE80211_STA_NOTEXIST); case IEEE80211_S_SCAN: return (IEEE80211_STA_NONE); case IEEE80211_S_AUTH: return (IEEE80211_STA_AUTH); case IEEE80211_S_ASSOC: return (IEEE80211_STA_ASSOC); case IEEE80211_S_RUN: return (IEEE80211_STA_AUTHORIZED); case IEEE80211_S_CAC: case IEEE80211_S_CSA: case IEEE80211_S_SLEEP: default: UNIMPLEMENTED; }; return (IEEE80211_STA_NOTEXIST); } #endif static struct linuxkpi_ieee80211_channel * lkpi_find_lkpi80211_chan(struct lkpi_hw *lhw, struct ieee80211_channel *c) { struct ieee80211_hw *hw; struct linuxkpi_ieee80211_channel *channels; enum nl80211_band band; int i, nchans; hw = LHW_TO_HW(lhw); band = lkpi_net80211_chan_to_nl80211_band(c); if (hw->wiphy->bands[band] == NULL) return (NULL); nchans = hw->wiphy->bands[band]->n_channels; if (nchans <= 0) return (NULL); channels = hw->wiphy->bands[band]->channels; for (i = 0; i < nchans; i++) { if (channels[i].hw_value == c->ic_ieee) return (&channels[i]); } return (NULL); } static struct linuxkpi_ieee80211_channel * lkpi_get_lkpi80211_chan(struct ieee80211com *ic, struct ieee80211_node *ni) { struct linuxkpi_ieee80211_channel *chan; struct ieee80211_channel *c; struct lkpi_hw *lhw; chan = NULL; if (ni != NULL && ni->ni_chan != IEEE80211_CHAN_ANYC) c = ni->ni_chan; else if (ic->ic_bsschan != IEEE80211_CHAN_ANYC) c = ic->ic_bsschan; else if (ic->ic_curchan != IEEE80211_CHAN_ANYC) c = ic->ic_curchan; else c = NULL; if (c != NULL && c != IEEE80211_CHAN_ANYC) { lhw = ic->ic_softc; chan = lkpi_find_lkpi80211_chan(lhw, c); } return (chan); } struct linuxkpi_ieee80211_channel * linuxkpi_ieee80211_get_channel(struct wiphy *wiphy, uint32_t freq) { enum nl80211_band band; for (band = 0; band < NUM_NL80211_BANDS; band++) { struct ieee80211_supported_band *supband; struct linuxkpi_ieee80211_channel *channels; int i; supband = wiphy->bands[band]; if (supband == NULL || supband->n_channels == 0) continue; channels = supband->channels; for (i = 0; i < supband->n_channels; i++) { if (channels[i].center_freq == freq) return (&channels[i]); } } return (NULL); } #ifdef LKPI_80211_HW_CRYPTO static int _lkpi_iv_key_set_delete(struct ieee80211vap *vap, const struct ieee80211_key *k, enum set_key_cmd cmd) { struct ieee80211com *ic; struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct ieee80211_sta *sta; struct ieee80211_node *ni; struct ieee80211_key_conf *kc; int error; /* XXX TODO Check (k->wk_flags & IEEE80211_KEY_SWENCRYPT) and don't upload to driver/hw? */ ic = vap->iv_ic; lhw = ic->ic_softc; hw = LHW_TO_HW(lhw); lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); memset(&kc, 0, sizeof(kc)); kc = malloc(sizeof(*kc) + k->wk_keylen, M_LKPI80211, M_WAITOK | M_ZERO); kc->cipher = lkpi_net80211_to_l80211_cipher_suite( k->wk_cipher->ic_cipher, k->wk_keylen); kc->keyidx = k->wk_keyix; #if 0 kc->hw_key_idx = /* set by hw and needs to be passed for TX */; #endif atomic64_set(&kc->tx_pn, k->wk_keytsc); kc->keylen = k->wk_keylen; memcpy(kc->key, k->wk_key, k->wk_keylen); switch (kc->cipher) { case WLAN_CIPHER_SUITE_CCMP: kc->iv_len = k->wk_cipher->ic_header; kc->icv_len = k->wk_cipher->ic_trailer; break; case WLAN_CIPHER_SUITE_TKIP: default: IMPROVE(); return (0); }; ni = vap->iv_bss; sta = ieee80211_find_sta(vif, ni->ni_bssid); if (sta != NULL) { struct lkpi_sta *lsta; lsta = STA_TO_LSTA(sta); lsta->kc = kc; } error = lkpi_80211_mo_set_key(hw, cmd, vif, sta, kc); if (error != 0) { /* XXX-BZ leaking kc currently */ ic_printf(ic, "%s: set_key failed: %d\n", __func__, error); return (0); } else { ic_printf(ic, "%s: set_key succeeded: keyidx %u hw_key_idx %u " "flags %#10x\n", __func__, kc->keyidx, kc->hw_key_idx, kc->flags); return (1); } } static int lkpi_iv_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k) { /* XXX-BZ one day we should replace this iterating over VIFs, or node list? */ return (_lkpi_iv_key_set_delete(vap, k, DISABLE_KEY)); } static int lkpi_iv_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k) { return (_lkpi_iv_key_set_delete(vap, k, SET_KEY)); } #endif static u_int lkpi_ic_update_mcast_copy(void *arg, struct sockaddr_dl *sdl, u_int cnt) { struct netdev_hw_addr_list *mc_list; struct netdev_hw_addr *addr; KASSERT(arg != NULL && sdl != NULL, ("%s: arg %p sdl %p cnt %u\n", __func__, arg, sdl, cnt)); mc_list = arg; /* If it is on the list already skip it. */ netdev_hw_addr_list_for_each(addr, mc_list) { if (!memcmp(addr->addr, LLADDR(sdl), sdl->sdl_alen)) return (0); } addr = malloc(sizeof(*addr), M_LKPI80211, M_NOWAIT | M_ZERO); if (addr == NULL) return (0); INIT_LIST_HEAD(&addr->addr_list); memcpy(addr->addr, LLADDR(sdl), sdl->sdl_alen); /* XXX this should be a netdev function? */ list_add(&addr->addr_list, &mc_list->addr_list); mc_list->count++; #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE) printf("%s:%d: mc_list count %d: added %6D\n", __func__, __LINE__, mc_list->count, addr->addr, ":"); #endif return (1); } static void lkpi_update_mcast_filter(struct ieee80211com *ic, bool force) { struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct netdev_hw_addr_list mc_list; struct list_head *le, *next; struct netdev_hw_addr *addr; struct ieee80211vap *vap; u64 mc; unsigned int changed_flags, total_flags; lhw = ic->ic_softc; if (lhw->ops->prepare_multicast == NULL || lhw->ops->configure_filter == NULL) return; if (!lhw->update_mc && !force) return; changed_flags = total_flags = 0; mc_list.count = 0; INIT_LIST_HEAD(&mc_list.addr_list); if (ic->ic_allmulti == 0) { TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if_foreach_llmaddr(vap->iv_ifp, lkpi_ic_update_mcast_copy, &mc_list); } else { changed_flags |= FIF_ALLMULTI; } hw = LHW_TO_HW(lhw); mc = lkpi_80211_mo_prepare_multicast(hw, &mc_list); /* * XXX-BZ make sure to get this sorted what is a change, * what gets all set; what was already set? */ total_flags = changed_flags; lkpi_80211_mo_configure_filter(hw, changed_flags, &total_flags, mc); #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE) printf("%s: changed_flags %#06x count %d total_flags %#010x\n", __func__, changed_flags, mc_list.count, total_flags); #endif if (mc_list.count != 0) { list_for_each_safe(le, next, &mc_list.addr_list) { addr = list_entry(le, struct netdev_hw_addr, addr_list); free(addr, M_LKPI80211); mc_list.count--; } } KASSERT(mc_list.count == 0, ("%s: mc_list %p count %d != 0\n", __func__, &mc_list, mc_list.count)); } static enum ieee80211_bss_changed lkpi_update_dtim_tsf(struct ieee80211_vif *vif, struct ieee80211_node *ni, struct ieee80211vap *vap, const char *_f, int _l) { enum ieee80211_bss_changed bss_changed; bss_changed = 0; #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE) printf("%s:%d [%s:%d] assoc %d aid %d beacon_int %u " "dtim_period %u sync_dtim_count %u sync_tsf %ju " "sync_device_ts %u bss_changed %#08x\n", __func__, __LINE__, _f, _l, vif->cfg.assoc, vif->cfg.aid, vif->bss_conf.beacon_int, vif->bss_conf.dtim_period, vif->bss_conf.sync_dtim_count, (uintmax_t)vif->bss_conf.sync_tsf, vif->bss_conf.sync_device_ts, bss_changed); #endif if (vif->bss_conf.beacon_int != ni->ni_intval) { vif->bss_conf.beacon_int = ni->ni_intval; /* iwlwifi FW bug workaround; iwl_mvm_mac_sta_state. */ if (vif->bss_conf.beacon_int < 16) vif->bss_conf.beacon_int = 16; bss_changed |= BSS_CHANGED_BEACON_INT; } if (vif->bss_conf.dtim_period != vap->iv_dtim_period && vap->iv_dtim_period > 0) { vif->bss_conf.dtim_period = vap->iv_dtim_period; bss_changed |= BSS_CHANGED_BEACON_INFO; } vif->bss_conf.sync_dtim_count = vap->iv_dtim_count; vif->bss_conf.sync_tsf = le64toh(ni->ni_tstamp.tsf); /* vif->bss_conf.sync_device_ts = set in linuxkpi_ieee80211_rx. */ #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE) printf("%s:%d [%s:%d] assoc %d aid %d beacon_int %u " "dtim_period %u sync_dtim_count %u sync_tsf %ju " "sync_device_ts %u bss_changed %#08x\n", __func__, __LINE__, _f, _l, vif->cfg.assoc, vif->cfg.aid, vif->bss_conf.beacon_int, vif->bss_conf.dtim_period, vif->bss_conf.sync_dtim_count, (uintmax_t)vif->bss_conf.sync_tsf, vif->bss_conf.sync_device_ts, bss_changed); #endif return (bss_changed); } static void lkpi_stop_hw_scan(struct lkpi_hw *lhw, struct ieee80211_vif *vif) { struct ieee80211_hw *hw; int error; bool cancel; LKPI_80211_LHW_SCAN_LOCK(lhw); cancel = (lhw->scan_flags & LKPI_LHW_SCAN_RUNNING) != 0; LKPI_80211_LHW_SCAN_UNLOCK(lhw); if (!cancel) return; hw = LHW_TO_HW(lhw); IEEE80211_UNLOCK(lhw->ic); LKPI_80211_LHW_LOCK(lhw); /* Need to cancel the scan. */ lkpi_80211_mo_cancel_hw_scan(hw, vif); LKPI_80211_LHW_UNLOCK(lhw); /* Need to make sure we see ieee80211_scan_completed. */ LKPI_80211_LHW_SCAN_LOCK(lhw); if ((lhw->scan_flags & LKPI_LHW_SCAN_RUNNING) != 0) error = msleep(lhw, &lhw->scan_mtx, 0, "lhwscanstop", hz/2); cancel = (lhw->scan_flags & LKPI_LHW_SCAN_RUNNING) != 0; LKPI_80211_LHW_SCAN_UNLOCK(lhw); IEEE80211_LOCK(lhw->ic); if (cancel) ic_printf(lhw->ic, "%s: failed to cancel scan: %d (%p, %p)\n", __func__, error, lhw, vif); } static void lkpi_hw_conf_idle(struct ieee80211_hw *hw, bool new) { struct lkpi_hw *lhw; int error; bool old; old = hw->conf.flags & IEEE80211_CONF_IDLE; if (old == new) return; hw->conf.flags ^= IEEE80211_CONF_IDLE; error = lkpi_80211_mo_config(hw, IEEE80211_CONF_CHANGE_IDLE); if (error != 0 && error != EOPNOTSUPP) { lhw = HW_TO_LHW(hw); ic_printf(lhw->ic, "ERROR: %s: config %#0x returned %d\n", __func__, IEEE80211_CONF_CHANGE_IDLE, error); } } static void lkpi_disassoc(struct ieee80211_sta *sta, struct ieee80211_vif *vif, struct lkpi_hw *lhw) { sta->aid = 0; if (vif->cfg.assoc) { struct ieee80211_hw *hw; enum ieee80211_bss_changed changed; lhw->update_mc = true; lkpi_update_mcast_filter(lhw->ic, true); changed = 0; vif->cfg.assoc = false; vif->cfg.aid = 0; changed |= BSS_CHANGED_ASSOC; /* * This will remove the sta from firmware for iwlwifi. * So confusing that they use state and flags and ... ^%$%#%$^. */ IMPROVE(); hw = LHW_TO_HW(lhw); lkpi_80211_mo_bss_info_changed(hw, vif, &vif->bss_conf, changed); lkpi_hw_conf_idle(hw, true); } } static void lkpi_wake_tx_queues(struct ieee80211_hw *hw, struct ieee80211_sta *sta, bool dequeue_seen, bool no_emptyq) { struct lkpi_txq *ltxq; int tid; /* Wake up all queues to know they are allocated in the driver. */ for (tid = 0; tid < nitems(sta->txq); tid++) { if (tid == IEEE80211_NUM_TIDS) { IMPROVE("station specific?"); if (!ieee80211_hw_check(hw, STA_MMPDU_TXQ)) continue; } else if (tid >= hw->queues) continue; if (sta->txq[tid] == NULL) continue; ltxq = TXQ_TO_LTXQ(sta->txq[tid]); if (dequeue_seen && !ltxq->seen_dequeue) continue; if (no_emptyq && skb_queue_empty(<xq->skbq)) continue; lkpi_80211_mo_wake_tx_queue(hw, sta->txq[tid]); } } /* -------------------------------------------------------------------------- */ static int lkpi_sta_state_do_nada(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { return (0); } /* lkpi_iv_newstate() handles the stop scan case generally. */ #define lkpi_sta_scan_to_init(_v, _n, _a) lkpi_sta_state_do_nada(_v, _n, _a) static int lkpi_sta_scan_to_auth(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct linuxkpi_ieee80211_channel *chan; struct lkpi_chanctx *lchanctx; struct ieee80211_chanctx_conf *conf; struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct ieee80211_node *ni; struct lkpi_sta *lsta; enum ieee80211_bss_changed bss_changed; struct ieee80211_prep_tx_info prep_tx_info; uint32_t changed; int error; chan = lkpi_get_lkpi80211_chan(vap->iv_ic, vap->iv_bss); if (chan == NULL) { ic_printf(vap->iv_ic, "%s: failed to get channel\n", __func__); return (ESRCH); } lhw = vap->iv_ic->ic_softc; hw = LHW_TO_HW(lhw); lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); ni = ieee80211_ref_node(vap->iv_bss); IEEE80211_UNLOCK(vap->iv_ic); LKPI_80211_LHW_LOCK(lhw); /* Add chanctx (or if exists, change it). */ if (vif->chanctx_conf != NULL) { conf = vif->chanctx_conf; lchanctx = CHANCTX_CONF_TO_LCHANCTX(conf); IMPROVE("diff changes for changed, working on live copy, rcu"); } else { /* Keep separate alloc as in Linux this is rcu managed? */ lchanctx = malloc(sizeof(*lchanctx) + hw->chanctx_data_size, M_LKPI80211, M_WAITOK | M_ZERO); conf = &lchanctx->conf; } conf->rx_chains_dynamic = 1; conf->rx_chains_static = 1; conf->radar_enabled = (chan->flags & IEEE80211_CHAN_RADAR) ? true : false; conf->def.chan = chan; conf->def.width = NL80211_CHAN_WIDTH_20_NOHT; conf->def.center_freq1 = chan->center_freq; conf->def.center_freq2 = 0; /* Responder ... */ conf->min_def.chan = chan; conf->min_def.width = NL80211_CHAN_WIDTH_20_NOHT; conf->min_def.center_freq1 = chan->center_freq; conf->min_def.center_freq2 = 0; IMPROVE("currently 20_NOHT only"); /* Set bss info (bss_info_changed). */ bss_changed = 0; vif->bss_conf.bssid = ni->ni_bssid; bss_changed |= BSS_CHANGED_BSSID; vif->bss_conf.txpower = ni->ni_txpower; bss_changed |= BSS_CHANGED_TXPOWER; vif->cfg.idle = false; bss_changed |= BSS_CHANGED_IDLE; /* vif->bss_conf.basic_rates ? Where exactly? */ /* Should almost assert it is this. */ vif->cfg.assoc = false; vif->cfg.aid = 0; bss_changed |= lkpi_update_dtim_tsf(vif, ni, vap, __func__, __LINE__); error = 0; if (vif->chanctx_conf != NULL) { changed = IEEE80211_CHANCTX_CHANGE_MIN_WIDTH; changed |= IEEE80211_CHANCTX_CHANGE_RADAR; changed |= IEEE80211_CHANCTX_CHANGE_RX_CHAINS; changed |= IEEE80211_CHANCTX_CHANGE_WIDTH; lkpi_80211_mo_change_chanctx(hw, conf, changed); } else { error = lkpi_80211_mo_add_chanctx(hw, conf); if (error == 0 || error == EOPNOTSUPP) { vif->bss_conf.chandef.chan = conf->def.chan; vif->bss_conf.chandef.width = conf->def.width; vif->bss_conf.chandef.center_freq1 = conf->def.center_freq1; vif->bss_conf.chandef.center_freq2 = conf->def.center_freq2; } else { ic_printf(vap->iv_ic, "%s:%d: mo_add_chanctx " "failed: %d\n", __func__, __LINE__, error); goto out; } vif->bss_conf.chanctx_conf = conf; /* Assign vif chanctx. */ if (error == 0) error = lkpi_80211_mo_assign_vif_chanctx(hw, vif, &vif->bss_conf, conf); if (error == EOPNOTSUPP) error = 0; if (error != 0) { ic_printf(vap->iv_ic, "%s:%d: mo_assign_vif_chanctx " "failed: %d\n", __func__, __LINE__, error); lkpi_80211_mo_remove_chanctx(hw, conf); lchanctx = CHANCTX_CONF_TO_LCHANCTX(conf); free(lchanctx, M_LKPI80211); goto out; } } IMPROVE("update radiotap chan fields too"); /* RATES */ IMPROVE("bss info: not all needs to come now and rates are missing"); lkpi_80211_mo_bss_info_changed(hw, vif, &vif->bss_conf, bss_changed); /* * This is a bandaid for now. If we went through (*iv_update_bss)() * and then removed the lsta we end up here without a lsta and have * to manually allocate and link it in as lkpi_ic_node_alloc()/init() * would normally do. * XXX-BZ I do not like this but currently we have no good way of * intercepting the bss swap and state changes and packets going out * workflow so live with this. It is a compat layer after all. */ if (ni->ni_drv_data == NULL) { lsta = lkpi_lsta_alloc(vap, ni->ni_macaddr, hw, ni); if (lsta == NULL) { error = ENOMEM; ic_printf(vap->iv_ic, "%s:%d: lkpi_lsta_alloc " "failed: %d\n", __func__, __LINE__, error); goto out; } lsta->ni = ieee80211_ref_node(ni); } else { lsta = ni->ni_drv_data; } /* Insert the [l]sta into the list of known stations. */ LKPI_80211_LVIF_LOCK(lvif); TAILQ_INSERT_TAIL(&lvif->lsta_head, lsta, lsta_entry); LKPI_80211_LVIF_UNLOCK(lvif); /* Add (or adjust) sta and change state (from NOTEXIST) to NONE. */ KASSERT(lsta != NULL, ("%s: ni %p lsta is NULL\n", __func__, ni)); KASSERT(lsta->state == IEEE80211_STA_NOTEXIST, ("%s: lsta %p state not " "NOTEXIST: %#x\n", __func__, lsta, lsta->state)); error = lkpi_80211_mo_sta_state(hw, vif, lsta, IEEE80211_STA_NONE); if (error != 0) { IMPROVE("do we need to undo the chan ctx?"); ic_printf(vap->iv_ic, "%s:%d: mo_sta_state(NONE) " "failed: %d\n", __func__, __LINE__, error); goto out; } #if 0 lsta->added_to_drv = true; /* mo manages. */ #endif lkpi_lsta_dump(lsta, ni, __func__, __LINE__); /* * Wakeup all queues now that sta is there so we have as much time to * possibly prepare the queue in the driver to be ready for the 1st * packet; lkpi_80211_txq_tx_one() still has a workaround as there * is no guarantee or way to check. * XXX-BZ and by now we know that this does not work on all drivers * for all queues. */ lkpi_wake_tx_queues(hw, LSTA_TO_STA(lsta), false, false); /* Start mgd_prepare_tx. */ memset(&prep_tx_info, 0, sizeof(prep_tx_info)); prep_tx_info.duration = PREP_TX_INFO_DURATION; lkpi_80211_mo_mgd_prepare_tx(hw, vif, &prep_tx_info); lsta->in_mgd = true; /* * What is going to happen next: * - .. we should end up in "auth_to_assoc" * - event_callback * - update sta_state (NONE to AUTH) * - mgd_complete_tx * (ideally we'd do that on a callback for something else ...) */ out: LKPI_80211_LHW_UNLOCK(lhw); IEEE80211_LOCK(vap->iv_ic); if (ni != NULL) ieee80211_free_node(ni); return (error); } static int lkpi_sta_auth_to_scan(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct ieee80211_node *ni; struct lkpi_sta *lsta; struct ieee80211_sta *sta; struct ieee80211_prep_tx_info prep_tx_info; int error; lhw = vap->iv_ic->ic_softc; hw = LHW_TO_HW(lhw); lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); /* Keep ni around. */ ni = ieee80211_ref_node(vap->iv_bss); lsta = ni->ni_drv_data; sta = LSTA_TO_STA(lsta); lkpi_lsta_dump(lsta, ni, __func__, __LINE__); IEEE80211_UNLOCK(vap->iv_ic); LKPI_80211_LHW_LOCK(lhw); /* flush, drop. */ lkpi_80211_mo_flush(hw, vif, nitems(sta->txq), true); /* Wake tx queues to get packet(s) out. */ lkpi_wake_tx_queues(hw, sta, true, true); /* flush, no drop */ lkpi_80211_mo_flush(hw, vif, nitems(sta->txq), false); /* End mgd_complete_tx. */ if (lsta->in_mgd) { memset(&prep_tx_info, 0, sizeof(prep_tx_info)); prep_tx_info.success = false; lkpi_80211_mo_mgd_complete_tx(hw, vif, &prep_tx_info); lsta->in_mgd = false; } /* sync_rx_queues */ lkpi_80211_mo_sync_rx_queues(hw); /* sta_pre_rcu_remove */ lkpi_80211_mo_sta_pre_rcu_remove(hw, vif, sta); /* Take the station down. */ /* Adjust sta and change state (from NONE) to NOTEXIST. */ KASSERT(lsta != NULL, ("%s: ni %p lsta is NULL\n", __func__, ni)); KASSERT(lsta->state == IEEE80211_STA_NONE, ("%s: lsta %p state not " "NONE: %#x, nstate %d arg %d\n", __func__, lsta, lsta->state, nstate, arg)); error = lkpi_80211_mo_sta_state(hw, vif, lsta, IEEE80211_STA_NOTEXIST); if (error != 0) { IMPROVE("do we need to undo the chan ctx?"); ic_printf(vap->iv_ic, "%s:%d: mo_sta_state(NOTEXIST) " "failed: %d\n", __func__, __LINE__, error); goto out; } #if 0 lsta->added_to_drv = false; /* mo manages. */ #endif lkpi_lsta_dump(lsta, ni, __func__, __LINE__); lkpi_lsta_remove(lsta, lvif); /* conf_tx */ /* Take the chan ctx down. */ if (vif->chanctx_conf != NULL) { struct lkpi_chanctx *lchanctx; struct ieee80211_chanctx_conf *conf; conf = vif->chanctx_conf; /* Remove vif context. */ lkpi_80211_mo_unassign_vif_chanctx(hw, vif, &vif->bss_conf, &vif->chanctx_conf); /* NB: vif->chanctx_conf is NULL now. */ /* Remove chan ctx. */ lkpi_80211_mo_remove_chanctx(hw, conf); lchanctx = CHANCTX_CONF_TO_LCHANCTX(conf); free(lchanctx, M_LKPI80211); } out: LKPI_80211_LHW_UNLOCK(lhw); IEEE80211_LOCK(vap->iv_ic); if (ni != NULL) ieee80211_free_node(ni); return (error); } static int lkpi_sta_auth_to_init(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { int error; error = lkpi_sta_auth_to_scan(vap, nstate, arg); if (error == 0) error = lkpi_sta_scan_to_init(vap, nstate, arg); return (error); } static int lkpi_sta_auth_to_assoc(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct ieee80211_node *ni; struct lkpi_sta *lsta; struct ieee80211_prep_tx_info prep_tx_info; int error; lhw = vap->iv_ic->ic_softc; hw = LHW_TO_HW(lhw); lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); IEEE80211_UNLOCK(vap->iv_ic); LKPI_80211_LHW_LOCK(lhw); ni = NULL; /* Finish auth. */ IMPROVE("event callback"); /* Update sta_state (NONE to AUTH). */ ni = ieee80211_ref_node(vap->iv_bss); lsta = ni->ni_drv_data; KASSERT(lsta != NULL, ("%s: ni %p lsta is NULL\n", __func__, ni)); KASSERT(lsta->state == IEEE80211_STA_NONE, ("%s: lsta %p state not " "NONE: %#x\n", __func__, lsta, lsta->state)); error = lkpi_80211_mo_sta_state(hw, vif, lsta, IEEE80211_STA_AUTH); if (error != 0) { ic_printf(vap->iv_ic, "%s:%d: mo_sta_state(AUTH) " "failed: %d\n", __func__, __LINE__, error); goto out; } /* End mgd_complete_tx. */ if (lsta->in_mgd) { memset(&prep_tx_info, 0, sizeof(prep_tx_info)); prep_tx_info.success = true; lkpi_80211_mo_mgd_complete_tx(hw, vif, &prep_tx_info); lsta->in_mgd = false; } /* Now start assoc. */ /* Start mgd_prepare_tx. */ if (!lsta->in_mgd) { memset(&prep_tx_info, 0, sizeof(prep_tx_info)); prep_tx_info.duration = PREP_TX_INFO_DURATION; lkpi_80211_mo_mgd_prepare_tx(hw, vif, &prep_tx_info); lsta->in_mgd = true; } /* Wake tx queue to get packet out. */ lkpi_wake_tx_queues(hw, LSTA_TO_STA(lsta), true, true); /* * .. we end up in "assoc_to_run" * - update sta_state (AUTH to ASSOC) * - conf_tx [all] * - bss_info_changed (assoc, aid, ssid, ..) * - change_chanctx (if needed) * - event_callback * - mgd_complete_tx */ out: LKPI_80211_LHW_UNLOCK(lhw); IEEE80211_LOCK(vap->iv_ic); if (ni != NULL) ieee80211_free_node(ni); return (error); } /* auth_to_auth, assoc_to_assoc. */ static int lkpi_sta_a_to_a(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct ieee80211_node *ni; struct lkpi_sta *lsta; struct ieee80211_prep_tx_info prep_tx_info; lhw = vap->iv_ic->ic_softc; hw = LHW_TO_HW(lhw); lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); ni = ieee80211_ref_node(vap->iv_bss); IEEE80211_UNLOCK(vap->iv_ic); LKPI_80211_LHW_LOCK(lhw); lsta = ni->ni_drv_data; IMPROVE("event callback?"); /* End mgd_complete_tx. */ if (lsta->in_mgd) { memset(&prep_tx_info, 0, sizeof(prep_tx_info)); prep_tx_info.success = false; lkpi_80211_mo_mgd_complete_tx(hw, vif, &prep_tx_info); lsta->in_mgd = false; } /* Now start assoc. */ /* Start mgd_prepare_tx. */ if (!lsta->in_mgd) { memset(&prep_tx_info, 0, sizeof(prep_tx_info)); prep_tx_info.duration = PREP_TX_INFO_DURATION; lkpi_80211_mo_mgd_prepare_tx(hw, vif, &prep_tx_info); lsta->in_mgd = true; } LKPI_80211_LHW_UNLOCK(lhw); IEEE80211_LOCK(vap->iv_ic); if (ni != NULL) ieee80211_free_node(ni); return (0); } static int _lkpi_sta_assoc_to_down(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct ieee80211_node *ni; struct lkpi_sta *lsta; struct ieee80211_sta *sta; struct ieee80211_prep_tx_info prep_tx_info; enum ieee80211_bss_changed bss_changed; int error; lhw = vap->iv_ic->ic_softc; hw = LHW_TO_HW(lhw); lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); /* Keep ni around. */ ni = ieee80211_ref_node(vap->iv_bss); lsta = ni->ni_drv_data; sta = LSTA_TO_STA(lsta); lkpi_lsta_dump(lsta, ni, __func__, __LINE__); IEEE80211_UNLOCK(vap->iv_ic); LKPI_80211_LHW_LOCK(lhw); /* flush, drop. */ lkpi_80211_mo_flush(hw, vif, nitems(sta->txq), true); IMPROVE("What are the proper conditions for DEAUTH_NEED_MGD_TX_PREP?"); if (ieee80211_hw_check(hw, DEAUTH_NEED_MGD_TX_PREP) && !lsta->in_mgd) { memset(&prep_tx_info, 0, sizeof(prep_tx_info)); prep_tx_info.duration = PREP_TX_INFO_DURATION; lkpi_80211_mo_mgd_prepare_tx(hw, vif, &prep_tx_info); lsta->in_mgd = true; } LKPI_80211_LHW_UNLOCK(lhw); IEEE80211_LOCK(vap->iv_ic); /* Call iv_newstate first so we get potential DISASSOC packet out. */ error = lvif->iv_newstate(vap, nstate, arg); if (error != 0) { ic_printf(vap->iv_ic, "%s:%d: iv_newstate(%p, %d, %d) " "failed: %d\n", __func__, __LINE__, vap, nstate, arg, error); goto outni; } IEEE80211_UNLOCK(vap->iv_ic); LKPI_80211_LHW_LOCK(lhw); lkpi_lsta_dump(lsta, ni, __func__, __LINE__); /* Wake tx queues to get packet(s) out. */ lkpi_wake_tx_queues(hw, sta, true, true); /* flush, no drop */ lkpi_80211_mo_flush(hw, vif, nitems(sta->txq), false); /* End mgd_complete_tx. */ if (lsta->in_mgd) { memset(&prep_tx_info, 0, sizeof(prep_tx_info)); prep_tx_info.success = false; lkpi_80211_mo_mgd_complete_tx(hw, vif, &prep_tx_info); lsta->in_mgd = false; } /* sync_rx_queues */ lkpi_80211_mo_sync_rx_queues(hw); /* sta_pre_rcu_remove */ lkpi_80211_mo_sta_pre_rcu_remove(hw, vif, sta); /* Take the station down. */ /* Update sta and change state (from AUTH) to NONE. */ KASSERT(lsta != NULL, ("%s: ni %p lsta is NULL\n", __func__, ni)); KASSERT(lsta->state == IEEE80211_STA_AUTH, ("%s: lsta %p state not " "AUTH: %#x\n", __func__, lsta, lsta->state)); error = lkpi_80211_mo_sta_state(hw, vif, lsta, IEEE80211_STA_NONE); if (error != 0) { ic_printf(vap->iv_ic, "%s:%d: mo_sta_state(NONE) " "failed: %d\n", __func__, __LINE__, error); goto out; } lkpi_lsta_dump(lsta, ni, __func__, __LINE__); /* Update bss info (bss_info_changed) (assoc, aid, ..). */ /* * We need to do this now, before sta changes to IEEE80211_STA_NOTEXIST * as otherwise drivers (iwlwifi at least) will silently not remove * the sta from the firmware and when we will add a new one trigger * a fw assert. */ lkpi_disassoc(sta, vif, lhw); /* Adjust sta and change state (from NONE) to NOTEXIST. */ KASSERT(lsta != NULL, ("%s: ni %p lsta is NULL\n", __func__, ni)); KASSERT(lsta->state == IEEE80211_STA_NONE, ("%s: lsta %p state not " "NONE: %#x, nstate %d arg %d\n", __func__, lsta, lsta->state, nstate, arg)); error = lkpi_80211_mo_sta_state(hw, vif, lsta, IEEE80211_STA_NOTEXIST); if (error != 0) { IMPROVE("do we need to undo the chan ctx?"); ic_printf(vap->iv_ic, "%s:%d: mo_sta_state(NOTEXIST) " "failed: %d\n", __func__, __LINE__, error); goto out; } lkpi_lsta_dump(lsta, ni, __func__, __LINE__); /* sta no longer save to use. */ IMPROVE("Any bss_info changes to announce?"); bss_changed = 0; vif->bss_conf.qos = 0; bss_changed |= BSS_CHANGED_QOS; vif->cfg.ssid_len = 0; memset(vif->cfg.ssid, '\0', sizeof(vif->cfg.ssid)); bss_changed |= BSS_CHANGED_BSSID; lkpi_80211_mo_bss_info_changed(hw, vif, &vif->bss_conf, bss_changed); lkpi_lsta_remove(lsta, lvif); /* conf_tx */ /* Take the chan ctx down. */ if (vif->chanctx_conf != NULL) { struct lkpi_chanctx *lchanctx; struct ieee80211_chanctx_conf *conf; conf = vif->chanctx_conf; /* Remove vif context. */ lkpi_80211_mo_unassign_vif_chanctx(hw, vif, &vif->bss_conf, &vif->chanctx_conf); /* NB: vif->chanctx_conf is NULL now. */ /* Remove chan ctx. */ lkpi_80211_mo_remove_chanctx(hw, conf); lchanctx = CHANCTX_CONF_TO_LCHANCTX(conf); free(lchanctx, M_LKPI80211); } error = EALREADY; out: LKPI_80211_LHW_UNLOCK(lhw); IEEE80211_LOCK(vap->iv_ic); outni: if (ni != NULL) ieee80211_free_node(ni); return (error); } static int lkpi_sta_assoc_to_auth(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { int error; error = _lkpi_sta_assoc_to_down(vap, nstate, arg); if (error != 0 && error != EALREADY) return (error); /* At this point iv_bss is long a new node! */ error |= lkpi_sta_scan_to_auth(vap, nstate, 0); return (error); } static int lkpi_sta_assoc_to_scan(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { int error; error = _lkpi_sta_assoc_to_down(vap, nstate, arg); return (error); } static int lkpi_sta_assoc_to_init(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { int error; error = _lkpi_sta_assoc_to_down(vap, nstate, arg); return (error); } static int lkpi_sta_assoc_to_run(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct ieee80211_node *ni; struct lkpi_sta *lsta; struct ieee80211_sta *sta; struct ieee80211_prep_tx_info prep_tx_info; enum ieee80211_bss_changed bss_changed; int error; lhw = vap->iv_ic->ic_softc; hw = LHW_TO_HW(lhw); lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); IEEE80211_UNLOCK(vap->iv_ic); LKPI_80211_LHW_LOCK(lhw); ni = NULL; IMPROVE("ponder some of this moved to ic_newassoc, scan_assoc_success, " "and to lesser extend ieee80211_notify_node_join"); /* Finish assoc. */ /* Update sta_state (AUTH to ASSOC) and set aid. */ ni = ieee80211_ref_node(vap->iv_bss); lsta = ni->ni_drv_data; KASSERT(lsta != NULL, ("%s: ni %p lsta is NULL\n", __func__, ni)); KASSERT(lsta->state == IEEE80211_STA_AUTH, ("%s: lsta %p state not " "AUTH: %#x\n", __func__, lsta, lsta->state)); sta = LSTA_TO_STA(lsta); sta->aid = IEEE80211_NODE_AID(ni); #ifdef LKPI_80211_WME if (vap->iv_flags & IEEE80211_F_WME) sta->wme = true; #endif error = lkpi_80211_mo_sta_state(hw, vif, lsta, IEEE80211_STA_ASSOC); if (error != 0) { ic_printf(vap->iv_ic, "%s:%d: mo_sta_state(ASSOC) " "failed: %d\n", __func__, __LINE__, error); goto out; } IMPROVE("wme / conf_tx [all]"); /* Update bss info (bss_info_changed) (assoc, aid, ..). */ bss_changed = 0; #ifdef LKPI_80211_WME bss_changed |= lkpi_wme_update(lhw, vap, true); #endif if (!vif->cfg.assoc || vif->cfg.aid != IEEE80211_NODE_AID(ni)) { vif->cfg.assoc = true; vif->cfg.aid = IEEE80211_NODE_AID(ni); bss_changed |= BSS_CHANGED_ASSOC; } /* We set SSID but this is not BSSID! */ vif->cfg.ssid_len = ni->ni_esslen; memcpy(vif->cfg.ssid, ni->ni_essid, ni->ni_esslen); if ((vap->iv_flags & IEEE80211_F_SHPREAMBLE) != vif->bss_conf.use_short_preamble) { vif->bss_conf.use_short_preamble ^= 1; /* bss_changed |= BSS_CHANGED_??? */ } if ((vap->iv_flags & IEEE80211_F_SHSLOT) != vif->bss_conf.use_short_slot) { vif->bss_conf.use_short_slot ^= 1; /* bss_changed |= BSS_CHANGED_??? */ } if ((ni->ni_flags & IEEE80211_NODE_QOS) != vif->bss_conf.qos) { vif->bss_conf.qos ^= 1; bss_changed |= BSS_CHANGED_QOS; } bss_changed |= lkpi_update_dtim_tsf(vif, ni, vap, __func__, __LINE__); lkpi_80211_mo_bss_info_changed(hw, vif, &vif->bss_conf, bss_changed); /* - change_chanctx (if needed) * - event_callback */ /* End mgd_complete_tx. */ if (lsta->in_mgd) { memset(&prep_tx_info, 0, sizeof(prep_tx_info)); prep_tx_info.success = true; lkpi_80211_mo_mgd_complete_tx(hw, vif, &prep_tx_info); lsta->in_mgd = false; } lkpi_hw_conf_idle(hw, false); /* * And then: * - (more packets)? * - set_key * - set_default_unicast_key * - set_key (?) * - ipv6_addr_change (?) */ /* Prepare_multicast && configure_filter. */ lhw->update_mc = true; lkpi_update_mcast_filter(vap->iv_ic, true); if (!ieee80211_node_is_authorized(ni)) { IMPROVE("net80211 does not consider node authorized"); } /* Update sta_state (ASSOC to AUTHORIZED). */ KASSERT(lsta != NULL, ("%s: ni %p lsta is NULL\n", __func__, ni)); KASSERT(lsta->state == IEEE80211_STA_ASSOC, ("%s: lsta %p state not " "ASSOC: %#x\n", __func__, lsta, lsta->state)); error = lkpi_80211_mo_sta_state(hw, vif, lsta, IEEE80211_STA_AUTHORIZED); if (error != 0) { IMPROVE("undo some changes?"); ic_printf(vap->iv_ic, "%s:%d: mo_sta_state(AUTHORIZED) " "failed: %d\n", __func__, __LINE__, error); goto out; } /* - drv_config (?) * - bss_info_changed * - set_rekey_data (?) * * And now we should be passing packets. */ IMPROVE("Need that bssid setting, and the keys"); bss_changed = 0; bss_changed |= lkpi_update_dtim_tsf(vif, ni, vap, __func__, __LINE__); lkpi_80211_mo_bss_info_changed(hw, vif, &vif->bss_conf, bss_changed); out: LKPI_80211_LHW_UNLOCK(lhw); IEEE80211_LOCK(vap->iv_ic); if (ni != NULL) ieee80211_free_node(ni); return (error); } static int lkpi_sta_auth_to_run(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { int error; error = lkpi_sta_auth_to_assoc(vap, nstate, arg); if (error == 0) error = lkpi_sta_assoc_to_run(vap, nstate, arg); return (error); } static int lkpi_sta_run_to_assoc(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct ieee80211_node *ni; struct lkpi_sta *lsta; struct ieee80211_sta *sta; struct ieee80211_prep_tx_info prep_tx_info; #if 0 enum ieee80211_bss_changed bss_changed; #endif int error; lhw = vap->iv_ic->ic_softc; hw = LHW_TO_HW(lhw); lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); /* Keep ni around. */ ni = ieee80211_ref_node(vap->iv_bss); lsta = ni->ni_drv_data; sta = LSTA_TO_STA(lsta); lkpi_lsta_dump(lsta, ni, __func__, __LINE__); IEEE80211_UNLOCK(vap->iv_ic); LKPI_80211_LHW_LOCK(lhw); /* flush, drop. */ lkpi_80211_mo_flush(hw, vif, nitems(sta->txq), true); IMPROVE("What are the proper conditions for DEAUTH_NEED_MGD_TX_PREP?"); if (ieee80211_hw_check(hw, DEAUTH_NEED_MGD_TX_PREP) && !lsta->in_mgd) { memset(&prep_tx_info, 0, sizeof(prep_tx_info)); prep_tx_info.duration = PREP_TX_INFO_DURATION; lkpi_80211_mo_mgd_prepare_tx(hw, vif, &prep_tx_info); lsta->in_mgd = true; } LKPI_80211_LHW_UNLOCK(lhw); IEEE80211_LOCK(vap->iv_ic); /* Call iv_newstate first so we get potential DISASSOC packet out. */ error = lvif->iv_newstate(vap, nstate, arg); if (error != 0) { ic_printf(vap->iv_ic, "%s:%d: iv_newstate(%p, %d, %d) " "failed: %d\n", __func__, __LINE__, vap, nstate, arg, error); goto outni; } IEEE80211_UNLOCK(vap->iv_ic); LKPI_80211_LHW_LOCK(lhw); lkpi_lsta_dump(lsta, ni, __func__, __LINE__); /* Wake tx queues to get packet(s) out. */ lkpi_wake_tx_queues(hw, sta, true, true); /* flush, no drop */ lkpi_80211_mo_flush(hw, vif, nitems(sta->txq), false); /* End mgd_complete_tx. */ if (lsta->in_mgd) { memset(&prep_tx_info, 0, sizeof(prep_tx_info)); prep_tx_info.success = false; lkpi_80211_mo_mgd_complete_tx(hw, vif, &prep_tx_info); lsta->in_mgd = false; } #if 0 /* sync_rx_queues */ lkpi_80211_mo_sync_rx_queues(hw); /* sta_pre_rcu_remove */ lkpi_80211_mo_sta_pre_rcu_remove(hw, vif, sta); #endif /* Take the station down. */ /* Adjust sta and change state (from AUTHORIZED) to ASSOC. */ KASSERT(lsta != NULL, ("%s: ni %p lsta is NULL\n", __func__, ni)); KASSERT(lsta->state == IEEE80211_STA_AUTHORIZED, ("%s: lsta %p state not " "AUTHORIZED: %#x\n", __func__, lsta, lsta->state)); error = lkpi_80211_mo_sta_state(hw, vif, lsta, IEEE80211_STA_ASSOC); if (error != 0) { ic_printf(vap->iv_ic, "%s:%d: mo_sta_state(ASSOC) " "failed: %d\n", __func__, __LINE__, error); goto out; } lkpi_lsta_dump(lsta, ni, __func__, __LINE__); /* Update sta_state (ASSOC to AUTH). */ KASSERT(lsta != NULL, ("%s: ni %p lsta is NULL\n", __func__, ni)); KASSERT(lsta->state == IEEE80211_STA_ASSOC, ("%s: lsta %p state not " "ASSOC: %#x\n", __func__, lsta, lsta->state)); error = lkpi_80211_mo_sta_state(hw, vif, lsta, IEEE80211_STA_AUTH); if (error != 0) { ic_printf(vap->iv_ic, "%s:%d: mo_sta_state(AUTH) " "failed: %d\n", __func__, __LINE__, error); goto out; } lkpi_lsta_dump(lsta, ni, __func__, __LINE__); #if 0 /* Update bss info (bss_info_changed) (assoc, aid, ..). */ lkpi_disassoc(sta, vif, lhw); #endif error = EALREADY; out: LKPI_80211_LHW_UNLOCK(lhw); IEEE80211_LOCK(vap->iv_ic); outni: if (ni != NULL) ieee80211_free_node(ni); return (error); } static int lkpi_sta_run_to_init(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct ieee80211_node *ni; struct lkpi_sta *lsta; struct ieee80211_sta *sta; struct ieee80211_prep_tx_info prep_tx_info; enum ieee80211_bss_changed bss_changed; int error; lhw = vap->iv_ic->ic_softc; hw = LHW_TO_HW(lhw); lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); /* Keep ni around. */ ni = ieee80211_ref_node(vap->iv_bss); lsta = ni->ni_drv_data; sta = LSTA_TO_STA(lsta); lkpi_lsta_dump(lsta, ni, __func__, __LINE__); IEEE80211_UNLOCK(vap->iv_ic); LKPI_80211_LHW_LOCK(lhw); /* flush, drop. */ lkpi_80211_mo_flush(hw, vif, nitems(sta->txq), true); IMPROVE("What are the proper conditions for DEAUTH_NEED_MGD_TX_PREP?"); if (ieee80211_hw_check(hw, DEAUTH_NEED_MGD_TX_PREP) && !lsta->in_mgd) { memset(&prep_tx_info, 0, sizeof(prep_tx_info)); prep_tx_info.duration = PREP_TX_INFO_DURATION; lkpi_80211_mo_mgd_prepare_tx(hw, vif, &prep_tx_info); lsta->in_mgd = true; } LKPI_80211_LHW_UNLOCK(lhw); IEEE80211_LOCK(vap->iv_ic); /* Call iv_newstate first so we get potential DISASSOC packet out. */ error = lvif->iv_newstate(vap, nstate, arg); if (error != 0) { ic_printf(vap->iv_ic, "%s:%d: iv_newstate(%p, %d, %d) " "failed: %d\n", __func__, __LINE__, vap, nstate, arg, error); goto outni; } IEEE80211_UNLOCK(vap->iv_ic); LKPI_80211_LHW_LOCK(lhw); lkpi_lsta_dump(lsta, ni, __func__, __LINE__); /* Wake tx queues to get packet(s) out. */ lkpi_wake_tx_queues(hw, sta, true, true); /* flush, no drop */ lkpi_80211_mo_flush(hw, vif, nitems(sta->txq), false); /* End mgd_complete_tx. */ if (lsta->in_mgd) { memset(&prep_tx_info, 0, sizeof(prep_tx_info)); prep_tx_info.success = false; lkpi_80211_mo_mgd_complete_tx(hw, vif, &prep_tx_info); lsta->in_mgd = false; } /* sync_rx_queues */ lkpi_80211_mo_sync_rx_queues(hw); /* sta_pre_rcu_remove */ lkpi_80211_mo_sta_pre_rcu_remove(hw, vif, sta); /* Take the station down. */ /* Adjust sta and change state (from AUTHORIZED) to ASSOC. */ KASSERT(lsta != NULL, ("%s: ni %p lsta is NULL\n", __func__, ni)); KASSERT(lsta->state == IEEE80211_STA_AUTHORIZED, ("%s: lsta %p state not " "AUTHORIZED: %#x\n", __func__, lsta, lsta->state)); error = lkpi_80211_mo_sta_state(hw, vif, lsta, IEEE80211_STA_ASSOC); if (error != 0) { ic_printf(vap->iv_ic, "%s:%d: mo_sta_state(ASSOC) " "failed: %d\n", __func__, __LINE__, error); goto out; } lkpi_lsta_dump(lsta, ni, __func__, __LINE__); /* Update sta_state (ASSOC to AUTH). */ KASSERT(lsta != NULL, ("%s: ni %p lsta is NULL\n", __func__, ni)); KASSERT(lsta->state == IEEE80211_STA_ASSOC, ("%s: lsta %p state not " "ASSOC: %#x\n", __func__, lsta, lsta->state)); error = lkpi_80211_mo_sta_state(hw, vif, lsta, IEEE80211_STA_AUTH); if (error != 0) { ic_printf(vap->iv_ic, "%s:%d: mo_sta_state(AUTH) " "failed: %d\n", __func__, __LINE__, error); goto out; } lkpi_lsta_dump(lsta, ni, __func__, __LINE__); /* Update sta and change state (from AUTH) to NONE. */ KASSERT(lsta != NULL, ("%s: ni %p lsta is NULL\n", __func__, ni)); KASSERT(lsta->state == IEEE80211_STA_AUTH, ("%s: lsta %p state not " "AUTH: %#x\n", __func__, lsta, lsta->state)); error = lkpi_80211_mo_sta_state(hw, vif, lsta, IEEE80211_STA_NONE); if (error != 0) { ic_printf(vap->iv_ic, "%s:%d: mo_sta_state(NONE) " "failed: %d\n", __func__, __LINE__, error); goto out; } lkpi_lsta_dump(lsta, ni, __func__, __LINE__); /* Update bss info (bss_info_changed) (assoc, aid, ..). */ /* * One would expect this to happen when going off AUTHORIZED. * See comment there; removes the sta from fw. */ lkpi_disassoc(sta, vif, lhw); /* Adjust sta and change state (from NONE) to NOTEXIST. */ KASSERT(lsta != NULL, ("%s: ni %p lsta is NULL\n", __func__, ni)); KASSERT(lsta->state == IEEE80211_STA_NONE, ("%s: lsta %p state not " "NONE: %#x, nstate %d arg %d\n", __func__, lsta, lsta->state, nstate, arg)); error = lkpi_80211_mo_sta_state(hw, vif, lsta, IEEE80211_STA_NOTEXIST); if (error != 0) { IMPROVE("do we need to undo the chan ctx?"); ic_printf(vap->iv_ic, "%s:%d: mo_sta_state(NOTEXIST) " "failed: %d\n", __func__, __LINE__, error); goto out; } lkpi_lsta_dump(lsta, ni, __func__, __LINE__); /* sta no longer save to use. */ IMPROVE("Any bss_info changes to announce?"); bss_changed = 0; vif->bss_conf.qos = 0; bss_changed |= BSS_CHANGED_QOS; vif->cfg.ssid_len = 0; memset(vif->cfg.ssid, '\0', sizeof(vif->cfg.ssid)); bss_changed |= BSS_CHANGED_BSSID; lkpi_80211_mo_bss_info_changed(hw, vif, &vif->bss_conf, bss_changed); lkpi_lsta_remove(lsta, lvif); /* conf_tx */ /* Take the chan ctx down. */ if (vif->chanctx_conf != NULL) { struct lkpi_chanctx *lchanctx; struct ieee80211_chanctx_conf *conf; conf = vif->chanctx_conf; /* Remove vif context. */ lkpi_80211_mo_unassign_vif_chanctx(hw, vif, &vif->bss_conf, &vif->chanctx_conf); /* NB: vif->chanctx_conf is NULL now. */ /* Remove chan ctx. */ lkpi_80211_mo_remove_chanctx(hw, conf); lchanctx = CHANCTX_CONF_TO_LCHANCTX(conf); free(lchanctx, M_LKPI80211); } error = EALREADY; out: LKPI_80211_LHW_UNLOCK(lhw); IEEE80211_LOCK(vap->iv_ic); outni: if (ni != NULL) ieee80211_free_node(ni); return (error); } static int lkpi_sta_run_to_scan(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { return (lkpi_sta_run_to_init(vap, nstate, arg)); } static int lkpi_sta_run_to_auth(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { int error; error = lkpi_sta_run_to_init(vap, nstate, arg); if (error != 0 && error != EALREADY) return (error); /* At this point iv_bss is long a new node! */ error |= lkpi_sta_scan_to_auth(vap, nstate, 0); return (error); } /* -------------------------------------------------------------------------- */ /* * The matches the documented state changes in net80211::sta_newstate(). * XXX (1) without CSA and SLEEP yet, * XXX (2) not all unhandled cases * there are "invalid" (so there is a room for failure here). */ struct fsm_state { /* INIT, SCAN, AUTH, ASSOC, CAC, RUN, CSA, SLEEP */ enum ieee80211_state ostate; enum ieee80211_state nstate; int (*handler)(struct ieee80211vap *, enum ieee80211_state, int); } sta_state_fsm[] = { { IEEE80211_S_INIT, IEEE80211_S_INIT, lkpi_sta_state_do_nada }, { IEEE80211_S_SCAN, IEEE80211_S_INIT, lkpi_sta_state_do_nada }, /* scan_to_init */ { IEEE80211_S_AUTH, IEEE80211_S_INIT, lkpi_sta_auth_to_init }, /* not explicitly in sta_newstate() */ { IEEE80211_S_ASSOC, IEEE80211_S_INIT, lkpi_sta_assoc_to_init }, /* Send DEAUTH. */ { IEEE80211_S_RUN, IEEE80211_S_INIT, lkpi_sta_run_to_init }, /* Send DISASSOC. */ { IEEE80211_S_INIT, IEEE80211_S_SCAN, lkpi_sta_state_do_nada }, { IEEE80211_S_SCAN, IEEE80211_S_SCAN, lkpi_sta_state_do_nada }, { IEEE80211_S_AUTH, IEEE80211_S_SCAN, lkpi_sta_auth_to_scan }, { IEEE80211_S_ASSOC, IEEE80211_S_SCAN, lkpi_sta_assoc_to_scan }, { IEEE80211_S_RUN, IEEE80211_S_SCAN, lkpi_sta_run_to_scan }, /* Beacon miss. */ { IEEE80211_S_INIT, IEEE80211_S_AUTH, lkpi_sta_scan_to_auth }, /* Send AUTH. */ { IEEE80211_S_SCAN, IEEE80211_S_AUTH, lkpi_sta_scan_to_auth }, /* Send AUTH. */ { IEEE80211_S_AUTH, IEEE80211_S_AUTH, lkpi_sta_a_to_a }, /* Send ?AUTH. */ { IEEE80211_S_ASSOC, IEEE80211_S_AUTH, lkpi_sta_assoc_to_auth }, /* Send ?AUTH. */ { IEEE80211_S_RUN, IEEE80211_S_AUTH, lkpi_sta_run_to_auth }, /* Send ?AUTH. */ { IEEE80211_S_AUTH, IEEE80211_S_ASSOC, lkpi_sta_auth_to_assoc }, /* Send ASSOCREQ. */ { IEEE80211_S_ASSOC, IEEE80211_S_ASSOC, lkpi_sta_a_to_a }, /* Send ASSOCREQ. */ { IEEE80211_S_RUN, IEEE80211_S_ASSOC, lkpi_sta_run_to_assoc }, /* Send ASSOCREQ/REASSOCREQ. */ { IEEE80211_S_AUTH, IEEE80211_S_RUN, lkpi_sta_auth_to_run }, { IEEE80211_S_ASSOC, IEEE80211_S_RUN, lkpi_sta_assoc_to_run }, { IEEE80211_S_RUN, IEEE80211_S_RUN, lkpi_sta_state_do_nada }, /* Dummy at the end without handler. */ { IEEE80211_S_INIT, IEEE80211_S_INIT, NULL }, }; static int lkpi_iv_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic; struct lkpi_hw *lhw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct fsm_state *s; enum ieee80211_state ostate; int error; ic = vap->iv_ic; IEEE80211_LOCK_ASSERT(ic); ostate = vap->iv_state; #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE) ic_printf(vap->iv_ic, "%s:%d: vap %p nstate %#x arg %#x\n", __func__, __LINE__, vap, nstate, arg); #endif if (vap->iv_opmode == IEEE80211_M_STA) { lhw = ic->ic_softc; lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); /* No need to replicate this in most state handlers. */ if (ostate == IEEE80211_S_SCAN && nstate != IEEE80211_S_SCAN) lkpi_stop_hw_scan(lhw, vif); s = sta_state_fsm; } else { ic_printf(vap->iv_ic, "%s: only station mode currently supported: " "cap %p iv_opmode %d\n", __func__, vap, vap->iv_opmode); return (ENOSYS); } error = 0; for (; s->handler != NULL; s++) { if (ostate == s->ostate && nstate == s->nstate) { #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE) ic_printf(vap->iv_ic, "%s: new state %d (%s) ->" " %d (%s): arg %d.\n", __func__, ostate, ieee80211_state_name[ostate], nstate, ieee80211_state_name[nstate], arg); #endif error = s->handler(vap, nstate, arg); break; } } IEEE80211_LOCK_ASSERT(vap->iv_ic); if (s->handler == NULL) { IMPROVE("turn this into a KASSERT\n"); ic_printf(vap->iv_ic, "%s: unsupported state transition " "%d (%s) -> %d (%s)\n", __func__, ostate, ieee80211_state_name[ostate], nstate, ieee80211_state_name[nstate]); return (ENOSYS); } if (error == EALREADY) { #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE) ic_printf(vap->iv_ic, "%s: state transition %d (%s) -> " "%d (%s): iv_newstate already handled: %d.\n", __func__, ostate, ieee80211_state_name[ostate], nstate, ieee80211_state_name[nstate], error); #endif return (0); } if (error != 0) { ic_printf(vap->iv_ic, "%s: error %d during state transition " "%d (%s) -> %d (%s)\n", __func__, error, ostate, ieee80211_state_name[ostate], nstate, ieee80211_state_name[nstate]); return (error); } #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE) ic_printf(vap->iv_ic, "%s:%d: vap %p nstate %#x arg %#x " "calling net80211 parent\n", __func__, __LINE__, vap, nstate, arg); #endif return (lvif->iv_newstate(vap, nstate, arg)); } /* -------------------------------------------------------------------------- */ /* * We overload (*iv_update_bss) as otherwise we have cases in, e.g., * net80211::ieee80211_sta_join1() where vap->iv_bss gets replaced by a * new node without us knowing and thus our ni/lsta are out of sync. */ static struct ieee80211_node * lkpi_iv_update_bss(struct ieee80211vap *vap, struct ieee80211_node *ni) { struct lkpi_vif *lvif; struct ieee80211_node *obss; struct lkpi_sta *lsta; struct ieee80211_sta *sta; obss = vap->iv_bss; #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE) ic_printf(vap->iv_ic, "%s: obss %p ni_drv_data %p " "ni %p ni_drv_data %p\n", __func__, obss, (obss != NULL) ? obss->ni_drv_data : NULL, ni, (ni != NULL) ? ni->ni_drv_data : NULL); #endif /* Nothing to copy from. Just return. */ if (obss == NULL || obss->ni_drv_data == NULL) goto out; /* Nothing to copy to. Just return. */ IMPROVE("clearing the obss might still be needed?"); if (ni == NULL) goto out; /* Nothing changed? panic? */ if (obss == ni) goto out; lsta = obss->ni_drv_data; obss->ni_drv_data = ni->ni_drv_data; ni->ni_drv_data = lsta; if (lsta != NULL) { lsta->ni = ni; sta = LSTA_TO_STA(lsta); IEEE80211_ADDR_COPY(sta->addr, lsta->ni->ni_macaddr); IEEE80211_ADDR_COPY(sta->deflink.addr, sta->addr); } lsta = obss->ni_drv_data; if (lsta != NULL) { lsta->ni = obss; sta = LSTA_TO_STA(lsta); IEEE80211_ADDR_COPY(sta->addr, lsta->ni->ni_macaddr); IEEE80211_ADDR_COPY(sta->deflink.addr, sta->addr); } out: lvif = VAP_TO_LVIF(vap); return (lvif->iv_update_bss(vap, ni)); } #ifdef LKPI_80211_WME static int lkpi_wme_update(struct lkpi_hw *lhw, struct ieee80211vap *vap, bool planned) { struct ieee80211com *ic; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct chanAccParams chp; struct wmeParams wmeparr[WME_NUM_AC]; struct ieee80211_tx_queue_params txqp; enum ieee80211_bss_changed changed; int error; uint16_t ac; IMPROVE(); KASSERT(WME_NUM_AC == IEEE80211_NUM_ACS, ("%s: WME_NUM_AC %d != " "IEEE80211_NUM_ACS %d\n", __func__, WME_NUM_AC, IEEE80211_NUM_ACS)); if (vap == NULL) return (0); if ((vap->iv_flags & IEEE80211_F_WME) == 0) return (0); if (lhw->ops->conf_tx == NULL) return (0); if (!planned && (vap->iv_state != IEEE80211_S_RUN)) { lhw->update_wme = true; return (0); } lhw->update_wme = false; ic = lhw->ic; ieee80211_wme_ic_getparams(ic, &chp); IEEE80211_LOCK(ic); for (ac = 0; ac < WME_NUM_AC; ac++) wmeparr[ac] = chp.cap_wmeParams[ac]; IEEE80211_UNLOCK(ic); hw = LHW_TO_HW(lhw); lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); /* Configure tx queues (conf_tx) & send BSS_CHANGED_QOS. */ LKPI_80211_LHW_LOCK(lhw); for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { struct wmeParams *wmep; wmep = &wmeparr[ac]; bzero(&txqp, sizeof(txqp)); txqp.cw_min = wmep->wmep_logcwmin; txqp.cw_max = wmep->wmep_logcwmax; txqp.txop = wmep->wmep_txopLimit; txqp.aifs = wmep->wmep_aifsn; error = lkpi_80211_mo_conf_tx(hw, vif, /* link_id */0, ac, &txqp); if (error != 0) ic_printf(ic, "%s: conf_tx ac %u failed %d\n", __func__, ac, error); } LKPI_80211_LHW_UNLOCK(lhw); changed = BSS_CHANGED_QOS; if (!planned) lkpi_80211_mo_bss_info_changed(hw, vif, &vif->bss_conf, changed); return (changed); } #endif static int lkpi_ic_wme_update(struct ieee80211com *ic) { #ifdef LKPI_80211_WME struct ieee80211vap *vap; struct lkpi_hw *lhw; IMPROVE("Use the per-VAP callback in net80211."); vap = TAILQ_FIRST(&ic->ic_vaps); if (vap == NULL) return (0); lhw = ic->ic_softc; lkpi_wme_update(lhw, vap, false); #endif return (0); /* unused */ } static struct ieee80211vap * lkpi_ic_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211vap *vap; struct ieee80211_vif *vif; struct ieee80211_tx_queue_params txqp; enum ieee80211_bss_changed changed; size_t len; int error, i; uint16_t ac; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* 1 so far. Add once this works. */ return (NULL); lhw = ic->ic_softc; hw = LHW_TO_HW(lhw); len = sizeof(*lvif); len += hw->vif_data_size; /* vif->drv_priv */ lvif = malloc(len, M_80211_VAP, M_WAITOK | M_ZERO); mtx_init(&lvif->mtx, "lvif", NULL, MTX_DEF); TAILQ_INIT(&lvif->lsta_head); vap = LVIF_TO_VAP(lvif); vif = LVIF_TO_VIF(lvif); memcpy(vif->addr, mac, IEEE80211_ADDR_LEN); vif->p2p = false; vif->probe_req_reg = false; vif->type = lkpi_opmode_to_vif_type(opmode); lvif->wdev.iftype = vif->type; /* Need to fill in other fields as well. */ IMPROVE(); /* XXX-BZ hardcoded for now! */ #if 1 vif->chanctx_conf = NULL; vif->bss_conf.vif = vif; /* vap->iv_myaddr is not set until net80211::vap_setup or vap_attach. */ IEEE80211_ADDR_COPY(vif->bss_conf.addr, mac); vif->bss_conf.link_id = 0; /* Non-MLO operation. */ vif->bss_conf.chandef.width = NL80211_CHAN_WIDTH_20_NOHT; vif->bss_conf.use_short_preamble = false; /* vap->iv_flags IEEE80211_F_SHPREAMBLE */ vif->bss_conf.use_short_slot = false; /* vap->iv_flags IEEE80211_F_SHSLOT */ vif->bss_conf.qos = false; vif->bss_conf.use_cts_prot = false; /* vap->iv_protmode */ vif->bss_conf.ht_operation_mode = IEEE80211_HT_OP_MODE_PROTECTION_NONE; vif->cfg.aid = 0; vif->cfg.assoc = false; vif->cfg.idle = true; vif->cfg.ps = false; IMPROVE("Check other fields and then figure out whats is left elsewhere of them"); /* * We need to initialize it to something as the bss_info_changed call * will try to copy from it in iwlwifi and NULL is a panic. * We will set the proper one in scan_to_auth() before being assoc. */ vif->bss_conf.bssid = ieee80211broadcastaddr; #endif #if 0 vif->bss_conf.dtim_period = 0; /* IEEE80211_DTIM_DEFAULT ; must stay 0. */ IEEE80211_ADDR_COPY(vif->bss_conf.bssid, bssid); vif->bss_conf.beacon_int = ic->ic_bintval; /* iwlwifi bug. */ if (vif->bss_conf.beacon_int < 16) vif->bss_conf.beacon_int = 16; #endif /* Link Config */ vif->link_conf[0] = &vif->bss_conf; for (i = 0; i < nitems(vif->link_conf); i++) { IMPROVE("more than 1 link one day"); } /* Setup queue defaults; driver may override in (*add_interface). */ for (i = 0; i < IEEE80211_NUM_ACS; i++) { if (ieee80211_hw_check(hw, QUEUE_CONTROL)) vif->hw_queue[i] = IEEE80211_INVAL_HW_QUEUE; else if (hw->queues >= IEEE80211_NUM_ACS) vif->hw_queue[i] = i; else vif->hw_queue[i] = 0; /* Initialize the queue to running. Stopped? */ lvif->hw_queue_stopped[i] = false; } vif->cab_queue = IEEE80211_INVAL_HW_QUEUE; IMPROVE(); error = lkpi_80211_mo_start(hw); if (error != 0) { ic_printf(ic, "%s: failed to start hw: %d\n", __func__, error); mtx_destroy(&lvif->mtx); free(lvif, M_80211_VAP); return (NULL); } error = lkpi_80211_mo_add_interface(hw, vif); if (error != 0) { IMPROVE(); /* XXX-BZ mo_stop()? */ ic_printf(ic, "%s: failed to add interface: %d\n", __func__, error); mtx_destroy(&lvif->mtx); free(lvif, M_80211_VAP); return (NULL); } LKPI_80211_LHW_LVIF_LOCK(lhw); TAILQ_INSERT_TAIL(&lhw->lvif_head, lvif, lvif_entry); LKPI_80211_LHW_LVIF_UNLOCK(lhw); /* Set bss_info. */ changed = 0; lkpi_80211_mo_bss_info_changed(hw, vif, &vif->bss_conf, changed); /* Configure tx queues (conf_tx), default WME & send BSS_CHANGED_QOS. */ IMPROVE("Hardcoded values; to fix see 802.11-2016, 9.4.2.29 EDCA Parameter Set element"); LKPI_80211_LHW_LOCK(lhw); for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { bzero(&txqp, sizeof(txqp)); txqp.cw_min = 15; txqp.cw_max = 1023; txqp.txop = 0; txqp.aifs = 2; error = lkpi_80211_mo_conf_tx(hw, vif, /* link_id */0, ac, &txqp); if (error != 0) ic_printf(ic, "%s: conf_tx ac %u failed %d\n", __func__, ac, error); } LKPI_80211_LHW_UNLOCK(lhw); changed = BSS_CHANGED_QOS; lkpi_80211_mo_bss_info_changed(hw, vif, &vif->bss_conf, changed); /* Force MC init. */ lkpi_update_mcast_filter(ic, true); IMPROVE(); ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid); /* Override with LinuxKPI method so we can drive mac80211/cfg80211. */ lvif->iv_newstate = vap->iv_newstate; vap->iv_newstate = lkpi_iv_newstate; lvif->iv_update_bss = vap->iv_update_bss; vap->iv_update_bss = lkpi_iv_update_bss; /* Key management. */ if (lhw->ops->set_key != NULL) { #ifdef LKPI_80211_HW_CRYPTO vap->iv_key_set = lkpi_iv_key_set; vap->iv_key_delete = lkpi_iv_key_delete; #endif } ieee80211_ratectl_init(vap); /* Complete setup. */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status, mac); if (hw->max_listen_interval == 0) hw->max_listen_interval = 7 * (ic->ic_lintval / ic->ic_bintval); hw->conf.listen_interval = hw->max_listen_interval; ic->ic_set_channel(ic); /* XXX-BZ do we need to be able to update these? */ hw->wiphy->frag_threshold = vap->iv_fragthreshold; lkpi_80211_mo_set_frag_threshold(hw, vap->iv_fragthreshold); hw->wiphy->rts_threshold = vap->iv_rtsthreshold; lkpi_80211_mo_set_rts_threshold(hw, vap->iv_rtsthreshold); /* any others? */ IMPROVE(); return (vap); } void linuxkpi_ieee80211_unregister_hw(struct ieee80211_hw *hw) { wiphy_unregister(hw->wiphy); linuxkpi_ieee80211_ifdetach(hw); IMPROVE(); } void linuxkpi_ieee80211_restart_hw(struct ieee80211_hw *hw) { TODO(); } static void lkpi_ic_vap_delete(struct ieee80211vap *vap) { struct ieee80211com *ic; struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); ic = vap->iv_ic; lhw = ic->ic_softc; hw = LHW_TO_HW(lhw); LKPI_80211_LHW_LVIF_LOCK(lhw); TAILQ_REMOVE(&lhw->lvif_head, lvif, lvif_entry); LKPI_80211_LHW_LVIF_UNLOCK(lhw); ieee80211_ratectl_deinit(vap); ieee80211_vap_detach(vap); IMPROVE("clear up other bits in this state"); lkpi_80211_mo_remove_interface(hw, vif); /* Single VAP, so we can do this here. */ lkpi_80211_mo_stop(hw); mtx_destroy(&lvif->mtx); free(lvif, M_80211_VAP); } static void lkpi_ic_update_mcast(struct ieee80211com *ic) { lkpi_update_mcast_filter(ic, false); TRACEOK(); } static void lkpi_ic_update_promisc(struct ieee80211com *ic) { UNIMPLEMENTED; } static void lkpi_ic_update_chw(struct ieee80211com *ic) { UNIMPLEMENTED; } /* Start / stop device. */ static void lkpi_ic_parent(struct ieee80211com *ic) { struct lkpi_hw *lhw; #ifdef HW_START_STOP struct ieee80211_hw *hw; int error; #endif bool start_all; IMPROVE(); lhw = ic->ic_softc; #ifdef HW_START_STOP hw = LHW_TO_HW(lhw); #endif start_all = false; /* IEEE80211_UNLOCK(ic); */ LKPI_80211_LHW_LOCK(lhw); if (ic->ic_nrunning > 0) { #ifdef HW_START_STOP error = lkpi_80211_mo_start(hw); if (error == 0) #endif start_all = true; } else { #ifdef HW_START_STOP lkpi_80211_mo_stop(hw); #endif } LKPI_80211_LHW_UNLOCK(lhw); /* IEEE80211_LOCK(ic); */ if (start_all) ieee80211_start_all(ic); } bool linuxkpi_ieee80211_is_ie_id_in_ie_buf(const u8 ie, const u8 *ie_ids, size_t ie_ids_len) { int i; for (i = 0; i < ie_ids_len; i++) { if (ie == *ie_ids) return (true); } return (false); } /* Return true if skipped; false if error. */ bool linuxkpi_ieee80211_ie_advance(size_t *xp, const u8 *ies, size_t ies_len) { size_t x; uint8_t l; x = *xp; KASSERT(x < ies_len, ("%s: x %zu ies_len %zu ies %p\n", __func__, x, ies_len, ies)); l = ies[x + 1]; x += 2 + l; if (x > ies_len) return (false); *xp = x; return (true); } static uint8_t * lkpi_scan_ies_add(uint8_t *p, struct ieee80211_scan_ies *scan_ies, uint32_t band_mask, struct ieee80211vap *vap, struct ieee80211_hw *hw) { struct ieee80211_supported_band *supband; struct linuxkpi_ieee80211_channel *channels; struct ieee80211com *ic; const struct ieee80211_channel *chan; const struct ieee80211_rateset *rs; uint8_t *pb; int band, i; ic = vap->iv_ic; for (band = 0; band < NUM_NL80211_BANDS; band++) { if ((band_mask & (1 << band)) == 0) continue; supband = hw->wiphy->bands[band]; /* * This should not happen; * band_mask is a bitmask of valid bands to scan on. */ if (supband == NULL || supband->n_channels == 0) continue; /* Find a first channel to get the mode and rates from. */ channels = supband->channels; chan = NULL; for (i = 0; i < supband->n_channels; i++) { if (channels[i].flags & IEEE80211_CHAN_DISABLED) continue; chan = ieee80211_find_channel(ic, channels[i].center_freq, 0); if (chan != NULL) break; } /* This really should not happen. */ if (chan == NULL) continue; pb = p; rs = ieee80211_get_suprates(ic, chan); /* calls chan2mode */ p = ieee80211_add_rates(p, rs); p = ieee80211_add_xrates(p, rs); #if defined(LKPI_80211_HT) if ((vap->iv_flags_ht & IEEE80211_FHT_HT) != 0) { struct ieee80211_channel *c; c = ieee80211_ht_adjust_channel(ic, ic->ic_curchan, vap->iv_flags_ht); p = ieee80211_add_htcap_ch(p, vap, c); } #endif #if defined(LKPI_80211_VHT) if ((vap->iv_vht_flags & IEEE80211_FVHT_VHT) != 0) { 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); p = ieee80211_add_vhtcap_ch(p, vap, c); } #endif scan_ies->ies[band] = pb; scan_ies->len[band] = p - pb; } /* Add common_ies */ pb = p; if ((vap->iv_flags & IEEE80211_F_WPA1) != 0 && vap->iv_wpa_ie != NULL) { memcpy(p, vap->iv_wpa_ie, 2 + vap->iv_wpa_ie[1]); p += 2 + vap->iv_wpa_ie[1]; } if (vap->iv_appie_probereq != NULL) { memcpy(p, vap->iv_appie_probereq->ie_data, vap->iv_appie_probereq->ie_len); p += vap->iv_appie_probereq->ie_len; } scan_ies->common_ies = pb; scan_ies->common_ie_len = p - pb; return (p); } static void lkpi_ic_scan_start(struct ieee80211com *ic) { struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct ieee80211_scan_state *ss; struct ieee80211vap *vap; int error; bool is_hw_scan; lhw = ic->ic_softc; LKPI_80211_LHW_SCAN_LOCK(lhw); if ((lhw->scan_flags & LKPI_LHW_SCAN_RUNNING) != 0) { /* A scan is still running. */ LKPI_80211_LHW_SCAN_UNLOCK(lhw); return; } is_hw_scan = (lhw->scan_flags & LKPI_LHW_SCAN_HW) != 0; LKPI_80211_LHW_SCAN_UNLOCK(lhw); ss = ic->ic_scan; vap = ss->ss_vap; if (vap->iv_state != IEEE80211_S_SCAN) { IMPROVE("We need to be able to scan if not in S_SCAN"); return; } hw = LHW_TO_HW(lhw); if (!is_hw_scan) { /* If hw_scan is cleared clear FEXT_SCAN_OFFLOAD too. */ vap->iv_flags_ext &= ~IEEE80211_FEXT_SCAN_OFFLOAD; sw_scan: lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); if (vap->iv_state == IEEE80211_S_SCAN) lkpi_hw_conf_idle(hw, false); lkpi_80211_mo_sw_scan_start(hw, vif, vif->addr); /* net80211::scan_start() handled PS for us. */ IMPROVE(); /* XXX Also means it is too late to flush queues? * need to check iv_sta_ps or overload? */ /* XXX want to adjust ss end time/ maxdwell? */ } else { struct ieee80211_channel *c; struct ieee80211_scan_request *hw_req; struct linuxkpi_ieee80211_channel *lc, **cpp; struct cfg80211_ssid *ssids; struct cfg80211_scan_6ghz_params *s6gp; size_t chan_len, nchan, ssids_len, s6ghzlen; int band, i, ssid_count, common_ie_len; uint32_t band_mask; uint8_t *ie, *ieend; bool running; ssid_count = min(ss->ss_nssid, hw->wiphy->max_scan_ssids); ssids_len = ssid_count * sizeof(*ssids); s6ghzlen = 0 * (sizeof(*s6gp)); /* XXX-BZ */ band_mask = 0; nchan = 0; for (i = ss->ss_next; i < ss->ss_last; i++) { nchan++; band = lkpi_net80211_chan_to_nl80211_band( ss->ss_chans[ss->ss_next + i]); band_mask |= (1 << band); } if (!ieee80211_hw_check(hw, SINGLE_SCAN_ON_ALL_BANDS)) { IMPROVE("individual band scans not yet supported, only scanning first band"); /* In theory net80211 should drive this. */ /* Probably we need to add local logic for now; * need to deal with scan_complete * and cancel_scan and keep local state. * Also cut the nchan down above. */ /* XXX-BZ ath10k does not set this but still does it? &$%^ */ } chan_len = nchan * (sizeof(lc) + sizeof(*lc)); common_ie_len = 0; if ((vap->iv_flags & IEEE80211_F_WPA1) != 0 && vap->iv_wpa_ie != NULL) common_ie_len += vap->iv_wpa_ie[1]; if (vap->iv_appie_probereq != NULL) common_ie_len += vap->iv_appie_probereq->ie_len; /* We would love to check this at an earlier stage... */ if (common_ie_len > hw->wiphy->max_scan_ie_len) { ic_printf(ic, "WARNING: %s: common_ie_len %d > " "wiphy->max_scan_ie_len %d\n", __func__, common_ie_len, hw->wiphy->max_scan_ie_len); } hw_req = malloc(sizeof(*hw_req) + ssids_len + s6ghzlen + chan_len + lhw->supbands * lhw->scan_ie_len + common_ie_len, M_LKPI80211, M_WAITOK | M_ZERO); hw_req->req.flags = 0; /* XXX ??? */ /* hw_req->req.wdev */ hw_req->req.wiphy = hw->wiphy; hw_req->req.no_cck = false; /* XXX */ #if 0 /* This seems to pessimise default scanning behaviour. */ hw_req->req.duration_mandatory = TICKS_2_USEC(ss->ss_mindwell); hw_req->req.duration = TICKS_2_USEC(ss->ss_maxdwell); #endif #ifdef __notyet__ hw_req->req.flags |= NL80211_SCAN_FLAG_RANDOM_ADDR; memcpy(hw_req->req.mac_addr, xxx, IEEE80211_ADDR_LEN); memset(hw_req->req.mac_addr_mask, 0xxx, IEEE80211_ADDR_LEN); #endif eth_broadcast_addr(hw_req->req.bssid); hw_req->req.n_channels = nchan; cpp = (struct linuxkpi_ieee80211_channel **)(hw_req + 1); lc = (struct linuxkpi_ieee80211_channel *)(cpp + nchan); for (i = 0; i < nchan; i++) { *(cpp + i) = (struct linuxkpi_ieee80211_channel *)(lc + i); } for (i = 0; i < nchan; i++) { c = ss->ss_chans[ss->ss_next + i]; lc->hw_value = c->ic_ieee; lc->center_freq = c->ic_freq; /* XXX */ /* lc->flags */ lc->band = lkpi_net80211_chan_to_nl80211_band(c); lc->max_power = c->ic_maxpower; /* lc-> ... */ lc++; } hw_req->req.n_ssids = ssid_count; if (hw_req->req.n_ssids > 0) { ssids = (struct cfg80211_ssid *)lc; hw_req->req.ssids = ssids; for (i = 0; i < ssid_count; i++) { ssids->ssid_len = ss->ss_ssid[i].len; memcpy(ssids->ssid, ss->ss_ssid[i].ssid, ss->ss_ssid[i].len); ssids++; } s6gp = (struct cfg80211_scan_6ghz_params *)ssids; } else { s6gp = (struct cfg80211_scan_6ghz_params *)lc; } /* 6GHz one day. */ hw_req->req.n_6ghz_params = 0; hw_req->req.scan_6ghz_params = NULL; hw_req->req.scan_6ghz = false; /* Weird boolean; not what you think. */ /* s6gp->... */ ie = ieend = (uint8_t *)s6gp; /* Copy per-band IEs, copy common IEs */ ieend = lkpi_scan_ies_add(ie, &hw_req->ies, band_mask, vap, hw); hw_req->req.ie = ie; hw_req->req.ie_len = ieend - ie; lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); LKPI_80211_LHW_SCAN_LOCK(lhw); /* Re-check under lock. */ running = (lhw->scan_flags & LKPI_LHW_SCAN_RUNNING) != 0; if (!running) { KASSERT(lhw->hw_req == NULL, ("%s: ic %p lhw %p hw_req %p " "!= NULL\n", __func__, ic, lhw, lhw->hw_req)); lhw->scan_flags |= LKPI_LHW_SCAN_RUNNING; lhw->hw_req = hw_req; } LKPI_80211_LHW_SCAN_UNLOCK(lhw); if (running) { free(hw_req, M_LKPI80211); return; } error = lkpi_80211_mo_hw_scan(hw, vif, hw_req); if (error != 0) { ieee80211_cancel_scan(vap); /* * ieee80211_scan_completed must be called in either * case of error or none. So let the free happen there * and only there. * That would be fine in theory but in practice drivers * behave differently: * ath10k does not return hw_scan until after scan_complete * and can then still return an error. * rtw88 can return 1 or -EBUSY without scan_complete * iwlwifi can return various errors before scan starts * ... * So we cannot rely on that behaviour and have to check * and balance between both code paths. */ LKPI_80211_LHW_SCAN_LOCK(lhw); if ((lhw->scan_flags & LKPI_LHW_SCAN_RUNNING) != 0) { free(lhw->hw_req, M_LKPI80211); lhw->hw_req = NULL; lhw->scan_flags &= ~LKPI_LHW_SCAN_RUNNING; } LKPI_80211_LHW_SCAN_UNLOCK(lhw); /* * XXX-SIGH magic number. * rtw88 has a magic "return 1" if offloading scan is * not possible. Fall back to sw scan in that case. */ if (error == 1) { LKPI_80211_LHW_SCAN_LOCK(lhw); lhw->scan_flags &= ~LKPI_LHW_SCAN_HW; LKPI_80211_LHW_SCAN_UNLOCK(lhw); /* * XXX If we clear this now and later a driver * thinks it * can do a hw_scan again, we will * currently not re-enable it? */ vap->iv_flags_ext &= ~IEEE80211_FEXT_SCAN_OFFLOAD; ieee80211_start_scan(vap, IEEE80211_SCAN_ACTIVE | IEEE80211_SCAN_NOPICK | IEEE80211_SCAN_ONCE, IEEE80211_SCAN_FOREVER, ss->ss_mindwell ? ss->ss_mindwell : msecs_to_ticks(20), ss->ss_maxdwell ? ss->ss_maxdwell : msecs_to_ticks(200), vap->iv_des_nssid, vap->iv_des_ssid); goto sw_scan; } ic_printf(ic, "ERROR: %s: hw_scan returned %d\n", __func__, error); } } } static void lkpi_ic_scan_end(struct ieee80211com *ic) { struct lkpi_hw *lhw; bool is_hw_scan; lhw = ic->ic_softc; LKPI_80211_LHW_SCAN_LOCK(lhw); if ((lhw->scan_flags & LKPI_LHW_SCAN_RUNNING) == 0) { LKPI_80211_LHW_SCAN_UNLOCK(lhw); return; } is_hw_scan = (lhw->scan_flags & LKPI_LHW_SCAN_HW) != 0; LKPI_80211_LHW_SCAN_UNLOCK(lhw); if (!is_hw_scan) { struct ieee80211_scan_state *ss; struct ieee80211vap *vap; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; ss = ic->ic_scan; vap = ss->ss_vap; hw = LHW_TO_HW(lhw); lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); lkpi_80211_mo_sw_scan_complete(hw, vif); /* Send PS to stop buffering if n80211 does not for us? */ if (vap->iv_state == IEEE80211_S_SCAN) lkpi_hw_conf_idle(hw, true); } } static void lkpi_ic_scan_curchan(struct ieee80211_scan_state *ss, unsigned long maxdwell) { struct lkpi_hw *lhw; bool is_hw_scan; lhw = ss->ss_ic->ic_softc; LKPI_80211_LHW_SCAN_LOCK(lhw); is_hw_scan = (lhw->scan_flags & LKPI_LHW_SCAN_HW) != 0; LKPI_80211_LHW_SCAN_UNLOCK(lhw); if (!is_hw_scan) lhw->ic_scan_curchan(ss, maxdwell); } static void lkpi_ic_scan_mindwell(struct ieee80211_scan_state *ss) { struct lkpi_hw *lhw; bool is_hw_scan; lhw = ss->ss_ic->ic_softc; LKPI_80211_LHW_SCAN_LOCK(lhw); is_hw_scan = (lhw->scan_flags & LKPI_LHW_SCAN_HW) != 0; LKPI_80211_LHW_SCAN_UNLOCK(lhw); if (!is_hw_scan) lhw->ic_scan_mindwell(ss); } static void lkpi_ic_set_channel(struct ieee80211com *ic) { struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct ieee80211_channel *c; struct linuxkpi_ieee80211_channel *chan; int error; bool hw_scan_running; lhw = ic->ic_softc; /* If we do not support (*config)() save us the work. */ if (lhw->ops->config == NULL) return; /* If we have a hw_scan running do not switch channels. */ LKPI_80211_LHW_SCAN_LOCK(lhw); hw_scan_running = (lhw->scan_flags & (LKPI_LHW_SCAN_RUNNING|LKPI_LHW_SCAN_HW)) == (LKPI_LHW_SCAN_RUNNING|LKPI_LHW_SCAN_HW); LKPI_80211_LHW_SCAN_UNLOCK(lhw); if (hw_scan_running) return; c = ic->ic_curchan; if (c == NULL || c == IEEE80211_CHAN_ANYC) { ic_printf(ic, "%s: c %p ops->config %p\n", __func__, c, lhw->ops->config); return; } chan = lkpi_find_lkpi80211_chan(lhw, c); if (chan == NULL) { ic_printf(ic, "%s: c %p chan %p\n", __func__, c, chan); return; } /* XXX max power for scanning? */ IMPROVE(); hw = LHW_TO_HW(lhw); cfg80211_chandef_create(&hw->conf.chandef, chan, NL80211_CHAN_NO_HT); error = lkpi_80211_mo_config(hw, IEEE80211_CONF_CHANGE_CHANNEL); if (error != 0 && error != EOPNOTSUPP) { ic_printf(ic, "ERROR: %s: config %#0x returned %d\n", __func__, IEEE80211_CONF_CHANGE_CHANNEL, error); /* XXX should we unroll to the previous chandef? */ IMPROVE(); } else { /* Update radiotap channels as well. */ lhw->rtap_tx.wt_chan_freq = htole16(c->ic_freq); lhw->rtap_tx.wt_chan_flags = htole16(c->ic_flags); lhw->rtap_rx.wr_chan_freq = htole16(c->ic_freq); lhw->rtap_rx.wr_chan_flags = htole16(c->ic_flags); } /* Currently PS is hard coded off! Not sure it belongs here. */ IMPROVE(); if (ieee80211_hw_check(hw, SUPPORTS_PS) && (hw->conf.flags & IEEE80211_CONF_PS) != 0) { hw->conf.flags &= ~IEEE80211_CONF_PS; error = lkpi_80211_mo_config(hw, IEEE80211_CONF_CHANGE_PS); if (error != 0 && error != EOPNOTSUPP) ic_printf(ic, "ERROR: %s: config %#0x returned " "%d\n", __func__, IEEE80211_CONF_CHANGE_PS, error); } } static struct ieee80211_node * lkpi_ic_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) { struct ieee80211com *ic; struct lkpi_hw *lhw; struct ieee80211_node *ni; struct ieee80211_hw *hw; struct lkpi_sta *lsta; ic = vap->iv_ic; lhw = ic->ic_softc; /* We keep allocations de-coupled so we can deal with the two worlds. */ if (lhw->ic_node_alloc == NULL) return (NULL); ni = lhw->ic_node_alloc(vap, mac); if (ni == NULL) return (NULL); hw = LHW_TO_HW(lhw); lsta = lkpi_lsta_alloc(vap, mac, hw, ni); if (lsta == NULL) { if (lhw->ic_node_free != NULL) lhw->ic_node_free(ni); return (NULL); } return (ni); } static int lkpi_ic_node_init(struct ieee80211_node *ni) { struct ieee80211com *ic; struct lkpi_hw *lhw; struct lkpi_sta *lsta; int error; ic = ni->ni_ic; lhw = ic->ic_softc; if (lhw->ic_node_init != NULL) { error = lhw->ic_node_init(ni); if (error != 0) return (error); } lsta = ni->ni_drv_data; /* Now take the reference before linking it to the table. */ lsta->ni = ieee80211_ref_node(ni); /* XXX-BZ Sync other state over. */ IMPROVE(); return (0); } static void lkpi_ic_node_cleanup(struct ieee80211_node *ni) { struct ieee80211com *ic; struct lkpi_hw *lhw; ic = ni->ni_ic; lhw = ic->ic_softc; /* XXX-BZ remove from driver, ... */ IMPROVE(); if (lhw->ic_node_cleanup != NULL) lhw->ic_node_cleanup(ni); } static void lkpi_ic_node_free(struct ieee80211_node *ni) { struct ieee80211com *ic; struct lkpi_hw *lhw; struct lkpi_sta *lsta; ic = ni->ni_ic; lhw = ic->ic_softc; lsta = ni->ni_drv_data; if (lsta == NULL) goto out; /* XXX-BZ free resources, ... */ IMPROVE(); /* Flush mbufq (make sure to release ni refs!). */ #ifdef __notyet__ KASSERT(mbufq_empty(&lsta->txq), ("%s: lsta %p has txq len %d != 0\n", __func__, lsta, mbufq_len(&lsta->txq))); #endif /* Drain taskq. */ /* Drain sta->txq[] */ mtx_destroy(&lsta->txq_mtx); /* Remove lsta if added_to_drv. */ /* Remove lsta from vif */ /* Remove ref from lsta node... */ /* Free lsta. */ lkpi_lsta_remove(lsta, VAP_TO_LVIF(ni->ni_vap)); out: if (lhw->ic_node_free != NULL) lhw->ic_node_free(ni); } static int lkpi_ic_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params __unused) { struct lkpi_sta *lsta; lsta = ni->ni_drv_data; /* Queue the packet and enqueue the task to handle it. */ LKPI_80211_LSTA_LOCK(lsta); mbufq_enqueue(&lsta->txq, m); LKPI_80211_LSTA_UNLOCK(lsta); #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE_TX) printf("%s:%d lsta %p ni %p %6D mbuf_qlen %d\n", __func__, __LINE__, lsta, ni, ni->ni_macaddr, ":", mbufq_len(&lsta->txq)); #endif taskqueue_enqueue(taskqueue_thread, &lsta->txq_task); return (0); } static void lkpi_80211_txq_tx_one(struct lkpi_sta *lsta, struct mbuf *m) { struct ieee80211_node *ni; #ifndef LKPI_80211_HW_CRYPTO struct ieee80211_frame *wh; #endif struct ieee80211_key *k; struct sk_buff *skb; struct ieee80211com *ic; struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct ieee80211_channel *c; struct ieee80211_tx_control control; struct ieee80211_tx_info *info; struct ieee80211_sta *sta; struct ieee80211_hdr *hdr; void *buf; uint8_t ac, tid; M_ASSERTPKTHDR(m); #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE_TX_DUMP) hexdump(mtod(m, const void *), m->m_len, "RAW TX (plain) ", 0); #endif ni = lsta->ni; k = NULL; #ifndef LKPI_80211_HW_CRYPTO /* Encrypt the frame if need be; XXX-BZ info->control.hw_key. */ wh = mtod(m, struct ieee80211_frame *); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { /* Retrieve key for TX && do software encryption. */ k = ieee80211_crypto_encap(ni, m); if (k == NULL) { ieee80211_free_node(ni); m_freem(m); return; } } #endif ic = ni->ni_ic; lhw = ic->ic_softc; hw = LHW_TO_HW(lhw); c = ni->ni_chan; if (ieee80211_radiotap_active_vap(ni->ni_vap)) { struct lkpi_radiotap_tx_hdr *rtap; rtap = &lhw->rtap_tx; rtap->wt_flags = 0; if (k != NULL) rtap->wt_flags |= IEEE80211_RADIOTAP_F_WEP; if (m->m_flags & M_FRAG) rtap->wt_flags |= IEEE80211_RADIOTAP_F_FRAG; IMPROVE(); rtap->wt_rate = 0; if (c != NULL && c != IEEE80211_CHAN_ANYC) { rtap->wt_chan_freq = htole16(c->ic_freq); rtap->wt_chan_flags = htole16(c->ic_flags); } ieee80211_radiotap_tx(ni->ni_vap, m); } /* * net80211 should handle hw->extra_tx_headroom. * Though for as long as we are copying we don't mind. * XXX-BZ rtw88 asks for too much headroom for ipv6+tcp: * https://lists.freebsd.org/archives/freebsd-transport/2022-February/000012.html */ skb = dev_alloc_skb(hw->extra_tx_headroom + m->m_pkthdr.len); if (skb == NULL) { ic_printf(ic, "ERROR %s: skb alloc failed\n", __func__); ieee80211_free_node(ni); m_freem(m); return; } skb_reserve(skb, hw->extra_tx_headroom); /* XXX-BZ we need a SKB version understanding mbuf. */ /* Save the mbuf for ieee80211_tx_complete(). */ skb->m_free_func = lkpi_ieee80211_free_skb_mbuf; skb->m = m; #if 0 skb_put_data(skb, m->m_data, m->m_pkthdr.len); #else buf = skb_put(skb, m->m_pkthdr.len); m_copydata(m, 0, m->m_pkthdr.len, buf); #endif /* Save the ni. */ m->m_pkthdr.PH_loc.ptr = ni; lvif = VAP_TO_LVIF(ni->ni_vap); vif = LVIF_TO_VIF(lvif); hdr = (void *)skb->data; tid = linuxkpi_ieee80211_get_tid(hdr, true); if (tid == IEEE80211_NONQOS_TID) { /* == IEEE80211_NUM_TIDS */ skb->priority = 0; ac = IEEE80211_AC_BE; } else { skb->priority = tid & IEEE80211_QOS_CTL_TID_MASK; ac = ieee80211e_up_to_ac[tid & 7]; } skb_set_queue_mapping(skb, ac); info = IEEE80211_SKB_CB(skb); info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; /* Slight delay; probably only happens on scanning so fine? */ if (c == NULL || c == IEEE80211_CHAN_ANYC) c = ic->ic_curchan; info->band = lkpi_net80211_chan_to_nl80211_band(c); info->hw_queue = vif->hw_queue[ac]; if (m->m_flags & M_EAPOL) info->control.flags |= IEEE80211_TX_CTRL_PORT_CTRL_PROTO; info->control.vif = vif; /* XXX-BZ info->control.rates */ lsta = lkpi_find_lsta_by_ni(lvif, ni); if (lsta != NULL) { sta = LSTA_TO_STA(lsta); #ifdef LKPI_80211_HW_CRYPTO info->control.hw_key = lsta->kc; #endif } else { sta = NULL; } IMPROVE(); if (sta != NULL) { struct lkpi_txq *ltxq; ltxq = NULL; if (!ieee80211_is_data_present(hdr->frame_control)) { if (vif->type == NL80211_IFTYPE_STATION && lsta->added_to_drv && sta->txq[IEEE80211_NUM_TIDS] != NULL) ltxq = TXQ_TO_LTXQ(sta->txq[IEEE80211_NUM_TIDS]); } else if (lsta->added_to_drv && sta->txq[skb->priority] != NULL) { ltxq = TXQ_TO_LTXQ(sta->txq[skb->priority]); } if (ltxq == NULL) goto ops_tx; KASSERT(ltxq != NULL, ("%s: lsta %p sta %p m %p skb %p " "ltxq %p != NULL\n", __func__, lsta, sta, m, skb, ltxq)); skb_queue_tail(<xq->skbq, skb); #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE_TX) printf("%s:%d mo_wake_tx_queue :: %d %u lsta %p sta %p " "ni %p %6D skb %p lxtq %p { qlen %u, ac %d tid %u } " "WAKE_TX_Q ac %d prio %u qmap %u\n", __func__, __LINE__, curthread->td_tid, (unsigned int)ticks, lsta, sta, ni, ni->ni_macaddr, ":", skb, ltxq, skb_queue_len(<xq->skbq), ltxq->txq.ac, ltxq->txq.tid, ac, skb->priority, skb->qmap); #endif lkpi_80211_mo_wake_tx_queue(hw, <xq->txq); return; } ops_tx: #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE_TX) printf("%s:%d mo_tx :: lsta %p sta %p ni %p %6D skb %p " "TX ac %d prio %u qmap %u\n", __func__, __LINE__, lsta, sta, ni, ni->ni_macaddr, ":", skb, ac, skb->priority, skb->qmap); #endif memset(&control, 0, sizeof(control)); control.sta = sta; lkpi_80211_mo_tx(hw, &control, skb); return; } static void lkpi_80211_txq_task(void *ctx, int pending) { struct lkpi_sta *lsta; struct mbufq mq; struct mbuf *m; lsta = ctx; #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE_TX) printf("%s:%d lsta %p ni %p %6D pending %d mbuf_qlen %d\n", __func__, __LINE__, lsta, lsta->ni, lsta->ni->ni_macaddr, ":", pending, mbufq_len(&lsta->txq)); #endif mbufq_init(&mq, IFQ_MAXLEN); LKPI_80211_LSTA_LOCK(lsta); mbufq_concat(&mq, &lsta->txq); LKPI_80211_LSTA_UNLOCK(lsta); m = mbufq_dequeue(&mq); while (m != NULL) { lkpi_80211_txq_tx_one(lsta, m); m = mbufq_dequeue(&mq); } } static int lkpi_ic_transmit(struct ieee80211com *ic, struct mbuf *m) { /* XXX TODO */ IMPROVE(); /* Quick and dirty cheating hack. */ struct ieee80211_node *ni; ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; return (lkpi_ic_raw_xmit(ni, m, NULL)); } static void lkpi_ic_getradiocaps(struct ieee80211com *ic, int maxchan, int *n, struct ieee80211_channel *c) { struct lkpi_hw *lhw; struct ieee80211_hw *hw; struct linuxkpi_ieee80211_channel *channels; uint8_t bands[IEEE80211_MODE_BYTES]; int chan_flags, error, i, nchans; /* Channels */ lhw = ic->ic_softc; hw = LHW_TO_HW(lhw); /* NL80211_BAND_2GHZ */ nchans = 0; if (hw->wiphy->bands[NL80211_BAND_2GHZ] != NULL) nchans = hw->wiphy->bands[NL80211_BAND_2GHZ]->n_channels; if (nchans > 0) { memset(bands, 0, sizeof(bands)); chan_flags = 0; setbit(bands, IEEE80211_MODE_11B); /* XXX-BZ unclear how to check for 11g. */ setbit(bands, IEEE80211_MODE_11G); #ifdef __notyet__ if (hw->wiphy->bands[NL80211_BAND_2GHZ]->ht_cap.ht_supported) { setbit(bands, IEEE80211_MODE_11NG); chan_flags |= NET80211_CBW_FLAG_HT40; } #endif channels = hw->wiphy->bands[NL80211_BAND_2GHZ]->channels; for (i = 0; i < nchans && *n < maxchan; i++) { uint32_t nflags = 0; int cflags = chan_flags; if (channels[i].flags & IEEE80211_CHAN_DISABLED) { ic_printf(ic, "%s: Skipping disabled chan " "[%u/%u/%#x]\n", __func__, channels[i].hw_value, channels[i].center_freq, channels[i].flags); continue; } if (channels[i].flags & IEEE80211_CHAN_NO_IR) nflags |= (IEEE80211_CHAN_NOADHOC|IEEE80211_CHAN_PASSIVE); if (channels[i].flags & IEEE80211_CHAN_RADAR) nflags |= IEEE80211_CHAN_DFS; if (channels[i].flags & IEEE80211_CHAN_NO_160MHZ) cflags &= ~(NET80211_CBW_FLAG_VHT160|NET80211_CBW_FLAG_VHT80P80); if (channels[i].flags & IEEE80211_CHAN_NO_80MHZ) cflags &= ~NET80211_CBW_FLAG_VHT80; /* XXX how to map the remaining enum ieee80211_channel_flags? */ if (channels[i].flags & IEEE80211_CHAN_NO_HT40) cflags &= ~NET80211_CBW_FLAG_HT40; error = ieee80211_add_channel_cbw(c, maxchan, n, channels[i].hw_value, channels[i].center_freq, channels[i].max_power, nflags, bands, cflags); /* net80211::ENOBUFS: *n >= maxchans */ if (error != 0 && error != ENOBUFS) ic_printf(ic, "%s: Adding chan %u/%u/%#x/%#x/%#x/%#x " "returned error %d\n", __func__, channels[i].hw_value, channels[i].center_freq, channels[i].flags, nflags, chan_flags, cflags, error); if (error != 0) break; } } /* NL80211_BAND_5GHZ */ nchans = 0; if (hw->wiphy->bands[NL80211_BAND_5GHZ] != NULL) nchans = hw->wiphy->bands[NL80211_BAND_5GHZ]->n_channels; if (nchans > 0) { memset(bands, 0, sizeof(bands)); chan_flags = 0; setbit(bands, IEEE80211_MODE_11A); #ifdef __not_yet__ if (hw->wiphy->bands[NL80211_BAND_5GHZ]->ht_cap.ht_supported) { setbit(bands, IEEE80211_MODE_11NA); chan_flags |= NET80211_CBW_FLAG_HT40; } if (hw->wiphy->bands[NL80211_BAND_5GHZ]->vht_cap.vht_supported){ ic->ic_flags_ext |= IEEE80211_FEXT_VHT; ic->ic_vht_cap.vht_cap_info = hw->wiphy->bands[NL80211_BAND_5GHZ]->vht_cap.cap; setbit(bands, IEEE80211_MODE_VHT_5GHZ); chan_flags |= NET80211_CBW_FLAG_VHT80; if (IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_IS_160MHZ( ic->ic_vht_cap.vht_cap_info)) chan_flags |= NET80211_CBW_FLAG_VHT160; if (IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_IS_160_80P80MHZ( ic->ic_vht_cap.vht_cap_info)) chan_flags |= NET80211_CBW_FLAG_VHT80P80; } #endif channels = hw->wiphy->bands[NL80211_BAND_5GHZ]->channels; for (i = 0; i < nchans && *n < maxchan; i++) { uint32_t nflags = 0; int cflags = chan_flags; if (channels[i].flags & IEEE80211_CHAN_DISABLED) { ic_printf(ic, "%s: Skipping disabled chan " "[%u/%u/%#x]\n", __func__, channels[i].hw_value, channels[i].center_freq, channels[i].flags); continue; } if (channels[i].flags & IEEE80211_CHAN_NO_IR) nflags |= (IEEE80211_CHAN_NOADHOC|IEEE80211_CHAN_PASSIVE); if (channels[i].flags & IEEE80211_CHAN_RADAR) nflags |= IEEE80211_CHAN_DFS; if (channels[i].flags & IEEE80211_CHAN_NO_160MHZ) cflags &= ~(NET80211_CBW_FLAG_VHT160|NET80211_CBW_FLAG_VHT80P80); if (channels[i].flags & IEEE80211_CHAN_NO_80MHZ) cflags &= ~NET80211_CBW_FLAG_VHT80; /* XXX hwo to map the remaining enum ieee80211_channel_flags? */ if (channels[i].flags & IEEE80211_CHAN_NO_HT40) cflags &= ~NET80211_CBW_FLAG_HT40; error = ieee80211_add_channel_cbw(c, maxchan, n, channels[i].hw_value, channels[i].center_freq, channels[i].max_power, nflags, bands, cflags); /* net80211::ENOBUFS: *n >= maxchans */ if (error != 0 && error != ENOBUFS) ic_printf(ic, "%s: Adding chan %u/%u/%#x/%#x/%#x/%#x " "returned error %d\n", __func__, channels[i].hw_value, channels[i].center_freq, channels[i].flags, nflags, chan_flags, cflags, error); if (error != 0) break; } } } static void * lkpi_ieee80211_ifalloc(void) { struct ieee80211com *ic; ic = malloc(sizeof(*ic), M_LKPI80211, M_WAITOK | M_ZERO); if (ic == NULL) return (NULL); /* Setting these happens later when we have device information. */ ic->ic_softc = NULL; ic->ic_name = "linuxkpi"; return (ic); } struct ieee80211_hw * linuxkpi_ieee80211_alloc_hw(size_t priv_len, const struct ieee80211_ops *ops) { struct ieee80211_hw *hw; struct lkpi_hw *lhw; struct wiphy *wiphy; int ac; /* Get us and the driver data also allocated. */ wiphy = wiphy_new(&linuxkpi_mac80211cfgops, sizeof(*lhw) + priv_len); if (wiphy == NULL) return (NULL); lhw = wiphy_priv(wiphy); lhw->ops = ops; LKPI_80211_LHW_LOCK_INIT(lhw); LKPI_80211_LHW_SCAN_LOCK_INIT(lhw); sx_init_flags(&lhw->lvif_sx, "lhw-lvif", SX_RECURSE | SX_DUPOK); TAILQ_INIT(&lhw->lvif_head); for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { lhw->txq_generation[ac] = 1; TAILQ_INIT(&lhw->scheduled_txqs[ac]); } /* * XXX-BZ TODO make sure there is a "_null" function to all ops * not initialized. */ hw = LHW_TO_HW(lhw); hw->wiphy = wiphy; hw->conf.flags |= IEEE80211_CONF_IDLE; hw->priv = (void *)(lhw + 1); /* BSD Specific. */ lhw->ic = lkpi_ieee80211_ifalloc(); if (lhw->ic == NULL) { ieee80211_free_hw(hw); return (NULL); } IMPROVE(); return (hw); } void linuxkpi_ieee80211_iffree(struct ieee80211_hw *hw) { struct lkpi_hw *lhw; lhw = HW_TO_LHW(hw); free(lhw->ic, M_LKPI80211); lhw->ic = NULL; /* Cleanup more of lhw here or in wiphy_free()? */ sx_destroy(&lhw->lvif_sx); LKPI_80211_LHW_LOCK_DESTROY(lhw); LKPI_80211_LHW_SCAN_LOCK_DESTROY(lhw); IMPROVE(); } void linuxkpi_set_ieee80211_dev(struct ieee80211_hw *hw, char *name) { struct lkpi_hw *lhw; struct ieee80211com *ic; lhw = HW_TO_LHW(hw); ic = lhw->ic; /* Now set a proper name before ieee80211_ifattach(). */ ic->ic_softc = lhw; ic->ic_name = name; /* XXX-BZ do we also need to set wiphy name? */ } struct ieee80211_hw * linuxkpi_wiphy_to_ieee80211_hw(struct wiphy *wiphy) { struct lkpi_hw *lhw; lhw = wiphy_priv(wiphy); return (LHW_TO_HW(lhw)); } static void lkpi_radiotap_attach(struct lkpi_hw *lhw) { struct ieee80211com *ic; ic = lhw->ic; ieee80211_radiotap_attach(ic, &lhw->rtap_tx.wt_ihdr, sizeof(lhw->rtap_tx), LKPI_RTAP_TX_FLAGS_PRESENT, &lhw->rtap_rx.wr_ihdr, sizeof(lhw->rtap_rx), LKPI_RTAP_RX_FLAGS_PRESENT); } int linuxkpi_ieee80211_ifattach(struct ieee80211_hw *hw) { struct ieee80211com *ic; struct lkpi_hw *lhw; int band, i; lhw = HW_TO_LHW(hw); ic = lhw->ic; /* We do it this late as wiphy->dev should be set for the name. */ lhw->workq = alloc_ordered_workqueue(wiphy_name(hw->wiphy), 0); if (lhw->workq == NULL) return (-EAGAIN); /* XXX-BZ figure this out how they count his... */ if (!is_zero_ether_addr(hw->wiphy->perm_addr)) { IEEE80211_ADDR_COPY(ic->ic_macaddr, hw->wiphy->perm_addr); } else if (hw->wiphy->n_addresses > 0) { /* We take the first one. */ IEEE80211_ADDR_COPY(ic->ic_macaddr, hw->wiphy->addresses[0].addr); } else { ic_printf(ic, "%s: warning, no hardware address!\n", __func__); } #ifdef __not_yet__ /* See comment in lkpi_80211_txq_tx_one(). */ ic->ic_headroom = hw->extra_tx_headroom; #endif ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ ic->ic_opmode = IEEE80211_M_STA; /* Set device capabilities. */ /* XXX-BZ we need to get these from linux80211/drivers and convert. */ ic->ic_caps = IEEE80211_C_STA | IEEE80211_C_MONITOR | IEEE80211_C_WPA | /* WPA/RSN */ #ifdef LKPI_80211_WME IEEE80211_C_WME | #endif #if 0 IEEE80211_C_PMGT | #endif IEEE80211_C_SHSLOT | /* short slot time supported */ IEEE80211_C_SHPREAMBLE /* short preamble supported */ ; #if 0 /* Scanning is a different kind of beast to re-work. */ ic->ic_caps |= IEEE80211_C_BGSCAN; #endif if (lhw->ops->hw_scan) { /* * Advertise full-offload scanning. * * Not limiting to SINGLE_SCAN_ON_ALL_BANDS here as otherwise * we essentially disable hw_scan for all drivers not setting * the flag. */ ic->ic_flags_ext |= IEEE80211_FEXT_SCAN_OFFLOAD; lhw->scan_flags |= LKPI_LHW_SCAN_HW; } #ifdef __notyet__ ic->ic_htcaps = IEEE80211_HTC_HT /* HT operation */ | IEEE80211_HTC_AMPDU /* A-MPDU tx/rx */ | IEEE80211_HTC_AMSDU /* A-MSDU tx/rx */ | IEEE80211_HTCAP_MAXAMSDU_3839 /* max A-MSDU length */ | IEEE80211_HTCAP_SMPS_OFF; /* SM power save off */ ic->ic_htcaps |= IEEE80211_HTCAP_SHORTGI20; ic->ic_htcaps |= IEEE80211_HTCAP_CHWIDTH40 | IEEE80211_HTCAP_SHORTGI40; ic->ic_htcaps |= IEEE80211_HTCAP_TXSTBC; #endif /* * The wiphy variables report bitmasks of avail antennas. * (*get_antenna) get the current bitmask sets which can be * altered by (*set_antenna) for some drivers. * XXX-BZ will the count alone do us much good long-term in net80211? */ if (hw->wiphy->available_antennas_rx || hw->wiphy->available_antennas_tx) { uint32_t rxs, txs; if (lkpi_80211_mo_get_antenna(hw, &txs, &rxs) == 0) { ic->ic_rxstream = bitcount32(rxs); ic->ic_txstream = bitcount32(txs); } } ic->ic_cryptocaps = 0; #ifdef LKPI_80211_HW_CRYPTO if (hw->wiphy->n_cipher_suites > 0) { for (i = 0; i < hw->wiphy->n_cipher_suites; i++) ic->ic_cryptocaps |= lkpi_l80211_to_net80211_cyphers( hw->wiphy->cipher_suites[i]); } #endif lkpi_ic_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels); ieee80211_ifattach(ic); ic->ic_update_mcast = lkpi_ic_update_mcast; ic->ic_update_promisc = lkpi_ic_update_promisc; ic->ic_update_chw = lkpi_ic_update_chw; ic->ic_parent = lkpi_ic_parent; ic->ic_scan_start = lkpi_ic_scan_start; ic->ic_scan_end = lkpi_ic_scan_end; ic->ic_set_channel = lkpi_ic_set_channel; ic->ic_transmit = lkpi_ic_transmit; ic->ic_raw_xmit = lkpi_ic_raw_xmit; ic->ic_vap_create = lkpi_ic_vap_create; ic->ic_vap_delete = lkpi_ic_vap_delete; ic->ic_getradiocaps = lkpi_ic_getradiocaps; ic->ic_wme.wme_update = lkpi_ic_wme_update; lhw->ic_scan_curchan = ic->ic_scan_curchan; ic->ic_scan_curchan = lkpi_ic_scan_curchan; lhw->ic_scan_mindwell = ic->ic_scan_mindwell; ic->ic_scan_mindwell = lkpi_ic_scan_mindwell; lhw->ic_node_alloc = ic->ic_node_alloc; ic->ic_node_alloc = lkpi_ic_node_alloc; lhw->ic_node_init = ic->ic_node_init; ic->ic_node_init = lkpi_ic_node_init; lhw->ic_node_cleanup = ic->ic_node_cleanup; ic->ic_node_cleanup = lkpi_ic_node_cleanup; lhw->ic_node_free = ic->ic_node_free; ic->ic_node_free = lkpi_ic_node_free; lkpi_radiotap_attach(lhw); /* * Assign the first possible channel for now; seems Realtek drivers * expect one. * Also remember the amount of bands we support and the most rates * in any band so we can scale [(ext) sup rates] IE(s) accordingly. */ lhw->supbands = lhw->max_rates = 0; for (band = 0; band < NUM_NL80211_BANDS; band++) { struct ieee80211_supported_band *supband; struct linuxkpi_ieee80211_channel *channels; supband = hw->wiphy->bands[band]; if (supband == NULL || supband->n_channels == 0) continue; lhw->supbands++; lhw->max_rates = max(lhw->max_rates, supband->n_bitrates); /* If we have a channel, we need to keep counting supbands. */ if (hw->conf.chandef.chan != NULL) continue; channels = supband->channels; for (i = 0; i < supband->n_channels; i++) { if (channels[i].flags & IEEE80211_CHAN_DISABLED) continue; cfg80211_chandef_create(&hw->conf.chandef, &channels[i], NL80211_CHAN_NO_HT); break; } } IMPROVE("see net80211::ieee80211_chan_init vs. wiphy->bands[].bitrates possibly in lkpi_ic_getradiocaps?"); /* Make sure we do not support more than net80211 is willing to take. */ if (lhw->max_rates > IEEE80211_RATE_MAXSIZE) { ic_printf(ic, "%s: limiting max_rates %d to %d!\n", __func__, lhw->max_rates, IEEE80211_RATE_MAXSIZE); lhw->max_rates = IEEE80211_RATE_MAXSIZE; } /* * The maximum supported bitrates on any band + size for * DSSS Parameter Set give our per-band IE size. * XXX-BZ FIXME add HT VHT ... later * SSID is the responsibility of the driver and goes on the side. * The user specified bits coming from the vap go into the * "common ies" fields. */ lhw->scan_ie_len = 2 + IEEE80211_RATE_SIZE; if (lhw->max_rates > IEEE80211_RATE_SIZE) lhw->scan_ie_len += 2 + (lhw->max_rates - IEEE80211_RATE_SIZE); if (hw->wiphy->features & NL80211_FEATURE_DS_PARAM_SET_IE_IN_PROBES) { /* * net80211 does not seem to support the DSSS Parameter Set but * some of the drivers insert it so calculate the extra fixed * space in. */ lhw->scan_ie_len += 2 + 1; } /* Reduce the max_scan_ie_len "left" by the amount we consume already. */ if (hw->wiphy->max_scan_ie_len > 0) { if (lhw->scan_ie_len > hw->wiphy->max_scan_ie_len) goto err; hw->wiphy->max_scan_ie_len -= lhw->scan_ie_len; } if (bootverbose) ieee80211_announce(ic); return (0); err: IMPROVE("TODO FIXME CLEANUP"); return (-EAGAIN); } void linuxkpi_ieee80211_ifdetach(struct ieee80211_hw *hw) { struct lkpi_hw *lhw; struct ieee80211com *ic; lhw = HW_TO_LHW(hw); ic = lhw->ic; ieee80211_ifdetach(ic); } void linuxkpi_ieee80211_iterate_interfaces(struct ieee80211_hw *hw, enum ieee80211_iface_iter flags, void(*iterfunc)(void *, uint8_t *, struct ieee80211_vif *), void *arg) { struct lkpi_hw *lhw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; bool active, atomic, nin_drv; lhw = HW_TO_LHW(hw); if (flags & ~(IEEE80211_IFACE_ITER_NORMAL| IEEE80211_IFACE_ITER_RESUME_ALL| IEEE80211_IFACE_SKIP_SDATA_NOT_IN_DRIVER| IEEE80211_IFACE_ITER_ACTIVE|IEEE80211_IFACE_ITER__ATOMIC)) { ic_printf(lhw->ic, "XXX TODO %s flags(%#x) not yet supported.\n", __func__, flags); } active = (flags & IEEE80211_IFACE_ITER_ACTIVE) != 0; atomic = (flags & IEEE80211_IFACE_ITER__ATOMIC) != 0; nin_drv = (flags & IEEE80211_IFACE_SKIP_SDATA_NOT_IN_DRIVER) != 0; if (atomic) LKPI_80211_LHW_LVIF_LOCK(lhw); TAILQ_FOREACH(lvif, &lhw->lvif_head, lvif_entry) { struct ieee80211vap *vap; vif = LVIF_TO_VIF(lvif); /* * If we want "active" interfaces, we need to distinguish on * whether the driver knows about them or not to be able to * handle the "resume" case correctly. Skip the ones the * driver does not know about. */ if (active && !lvif->added_to_drv && (flags & IEEE80211_IFACE_ITER_RESUME_ALL) != 0) continue; /* * If we shall skip interfaces not added to the driver do so * if we haven't yet. */ if (nin_drv && !lvif->added_to_drv) continue; /* * Run the iterator function if we are either not asking * asking for active only or if the VAP is "running". */ /* XXX-BZ probably should have state in the lvif as well. */ vap = LVIF_TO_VAP(lvif); if (!active || (vap->iv_state != IEEE80211_S_INIT)) iterfunc(arg, vif->addr, vif); } if (atomic) LKPI_80211_LHW_LVIF_UNLOCK(lhw); } void linuxkpi_ieee80211_iterate_keys(struct ieee80211_hw *hw, struct ieee80211_vif *vif, void(*iterfunc)(struct ieee80211_hw *, struct ieee80211_vif *, struct ieee80211_sta *, struct ieee80211_key_conf *, void *), void *arg) { UNIMPLEMENTED; } void linuxkpi_ieee80211_iterate_chan_contexts(struct ieee80211_hw *hw, void(*iterfunc)(struct ieee80211_hw *, struct ieee80211_chanctx_conf *, void *), void *arg) { struct lkpi_hw *lhw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct lkpi_chanctx *lchanctx; KASSERT(hw != NULL && iterfunc != NULL, ("%s: hw %p iterfunc %p arg %p\n", __func__, hw, iterfunc, arg)); lhw = HW_TO_LHW(hw); IMPROVE("lchanctx should be its own list somewhere"); LKPI_80211_LHW_LVIF_LOCK(lhw); TAILQ_FOREACH(lvif, &lhw->lvif_head, lvif_entry) { vif = LVIF_TO_VIF(lvif); if (vif->chanctx_conf == NULL) continue; lchanctx = CHANCTX_CONF_TO_LCHANCTX(vif->chanctx_conf); if (!lchanctx->added_to_drv) continue; iterfunc(hw, &lchanctx->conf, arg); } LKPI_80211_LHW_LVIF_UNLOCK(lhw); } void linuxkpi_ieee80211_iterate_stations_atomic(struct ieee80211_hw *hw, void (*iterfunc)(void *, struct ieee80211_sta *), void *arg) { struct lkpi_hw *lhw; struct lkpi_vif *lvif; struct lkpi_sta *lsta; struct ieee80211_sta *sta; KASSERT(hw != NULL && iterfunc != NULL, ("%s: hw %p iterfunc %p arg %p\n", __func__, hw, iterfunc, arg)); lhw = HW_TO_LHW(hw); LKPI_80211_LHW_LVIF_LOCK(lhw); TAILQ_FOREACH(lvif, &lhw->lvif_head, lvif_entry) { LKPI_80211_LVIF_LOCK(lvif); TAILQ_FOREACH(lsta, &lvif->lsta_head, lsta_entry) { if (!lsta->added_to_drv) continue; sta = LSTA_TO_STA(lsta); iterfunc(arg, sta); } LKPI_80211_LVIF_UNLOCK(lvif); } LKPI_80211_LHW_LVIF_UNLOCK(lhw); } struct linuxkpi_ieee80211_regdomain * lkpi_get_linuxkpi_ieee80211_regdomain(size_t n) { struct linuxkpi_ieee80211_regdomain *regd; regd = kzalloc(sizeof(*regd) + n * sizeof(struct ieee80211_reg_rule), GFP_KERNEL); return (regd); } int linuxkpi_regulatory_set_wiphy_regd_sync(struct wiphy *wiphy, struct linuxkpi_ieee80211_regdomain *regd) { struct lkpi_hw *lhw; struct ieee80211com *ic; struct ieee80211_regdomain *rd; lhw = wiphy_priv(wiphy); ic = lhw->ic; rd = &ic->ic_regdomain; if (rd->isocc[0] == '\0') { rd->isocc[0] = regd->alpha2[0]; rd->isocc[1] = regd->alpha2[1]; } TODO(); /* XXX-BZ finish the rest. */ return (0); } void linuxkpi_ieee80211_scan_completed(struct ieee80211_hw *hw, struct cfg80211_scan_info *info) { struct lkpi_hw *lhw; struct ieee80211com *ic; struct ieee80211_scan_state *ss; lhw = wiphy_priv(hw->wiphy); ic = lhw->ic; ss = ic->ic_scan; ieee80211_scan_done(ss->ss_vap); LKPI_80211_LHW_SCAN_LOCK(lhw); free(lhw->hw_req, M_LKPI80211); lhw->hw_req = NULL; lhw->scan_flags &= ~LKPI_LHW_SCAN_RUNNING; wakeup(lhw); LKPI_80211_LHW_SCAN_UNLOCK(lhw); return; } /* For %list see comment towards the end of the function. */ void linuxkpi_ieee80211_rx(struct ieee80211_hw *hw, struct sk_buff *skb, struct ieee80211_sta *sta, struct napi_struct *napi __unused, struct list_head *list __unused) { - struct epoch_tracker et; struct lkpi_hw *lhw; struct ieee80211com *ic; struct mbuf *m; struct skb_shared_info *shinfo; struct ieee80211_rx_status *rx_status; struct ieee80211_rx_stats rx_stats; struct ieee80211_node *ni; struct ieee80211vap *vap; struct ieee80211_hdr *hdr; struct lkpi_sta *lsta; int i, offset, ok; int8_t rssi; bool is_beacon; if (skb->len < 2) { /* Need 80211 stats here. */ IMPROVE(); goto err; } /* * For now do the data copy; we can later improve things. Might even * have an mbuf backing the skb data then? */ m = m_get2(skb->len, M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) goto err; m_copyback(m, 0, skb->tail - skb->data, skb->data); shinfo = skb_shinfo(skb); offset = m->m_len; for (i = 0; i < shinfo->nr_frags; i++) { m_copyback(m, offset, shinfo->frags[i].size, (uint8_t *)linux_page_address(shinfo->frags[i].page) + shinfo->frags[i].offset); offset += shinfo->frags[i].size; } rx_status = IEEE80211_SKB_RXCB(skb); hdr = (void *)skb->data; is_beacon = ieee80211_is_beacon(hdr->frame_control); #ifdef LINUXKPI_DEBUG_80211 if (is_beacon && (linuxkpi_debug_80211 & D80211_TRACE_RX_BEACONS) == 0) goto no_trace_beacons; if (linuxkpi_debug_80211 & D80211_TRACE_RX) printf("TRACE-RX: %s: skb %p a/l/d/t-len (%u/%u/%u/%u) " "h %p d %p t %p e %p sh %p (%u) m %p plen %u len %u%s\n", __func__, skb, skb->_alloc_len, skb->len, skb->data_len, skb->truesize, skb->head, skb->data, skb->tail, skb->end, shinfo, shinfo->nr_frags, m, m->m_pkthdr.len, m->m_len, is_beacon ? " beacon" : ""); if (linuxkpi_debug_80211 & D80211_TRACE_RX_DUMP) hexdump(mtod(m, const void *), m->m_len, "RX (raw) ", 0); /* Implement a dump_rxcb() !!! */ if (linuxkpi_debug_80211 & D80211_TRACE_RX) printf("TRACE %s: RXCB: %ju %ju %u, %#0x, %u, %#0x, %#0x, " "%u band %u, %u { %d %d %d %d }, %d, %#x %#x %#x %#x %u %u %u\n", __func__, (uintmax_t)rx_status->boottime_ns, (uintmax_t)rx_status->mactime, rx_status->device_timestamp, rx_status->flag, rx_status->freq, rx_status->bw, rx_status->encoding, rx_status->ampdu_reference, rx_status->band, rx_status->chains, rx_status->chain_signal[0], rx_status->chain_signal[1], rx_status->chain_signal[2], rx_status->chain_signal[3], rx_status->signal, rx_status->enc_flags, rx_status->he_dcm, rx_status->he_gi, rx_status->he_ru, rx_status->zero_length_psdu_type, rx_status->nss, rx_status->rate_idx); no_trace_beacons: #endif memset(&rx_stats, 0, sizeof(rx_stats)); rx_stats.r_flags = IEEE80211_R_NF | IEEE80211_R_RSSI; /* XXX-BZ correct hardcoded rssi and noise floor, how? survey? */ rx_stats.c_nf = -96; if (ieee80211_hw_check(hw, SIGNAL_DBM) && !(rx_status->flag & RX_FLAG_NO_SIGNAL_VAL)) rssi = rx_status->signal; else rssi = rx_stats.c_nf; /* * net80211 signal strength data are in .5 dBm units relative to * the current noise floor (see comment in ieee80211_node.h). */ rssi -= rx_stats.c_nf; rx_stats.c_rssi = rssi * 2; rx_stats.r_flags |= IEEE80211_R_BAND; rx_stats.c_band = lkpi_nl80211_band_to_net80211_band(rx_status->band); rx_stats.r_flags |= IEEE80211_R_FREQ | IEEE80211_R_IEEE; rx_stats.c_freq = rx_status->freq; rx_stats.c_ieee = ieee80211_mhz2ieee(rx_stats.c_freq, rx_stats.c_band); /* XXX (*sta_statistics)() to get to some of that? */ /* XXX-BZ dump the FreeBSD version of rx_stats as well! */ lhw = HW_TO_LHW(hw); ic = lhw->ic; ok = ieee80211_add_rx_params(m, &rx_stats); if (ok == 0) { m_freem(m); counter_u64_add(ic->ic_ierrors, 1); goto err; } if (sta != NULL) { lsta = STA_TO_LSTA(sta); ni = ieee80211_ref_node(lsta->ni); } else { struct ieee80211_frame_min *wh; wh = mtod(m, struct ieee80211_frame_min *); ni = ieee80211_find_rxnode(ic, wh); if (ni != NULL) lsta = ni->ni_drv_data; } if (ni != NULL) vap = ni->ni_vap; else /* * XXX-BZ can we improve this by looking at the frame hdr * or other meta-data passed up? */ vap = TAILQ_FIRST(&ic->ic_vaps); #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE_RX) printf("TRACE %s: sta %p lsta %p state %d ni %p vap %p%s\n", __func__, sta, lsta, (lsta != NULL) ? lsta->state : -1, ni, vap, is_beacon ? " beacon" : ""); #endif if (ni != NULL && vap != NULL && is_beacon && rx_status->device_timestamp > 0 && m->m_pkthdr.len >= sizeof(struct ieee80211_frame)) { struct lkpi_vif *lvif; struct ieee80211_vif *vif; struct ieee80211_frame *wh; wh = mtod(m, struct ieee80211_frame *); if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid)) goto skip_device_ts; lvif = VAP_TO_LVIF(vap); vif = LVIF_TO_VIF(lvif); IMPROVE("TIMING_BEACON_ONLY?"); /* mac80211 specific (not net80211) so keep it here. */ vif->bss_conf.sync_device_ts = rx_status->device_timestamp; /* * net80211 should take care of the other information (sync_tsf, * sync_dtim_count) as otherwise we need to parse the beacon. */ } skip_device_ts: if (vap != NULL && vap->iv_state > IEEE80211_S_INIT && ieee80211_radiotap_active_vap(vap)) { struct lkpi_radiotap_rx_hdr *rtap; rtap = &lhw->rtap_rx; rtap->wr_tsft = rx_status->device_timestamp; rtap->wr_flags = 0; if (rx_status->enc_flags & RX_ENC_FLAG_SHORTPRE) rtap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; if (rx_status->enc_flags & RX_ENC_FLAG_SHORT_GI) rtap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTGI; #if 0 /* .. or it does not given we strip it below. */ if (ieee80211_hw_check(hw, RX_INCLUDES_FCS)) rtap->wr_flags |= IEEE80211_RADIOTAP_F_FCS; #endif if (rx_status->flag & RX_FLAG_FAILED_FCS_CRC) rtap->wr_flags |= IEEE80211_RADIOTAP_F_BADFCS; rtap->wr_rate = 0; IMPROVE(); /* XXX TODO status->encoding / rate_index / bw */ rtap->wr_chan_freq = htole16(rx_stats.c_freq); if (ic->ic_curchan->ic_ieee == rx_stats.c_ieee) rtap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); rtap->wr_dbm_antsignal = rssi; rtap->wr_dbm_antnoise = rx_stats.c_nf; } if (ieee80211_hw_check(hw, RX_INCLUDES_FCS)) m_adj(m, -IEEE80211_CRC_LEN); #if 0 if (list != NULL) { /* * Normally this would be queued up and delivered by * netif_receive_skb_list(), napi_gro_receive(), or the like. * See mt76::mac80211.c as only current possible consumer. */ IMPROVE("we simply pass the packet to net80211 to deal with."); } #endif - NET_EPOCH_ENTER(et); if (ni != NULL) { ok = ieee80211_input_mimo(ni, m); ieee80211_free_node(ni); if (ok < 0) m_freem(m); } else { ok = ieee80211_input_mimo_all(ic, m); /* mbuf got consumed. */ } - NET_EPOCH_EXIT(et); #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE_RX) printf("TRACE %s: handled frame type %#0x\n", __func__, ok); #endif IMPROVE(); err: /* The skb is ours so we can free it :-) */ kfree_skb(skb); } uint8_t linuxkpi_ieee80211_get_tid(struct ieee80211_hdr *hdr, bool nonqos_ok) { const struct ieee80211_frame *wh; uint8_t tid; /* Linux seems to assume this is a QOS-Data-Frame */ KASSERT(nonqos_ok || ieee80211_is_data_qos(hdr->frame_control), ("%s: hdr %p fc %#06x not qos_data\n", __func__, hdr, hdr->frame_control)); wh = (const struct ieee80211_frame *)hdr; tid = ieee80211_gettid(wh); KASSERT(nonqos_ok || tid == (tid & IEEE80211_QOS_TID), ("%s: tid %u " "not expected (%u?)\n", __func__, tid, IEEE80211_NONQOS_TID)); return (tid); } struct wiphy * linuxkpi_wiphy_new(const struct cfg80211_ops *ops, size_t priv_len) { struct lkpi_wiphy *lwiphy; lwiphy = kzalloc(sizeof(*lwiphy) + priv_len, GFP_KERNEL); if (lwiphy == NULL) return (NULL); lwiphy->ops = ops; /* XXX TODO */ return (LWIPHY_TO_WIPHY(lwiphy)); } void linuxkpi_wiphy_free(struct wiphy *wiphy) { struct lkpi_wiphy *lwiphy; if (wiphy == NULL) return; lwiphy = WIPHY_TO_LWIPHY(wiphy); kfree(lwiphy); } uint32_t linuxkpi_ieee80211_channel_to_frequency(uint32_t channel, enum nl80211_band band) { switch (band) { case NL80211_BAND_2GHZ: return (ieee80211_ieee2mhz(channel, IEEE80211_CHAN_2GHZ)); break; case NL80211_BAND_5GHZ: return (ieee80211_ieee2mhz(channel, IEEE80211_CHAN_5GHZ)); break; default: /* XXX abort, retry, error, panic? */ break; } return (0); } uint32_t linuxkpi_ieee80211_frequency_to_channel(uint32_t freq, uint32_t flags __unused) { return (ieee80211_mhz2ieee(freq, 0)); } static struct lkpi_sta * lkpi_find_lsta_by_ni(struct lkpi_vif *lvif, struct ieee80211_node *ni) { struct lkpi_sta *lsta, *temp; LKPI_80211_LVIF_LOCK(lvif); TAILQ_FOREACH_SAFE(lsta, &lvif->lsta_head, lsta_entry, temp) { if (lsta->ni == ni) { LKPI_80211_LVIF_UNLOCK(lvif); return (lsta); } } LKPI_80211_LVIF_UNLOCK(lvif); return (NULL); } struct ieee80211_sta * linuxkpi_ieee80211_find_sta(struct ieee80211_vif *vif, const u8 *peer) { struct lkpi_vif *lvif; struct lkpi_sta *lsta, *temp; struct ieee80211_sta *sta; lvif = VIF_TO_LVIF(vif); LKPI_80211_LVIF_LOCK(lvif); TAILQ_FOREACH_SAFE(lsta, &lvif->lsta_head, lsta_entry, temp) { sta = LSTA_TO_STA(lsta); if (IEEE80211_ADDR_EQ(sta->addr, peer)) { LKPI_80211_LVIF_UNLOCK(lvif); return (sta); } } LKPI_80211_LVIF_UNLOCK(lvif); return (NULL); } struct ieee80211_sta * linuxkpi_ieee80211_find_sta_by_ifaddr(struct ieee80211_hw *hw, const uint8_t *addr, const uint8_t *ourvifaddr) { struct lkpi_hw *lhw; struct lkpi_vif *lvif; struct lkpi_sta *lsta; struct ieee80211_vif *vif; struct ieee80211_sta *sta; lhw = wiphy_priv(hw->wiphy); sta = NULL; LKPI_80211_LHW_LVIF_LOCK(lhw); TAILQ_FOREACH(lvif, &lhw->lvif_head, lvif_entry) { /* XXX-BZ check our address from the vif. */ vif = LVIF_TO_VIF(lvif); if (ourvifaddr != NULL && !IEEE80211_ADDR_EQ(vif->addr, ourvifaddr)) continue; sta = linuxkpi_ieee80211_find_sta(vif, addr); if (sta != NULL) break; } LKPI_80211_LHW_LVIF_UNLOCK(lhw); if (sta != NULL) { lsta = STA_TO_LSTA(sta); if (!lsta->added_to_drv) return (NULL); } return (sta); } struct sk_buff * linuxkpi_ieee80211_tx_dequeue(struct ieee80211_hw *hw, struct ieee80211_txq *txq) { struct lkpi_txq *ltxq; struct lkpi_vif *lvif; struct sk_buff *skb; skb = NULL; ltxq = TXQ_TO_LTXQ(txq); ltxq->seen_dequeue = true; if (ltxq->stopped) goto stopped; lvif = VIF_TO_LVIF(ltxq->txq.vif); if (lvif->hw_queue_stopped[ltxq->txq.ac]) { ltxq->stopped = true; goto stopped; } skb = skb_dequeue(<xq->skbq); stopped: return (skb); } void linuxkpi_ieee80211_txq_get_depth(struct ieee80211_txq *txq, unsigned long *frame_cnt, unsigned long *byte_cnt) { struct lkpi_txq *ltxq; struct sk_buff *skb; unsigned long fc, bc; ltxq = TXQ_TO_LTXQ(txq); fc = bc = 0; skb_queue_walk(<xq->skbq, skb) { fc++; bc += skb->len; } if (frame_cnt) *frame_cnt = fc; if (byte_cnt) *byte_cnt = bc; /* Validate that this is doing the correct thing. */ /* Should we keep track on en/dequeue? */ IMPROVE(); } /* * We are called from ieee80211_free_txskb() or ieee80211_tx_status(). * The latter tries to derive the success status from the info flags * passed back from the driver. rawx_mit() saves the ni on the m and the * m on the skb for us to be able to give feedback to net80211. */ static void _lkpi_ieee80211_free_txskb(struct ieee80211_hw *hw, struct sk_buff *skb, int status) { struct ieee80211_node *ni; struct mbuf *m; m = skb->m; skb->m = NULL; if (m != NULL) { ni = m->m_pkthdr.PH_loc.ptr; /* Status: 0 is ok, != 0 is error. */ ieee80211_tx_complete(ni, m, status); /* ni & mbuf were consumed. */ } } void linuxkpi_ieee80211_free_txskb(struct ieee80211_hw *hw, struct sk_buff *skb, int status) { _lkpi_ieee80211_free_txskb(hw, skb, status); kfree_skb(skb); } void linuxkpi_ieee80211_tx_status_ext(struct ieee80211_hw *hw, struct ieee80211_tx_status *txstat) { struct sk_buff *skb; struct ieee80211_tx_info *info; struct ieee80211_ratectl_tx_status txs; struct ieee80211_node *ni; int status; skb = txstat->skb; if (skb->m != NULL) { struct mbuf *m; m = skb->m; ni = m->m_pkthdr.PH_loc.ptr; memset(&txs, 0, sizeof(txs)); } else { ni = NULL; } info = txstat->info; if (info->flags & IEEE80211_TX_STAT_ACK) { status = 0; /* No error. */ txs.status = IEEE80211_RATECTL_TX_SUCCESS; } else { status = 1; txs.status = IEEE80211_RATECTL_TX_FAIL_UNSPECIFIED; } if (ni != NULL) { int ridx __unused; #ifdef LINUXKPI_DEBUG_80211 int old_rate; old_rate = ni->ni_vap->iv_bss->ni_txrate; #endif txs.pktlen = skb->len; txs.flags |= IEEE80211_RATECTL_STATUS_PKTLEN; if (info->status.rates[0].count > 1) { txs.long_retries = info->status.rates[0].count - 1; /* 1 + retries in drivers. */ txs.flags |= IEEE80211_RATECTL_STATUS_LONG_RETRY; } #if 0 /* Unused in net80211 currently. */ /* XXX-BZ convert check .flags for MCS/VHT/.. */ txs.final_rate = info->status.rates[0].idx; txs.flags |= IEEE80211_RATECTL_STATUS_FINAL_RATE; #endif if (info->status.flags & IEEE80211_TX_STATUS_ACK_SIGNAL_VALID) { txs.rssi = info->status.ack_signal; /* XXX-BZ CONVERT? */ txs.flags |= IEEE80211_RATECTL_STATUS_RSSI; } IMPROVE("only update of rate matches but that requires us to get a proper rate"); ieee80211_ratectl_tx_complete(ni, &txs); ridx = ieee80211_ratectl_rate(ni->ni_vap->iv_bss, NULL, 0); #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE_TX) { printf("TX-RATE: %s: old %d new %d ridx %d, " "long_retries %d\n", __func__, old_rate, ni->ni_vap->iv_bss->ni_txrate, ridx, txs.long_retries); } #endif } #ifdef LINUXKPI_DEBUG_80211 if (linuxkpi_debug_80211 & D80211_TRACE_TX) printf("TX-STATUS: %s: hw %p skb %p status %d : flags %#x " "band %u hw_queue %u tx_time_est %d : " "rates [ %u %u %#x, %u %u %#x, %u %u %#x, %u %u %#x ] " "ack_signal %u ampdu_ack_len %u ampdu_len %u antenna %u " "tx_time %u flags %#x " "status_driver_data [ %p %p ]\n", __func__, hw, skb, status, info->flags, info->band, info->hw_queue, info->tx_time_est, info->status.rates[0].idx, info->status.rates[0].count, info->status.rates[0].flags, info->status.rates[1].idx, info->status.rates[1].count, info->status.rates[1].flags, info->status.rates[2].idx, info->status.rates[2].count, info->status.rates[2].flags, info->status.rates[3].idx, info->status.rates[3].count, info->status.rates[3].flags, info->status.ack_signal, info->status.ampdu_ack_len, info->status.ampdu_len, info->status.antenna, info->status.tx_time, info->status.flags, info->status.status_driver_data[0], info->status.status_driver_data[1]); #endif if (txstat->free_list) { _lkpi_ieee80211_free_txskb(hw, skb, status); list_add_tail(&skb->list, txstat->free_list); } else { linuxkpi_ieee80211_free_txskb(hw, skb, status); } } void linuxkpi_ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb) { struct ieee80211_tx_status status; memset(&status, 0, sizeof(status)); status.info = IEEE80211_SKB_CB(skb); status.skb = skb; /* sta, n_rates, rates, free_list? */ ieee80211_tx_status_ext(hw, &status); } /* * This is an internal bandaid for the moment for the way we glue * skbs and mbufs together for TX. Once we have skbs backed by * mbufs this should go away. * This is a public function but kept on the private KPI (lkpi_) * and is not exposed by a header file. */ static void lkpi_ieee80211_free_skb_mbuf(void *p) { struct ieee80211_node *ni; struct mbuf *m; if (p == NULL) return; m = (struct mbuf *)p; M_ASSERTPKTHDR(m); ni = m->m_pkthdr.PH_loc.ptr; m->m_pkthdr.PH_loc.ptr = NULL; if (ni != NULL) ieee80211_free_node(ni); m_freem(m); } void linuxkpi_ieee80211_queue_delayed_work(struct ieee80211_hw *hw, struct delayed_work *w, int delay) { struct lkpi_hw *lhw; /* Need to make sure hw is in a stable (non-suspended) state. */ IMPROVE(); lhw = HW_TO_LHW(hw); queue_delayed_work(lhw->workq, w, delay); } void linuxkpi_ieee80211_queue_work(struct ieee80211_hw *hw, struct work_struct *w) { struct lkpi_hw *lhw; /* Need to make sure hw is in a stable (non-suspended) state. */ IMPROVE(); lhw = HW_TO_LHW(hw); queue_work(lhw->workq, w); } struct sk_buff * linuxkpi_ieee80211_probereq_get(struct ieee80211_hw *hw, uint8_t *addr, uint8_t *ssid, size_t ssid_len, size_t tailroom) { struct sk_buff *skb; struct ieee80211_frame *wh; uint8_t *p; size_t len; len = sizeof(*wh); len += 2 + ssid_len; skb = dev_alloc_skb(hw->extra_tx_headroom + len + tailroom); if (skb == NULL) return (NULL); skb_reserve(skb, hw->extra_tx_headroom); wh = skb_put_zero(skb, sizeof(*wh)); wh->i_fc[0] = IEEE80211_FC0_VERSION_0; wh->i_fc[0] |= IEEE80211_FC0_SUBTYPE_PROBE_REQ | IEEE80211_FC0_TYPE_MGT; IEEE80211_ADDR_COPY(wh->i_addr1, ieee80211broadcastaddr); IEEE80211_ADDR_COPY(wh->i_addr2, addr); IEEE80211_ADDR_COPY(wh->i_addr3, ieee80211broadcastaddr); p = skb_put(skb, 2 + ssid_len); *p++ = IEEE80211_ELEMID_SSID; *p++ = ssid_len; if (ssid_len > 0) memcpy(p, ssid, ssid_len); return (skb); } struct sk_buff * linuxkpi_ieee80211_pspoll_get(struct ieee80211_hw *hw, struct ieee80211_vif *vif) { struct lkpi_vif *lvif; struct ieee80211vap *vap; struct sk_buff *skb; struct ieee80211_frame_pspoll *psp; uint16_t v; skb = dev_alloc_skb(hw->extra_tx_headroom + sizeof(*psp)); if (skb == NULL) return (NULL); skb_reserve(skb, hw->extra_tx_headroom); lvif = VIF_TO_LVIF(vif); vap = LVIF_TO_VAP(lvif); psp = skb_put_zero(skb, sizeof(*psp)); psp->i_fc[0] = IEEE80211_FC0_VERSION_0; psp->i_fc[0] |= IEEE80211_FC0_SUBTYPE_PS_POLL | IEEE80211_FC0_TYPE_CTL; v = htole16(vif->cfg.aid | 1<<15 | 1<<16); memcpy(&psp->i_aid, &v, sizeof(v)); IEEE80211_ADDR_COPY(psp->i_bssid, vap->iv_bss->ni_macaddr); IEEE80211_ADDR_COPY(psp->i_ta, vif->addr); return (skb); } struct sk_buff * linuxkpi_ieee80211_nullfunc_get(struct ieee80211_hw *hw, struct ieee80211_vif *vif, int linkid, bool qos) { struct lkpi_vif *lvif; struct ieee80211vap *vap; struct sk_buff *skb; struct ieee80211_frame *nullf; IMPROVE("linkid"); skb = dev_alloc_skb(hw->extra_tx_headroom + sizeof(*nullf)); if (skb == NULL) return (NULL); skb_reserve(skb, hw->extra_tx_headroom); lvif = VIF_TO_LVIF(vif); vap = LVIF_TO_VAP(lvif); nullf = skb_put_zero(skb, sizeof(*nullf)); nullf->i_fc[0] = IEEE80211_FC0_VERSION_0; nullf->i_fc[0] |= IEEE80211_FC0_SUBTYPE_NODATA | IEEE80211_FC0_TYPE_DATA; nullf->i_fc[1] = IEEE80211_FC1_DIR_TODS; IEEE80211_ADDR_COPY(nullf->i_addr1, vap->iv_bss->ni_bssid); IEEE80211_ADDR_COPY(nullf->i_addr2, vif->addr); IEEE80211_ADDR_COPY(nullf->i_addr3, vap->iv_bss->ni_macaddr); return (skb); } struct wireless_dev * linuxkpi_ieee80211_vif_to_wdev(struct ieee80211_vif *vif) { struct lkpi_vif *lvif; lvif = VIF_TO_LVIF(vif); return (&lvif->wdev); } void linuxkpi_ieee80211_connection_loss(struct ieee80211_vif *vif) { struct lkpi_vif *lvif; struct ieee80211vap *vap; enum ieee80211_state nstate; int arg; lvif = VIF_TO_LVIF(vif); vap = LVIF_TO_VAP(lvif); /* * Go to init; otherwise we need to elaborately check state and * handle accordingly, e.g., if in RUN we could call iv_bmiss. * Let the statemachine handle all neccessary changes. */ nstate = IEEE80211_S_INIT; arg = 0; /* Not a valid reason. */ ic_printf(vap->iv_ic, "%s: vif %p vap %p state %s\n", __func__, vif, vap, ieee80211_state_name[vap->iv_state]); ieee80211_new_state(vap, nstate, arg); } void linuxkpi_ieee80211_beacon_loss(struct ieee80211_vif *vif) { struct lkpi_vif *lvif; struct ieee80211vap *vap; lvif = VIF_TO_LVIF(vif); vap = LVIF_TO_VAP(lvif); ic_printf(vap->iv_ic, "%s: vif %p vap %p state %s\n", __func__, vif, vap, ieee80211_state_name[vap->iv_state]); ieee80211_beacon_miss(vap->iv_ic); } /* -------------------------------------------------------------------------- */ void linuxkpi_ieee80211_stop_queue(struct ieee80211_hw *hw, int qnum) { struct lkpi_hw *lhw; struct lkpi_vif *lvif; struct ieee80211_vif *vif; int ac_count, ac; KASSERT(qnum < hw->queues, ("%s: qnum %d >= hw->queues %d, hw %p\n", __func__, qnum, hw->queues, hw)); lhw = wiphy_priv(hw->wiphy); /* See lkpi_ic_vap_create(). */ if (hw->queues >= IEEE80211_NUM_ACS) ac_count = IEEE80211_NUM_ACS; else ac_count = 1; LKPI_80211_LHW_LVIF_LOCK(lhw); TAILQ_FOREACH(lvif, &lhw->lvif_head, lvif_entry) { vif = LVIF_TO_VIF(lvif); for (ac = 0; ac < ac_count; ac++) { IMPROVE_TXQ("LOCKING"); if (qnum == vif->hw_queue[ac]) { #ifdef LINUXKPI_DEBUG_80211 /* * For now log this to better understand * how this is supposed to work. */ if (lvif->hw_queue_stopped[ac] && (linuxkpi_debug_80211 & D80211_IMPROVE_TXQ) != 0) ic_printf(lhw->ic, "%s:%d: lhw %p hw %p " "lvif %p vif %p ac %d qnum %d already " "stopped\n", __func__, __LINE__, lhw, hw, lvif, vif, ac, qnum); #endif lvif->hw_queue_stopped[ac] = true; } } } LKPI_80211_LHW_LVIF_UNLOCK(lhw); } void linuxkpi_ieee80211_stop_queues(struct ieee80211_hw *hw) { int i; IMPROVE_TXQ("Locking; do we need further info?"); for (i = 0; i < hw->queues; i++) linuxkpi_ieee80211_stop_queue(hw, i); } static void lkpi_ieee80211_wake_queues(struct ieee80211_hw *hw, int hwq) { struct lkpi_hw *lhw; struct lkpi_vif *lvif; struct lkpi_sta *lsta; int ac_count, ac, tid; /* See lkpi_ic_vap_create(). */ if (hw->queues >= IEEE80211_NUM_ACS) ac_count = IEEE80211_NUM_ACS; else ac_count = 1; lhw = wiphy_priv(hw->wiphy); IMPROVE_TXQ("Locking"); LKPI_80211_LHW_LVIF_LOCK(lhw); TAILQ_FOREACH(lvif, &lhw->lvif_head, lvif_entry) { struct ieee80211_vif *vif; vif = LVIF_TO_VIF(lvif); for (ac = 0; ac < ac_count; ac++) { if (hwq == vif->hw_queue[ac]) { /* XXX-BZ what about software scan? */ #ifdef LINUXKPI_DEBUG_80211 /* * For now log this to better understand * how this is supposed to work. */ if (!lvif->hw_queue_stopped[ac] && (linuxkpi_debug_80211 & D80211_IMPROVE_TXQ) != 0) ic_printf(lhw->ic, "%s:%d: lhw %p hw %p " "lvif %p vif %p ac %d hw_q not stopped\n", __func__, __LINE__, lhw, hw, lvif, vif, ac); #endif lvif->hw_queue_stopped[ac] = false; LKPI_80211_LVIF_LOCK(lvif); TAILQ_FOREACH(lsta, &lvif->lsta_head, lsta_entry) { struct ieee80211_sta *sta; sta = LSTA_TO_STA(lsta); for (tid = 0; tid < nitems(sta->txq); tid++) { struct lkpi_txq *ltxq; if (sta->txq[tid] == NULL) continue; if (sta->txq[tid]->ac != ac) continue; ltxq = TXQ_TO_LTXQ(sta->txq[tid]); if (!ltxq->stopped) continue; ltxq->stopped = false; /* XXX-BZ see when this explodes with all the locking. taskq? */ lkpi_80211_mo_wake_tx_queue(hw, sta->txq[tid]); } } LKPI_80211_LVIF_UNLOCK(lvif); } } } LKPI_80211_LHW_LVIF_UNLOCK(lhw); } void linuxkpi_ieee80211_wake_queues(struct ieee80211_hw *hw) { int i; IMPROVE_TXQ("Is this all/enough here?"); for (i = 0; i < hw->queues; i++) lkpi_ieee80211_wake_queues(hw, i); } void linuxkpi_ieee80211_wake_queue(struct ieee80211_hw *hw, int qnum) { KASSERT(qnum < hw->queues, ("%s: qnum %d >= hw->queues %d, hw %p\n", __func__, qnum, hw->queues, hw)); lkpi_ieee80211_wake_queues(hw, qnum); } /* This is just hardware queues. */ void linuxkpi_ieee80211_txq_schedule_start(struct ieee80211_hw *hw, uint8_t ac) { struct lkpi_hw *lhw; lhw = HW_TO_LHW(hw); IMPROVE_TXQ("Are there reasons why we wouldn't schedule?"); IMPROVE_TXQ("LOCKING"); if (++lhw->txq_generation[ac] == 0) lhw->txq_generation[ac]++; } struct ieee80211_txq * linuxkpi_ieee80211_next_txq(struct ieee80211_hw *hw, uint8_t ac) { struct lkpi_hw *lhw; struct ieee80211_txq *txq; struct lkpi_txq *ltxq; lhw = HW_TO_LHW(hw); txq = NULL; IMPROVE_TXQ("LOCKING"); /* Check that we are scheduled. */ if (lhw->txq_generation[ac] == 0) goto out; ltxq = TAILQ_FIRST(&lhw->scheduled_txqs[ac]); if (ltxq == NULL) goto out; if (ltxq->txq_generation == lhw->txq_generation[ac]) goto out; ltxq->txq_generation = lhw->txq_generation[ac]; TAILQ_REMOVE(&lhw->scheduled_txqs[ac], ltxq, txq_entry); txq = <xq->txq; TAILQ_ELEM_INIT(ltxq, txq_entry); out: return (txq); } void linuxkpi_ieee80211_schedule_txq(struct ieee80211_hw *hw, struct ieee80211_txq *txq, bool withoutpkts) { struct lkpi_hw *lhw; struct lkpi_txq *ltxq; ltxq = TXQ_TO_LTXQ(txq); IMPROVE_TXQ("LOCKING"); /* Only schedule if work to do or asked to anyway. */ if (!withoutpkts && skb_queue_empty(<xq->skbq)) goto out; /* Make sure we do not double-schedule. */ if (ltxq->txq_entry.tqe_next != NULL) goto out; lhw = HW_TO_LHW(hw); TAILQ_INSERT_TAIL(&lhw->scheduled_txqs[txq->ac], ltxq, txq_entry); out: return; } /* -------------------------------------------------------------------------- */ struct lkpi_cfg80211_bss { u_int refcnt; struct cfg80211_bss bss; }; struct lkpi_cfg80211_get_bss_iter_lookup { struct wiphy *wiphy; struct linuxkpi_ieee80211_channel *chan; const uint8_t *bssid; const uint8_t *ssid; size_t ssid_len; enum ieee80211_bss_type bss_type; enum ieee80211_privacy privacy; /* * Something to store a copy of the result as the net80211 scan cache * is not refoucnted so a scan entry might go away any time. */ bool match; struct cfg80211_bss *bss; }; static void lkpi_cfg80211_get_bss_iterf(void *arg, const struct ieee80211_scan_entry *se) { struct lkpi_cfg80211_get_bss_iter_lookup *lookup; size_t ielen; lookup = arg; /* Do not try to find another match. */ if (lookup->match) return; /* Nothing to store result. */ if (lookup->bss == NULL) return; if (lookup->privacy != IEEE80211_PRIVACY_ANY) { /* if (se->se_capinfo & IEEE80211_CAPINFO_PRIVACY) */ /* We have no idea what to compare to as the drivers only request ANY */ return; } if (lookup->bss_type != IEEE80211_BSS_TYPE_ANY) { /* if (se->se_capinfo & (IEEE80211_CAPINFO_IBSS|IEEE80211_CAPINFO_ESS)) */ /* We have no idea what to compare to as the drivers only request ANY */ return; } if (lookup->chan != NULL) { struct linuxkpi_ieee80211_channel *chan; chan = linuxkpi_ieee80211_get_channel(lookup->wiphy, se->se_chan->ic_freq); if (chan == NULL || chan != lookup->chan) return; } if (lookup->bssid && !IEEE80211_ADDR_EQ(lookup->bssid, se->se_bssid)) return; if (lookup->ssid) { if (lookup->ssid_len != se->se_ssid[1] || se->se_ssid[1] == 0) return; if (memcmp(lookup->ssid, se->se_ssid+2, lookup->ssid_len) != 0) return; } ielen = se->se_ies.len; lookup->bss->ies = malloc(sizeof(*lookup->bss->ies) + ielen, M_LKPI80211, M_NOWAIT | M_ZERO); if (lookup->bss->ies == NULL) return; lookup->bss->ies->data = (uint8_t *)lookup->bss->ies + sizeof(*lookup->bss->ies); lookup->bss->ies->len = ielen; if (ielen) memcpy(lookup->bss->ies->data, se->se_ies.data, ielen); lookup->match = true; } struct cfg80211_bss * linuxkpi_cfg80211_get_bss(struct wiphy *wiphy, struct linuxkpi_ieee80211_channel *chan, const uint8_t *bssid, const uint8_t *ssid, size_t ssid_len, enum ieee80211_bss_type bss_type, enum ieee80211_privacy privacy) { struct lkpi_cfg80211_bss *lbss; struct lkpi_cfg80211_get_bss_iter_lookup lookup; struct lkpi_hw *lhw; struct ieee80211vap *vap; lhw = wiphy_priv(wiphy); /* Let's hope we can alloc. */ lbss = malloc(sizeof(*lbss), M_LKPI80211, M_NOWAIT | M_ZERO); if (lbss == NULL) { ic_printf(lhw->ic, "%s: alloc failed.\n", __func__); return (NULL); } lookup.wiphy = wiphy; lookup.chan = chan; lookup.bssid = bssid; lookup.ssid = ssid; lookup.ssid_len = ssid_len; lookup.bss_type = bss_type; lookup.privacy = privacy; lookup.match = false; lookup.bss = &lbss->bss; IMPROVE("Iterate over all VAPs comparing perm_addr and addresses?"); vap = TAILQ_FIRST(&lhw->ic->ic_vaps); ieee80211_scan_iterate(vap, lkpi_cfg80211_get_bss_iterf, &lookup); if (!lookup.match) { free(lbss, M_LKPI80211); return (NULL); } refcount_init(&lbss->refcnt, 1); return (&lbss->bss); } void linuxkpi_cfg80211_put_bss(struct wiphy *wiphy, struct cfg80211_bss *bss) { struct lkpi_cfg80211_bss *lbss; lbss = container_of(bss, struct lkpi_cfg80211_bss, bss); /* Free everything again on refcount ... */ if (refcount_release(&lbss->refcnt)) { free(lbss->bss.ies, M_LKPI80211); free(lbss, M_LKPI80211); } } void linuxkpi_cfg80211_bss_flush(struct wiphy *wiphy) { struct lkpi_hw *lhw; struct ieee80211com *ic; struct ieee80211vap *vap; lhw = wiphy_priv(wiphy); ic = lhw->ic; /* * If we haven't called ieee80211_ifattach() yet * or there is no VAP, there are no scans to flush. */ if (ic == NULL || (lhw->sc_flags & LKPI_MAC80211_DRV_STARTED) == 0) return; /* Should only happen on the current one? Not seen it late enough. */ IEEE80211_LOCK(ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) ieee80211_scan_flush(vap); IEEE80211_UNLOCK(ic); } /* -------------------------------------------------------------------------- */ MODULE_VERSION(linuxkpi_wlan, 1); MODULE_DEPEND(linuxkpi_wlan, linuxkpi, 1, 1, 1); MODULE_DEPEND(linuxkpi_wlan, wlan, 1, 1, 1); diff --git a/sys/net80211/ieee80211_hostap.c b/sys/net80211/ieee80211_hostap.c index b7db78f32282..22e4cfd54536 100644 --- a/sys/net80211/ieee80211_hostap.c +++ b/sys/net80211/ieee80211_hostap.c @@ -1,2480 +1,2484 @@ /*- * 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. */ #include #ifdef __FreeBSD__ #endif /* * 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 */ #define IEEE80211_RATE2MBS(r) (((r) & IEEE80211_RATE_VAL) / 2) 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); /* XXX MCS/HT */ printf(" channel %d start %uMb\n", ieee80211_chan2ieee(ic, ic->ic_curchan), IEEE80211_RATE2MBS(ni->ni_txrate)); } #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) { + struct epoch_tracker et; + /* * 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; } + NET_EPOCH_ENTER(et); ifp->if_input(ifp, m); + NET_EPOCH_EXIT(et); } } /* * 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 ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) != IEEE80211_FC0_VERSION_0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "wrong version, 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, ifp->if_broadcastaddr)) { /* 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)) { if_printf(ifp, "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); 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 defered (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 defered (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; 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; 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_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); /* XXXPMKID */ 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 ifnet *ifp = vap->iv_ifp; 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, ifp->if_broadcastaddr); /* 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, 1); 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_ie_vhtcap) - 2, return); } if (vhtinfo != NULL) { IEEE80211_VERIFY_LENGTH(vhtinfo[1], sizeof(struct ieee80211_ie_vht_operation) - 2, 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__); 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; the 11n spec only specifies these ciphers * so permitting any others is undefined and can lead * to interoperability problems. */ if ((ni->ni_flags & IEEE80211_NODE_HT) && (((vap->iv_flags & IEEE80211_F_WPA) && rsnparms.rsn_ucastcipher != IEEE80211_CIPHER_AES_CCM) || (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_input.c b/sys/net80211/ieee80211_input.c index 0cc6902ae28e..a4bfe49bd8c4 100644 --- a/sys/net80211/ieee80211_input.c +++ b/sys/net80211/ieee80211_input.c @@ -1,1058 +1,1061 @@ /*- * 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 epoch_tracker et; 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; } + NET_EPOCH_ENTER(et); ifp->if_input(ifp, m); + NET_EPOCH_EXIT(et); } 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_ie_vhtcap) - 2, scan->vhtcap = NULL); } if (scan->vhtopmode != NULL) { IEEE80211_VERIFY_LENGTH(scan->vhtopmode[1], sizeof(struct ieee80211_ie_vht_operation) - 2, 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__); 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: ", ether_sprintf(mac), tag); ieee80211_print_essid(ssid + 2, ssid[1]); 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); if_printf(vap->iv_ifp, "%s", buf); /* NB: no \n */ if (len >= sizeof(buf)) printf("%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); if_printf(vap->iv_ifp, "[%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); } 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); if_printf(vap->iv_ifp, "[%s] %s\n", ether_sprintf(mac), buf); if (len >= sizeof(buf)) 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); if_printf(vap->iv_ifp, "[%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); } 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); if_printf(vap->iv_ifp, "[%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); } 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); if_printf(vap->iv_ifp, "[%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); } #endif /* IEEE80211_DEBUG */