--- src/sys/netproto/802_11/wlan/ieee80211_input.c 2005/11/28 17:13:46 1.3 +++ src/sys/netproto/802_11/wlan/ieee80211_input.c 2006/05/18 13:51:46 1.4 @@ -1,6 +1,6 @@ /* * Copyright (c) 2001 Atsushi Onoe - * Copyright (c) 2002, 2003 Sam Leffler, Errno Consulting + * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,42 +29,97 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD: src/sys/net80211/ieee80211_input.c,v 1.21 2004/06/13 17:29:09 mlaier Exp $ + * $FreeBSD: src/sys/net80211/ieee80211_input.c,v 1.62.2.12 2006/03/14 23:24:02 sam Exp $ * $DragonFly$ */ -#include "opt_inet.h" - #include -#include +#include #include #include +#include #include +#include + #include -#include -#include -#include -#include -#include -#include -#include - #include -#include -#include #include +#include +#include #include #include +#include #include #include -#ifdef INET -#include -#include -#endif +#ifdef IEEE80211_DEBUG +#include + +/* + * 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 ieee80211com *ic, int subtype) +{ + switch (subtype) { + case IEEE80211_FC0_SUBTYPE_BEACON: + return (ic->ic_flags & IEEE80211_F_SCAN); + case IEEE80211_FC0_SUBTYPE_PROBE_REQ: + return (ic->ic_opmode == IEEE80211_M_IBSS); + } + return 1; +} + +/* + * Emit a debug message about discarding a frame or information + * element. One format is for extracting the mac address from + * the frame header; the other is for when a header is not + * available or otherwise appropriate. + */ +#define IEEE80211_DISCARD(_ic, _m, _wh, _type, _fmt, ...) do { \ + if ((_ic)->ic_debug & (_m)) \ + ieee80211_discard_frame(_ic, _wh, _type, _fmt, __VA_ARGS__);\ +} while (0) +#define IEEE80211_DISCARD_IE(_ic, _m, _wh, _type, _fmt, ...) do { \ + if ((_ic)->ic_debug & (_m)) \ + ieee80211_discard_ie(_ic, _wh, _type, _fmt, __VA_ARGS__);\ +} while (0) +#define IEEE80211_DISCARD_MAC(_ic, _m, _mac, _type, _fmt, ...) do { \ + if ((_ic)->ic_debug & (_m)) \ + ieee80211_discard_mac(_ic, _mac, _type, _fmt, __VA_ARGS__);\ +} while (0) + +static const uint8_t *ieee80211_getbssid(struct ieee80211com *, + const struct ieee80211_frame *); +static void ieee80211_discard_frame(struct ieee80211com *, + const struct ieee80211_frame *, const char *type, const char *fmt, ...); +static void ieee80211_discard_ie(struct ieee80211com *, + const struct ieee80211_frame *, const char *type, const char *fmt, ...); +static void ieee80211_discard_mac(struct ieee80211com *, + const uint8_t mac[IEEE80211_ADDR_LEN], const char *type, + const char *fmt, ...); +#else +#define IEEE80211_DISCARD(_ic, _m, _wh, _type, _fmt, ...) +#define IEEE80211_DISCARD_IE(_ic, _m, _wh, _type, _fmt, ...) +#define IEEE80211_DISCARD_MAC(_ic, _m, _mac, _type, _fmt, ...) +#endif /* IEEE80211_DEBUG */ + +static struct mbuf *ieee80211_defrag(struct ieee80211com *, + struct ieee80211_node *, struct mbuf *, int); +static struct mbuf *ieee80211_decap(struct ieee80211com *, struct mbuf *, int); +static void ieee80211_send_error(struct ieee80211com *, struct ieee80211_node *, + const uint8_t *mac, int subtype, int arg); +static void ieee80211_deliver_data(struct ieee80211com *, + struct ieee80211_node *, struct mbuf *); +static void ieee80211_node_pwrsave(struct ieee80211_node *, int enable); +static void ieee80211_recv_pspoll(struct ieee80211com *, + struct ieee80211_node *, struct mbuf *); /* * Process a received frame. The node associated with the sender @@ -76,20 +131,25 @@ * mean ``better signal''. The receive timestamp is currently not used * by the 802.11 layer. */ -void -ieee80211_input(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni, - int rssi, uint32_t rstamp) +int +ieee80211_input(struct ieee80211com *ic, struct mbuf *m, + struct ieee80211_node *ni, int rssi, uint32_t rstamp) { - struct ieee80211com *ic = (void *)ifp; +#define SEQ_LEQ(a,b) ((int)((a)-(b)) <= 0) +#define HAS_SEQ(type) ((type & 0x4) == 0) + struct ifnet *ifp = ic->ic_ifp; struct ieee80211_frame *wh; + struct ieee80211_key *key; struct ether_header *eh; - struct mbuf *m1; - int len; + int hdrspace; uint8_t dir, type, subtype; uint8_t *bssid; uint16_t rxseq; + ASSERT_SERIALIZED(ifp->if_serializer); + KASSERT(ni != NULL, ("null node")); + ni->ni_inact = ni->ni_inact_reload; #ifdef M_HASFCS /* trim CRC here so WEP can find its own CRC at the end of packet. */ @@ -98,9 +158,8 @@ ieee80211_input(struct ifnet *ifp, struc m->m_flags &= ~M_HASFCS; } #endif - KASSERT(m->m_pkthdr.len >= sizeof(struct ieee80211_frame_min), - ("frame length too short: %u", m->m_pkthdr.len)); + type = -1; /* undefined */ /* * In monitor mode, send everything directly to bpf. * XXX may want to include the CRC @@ -108,38 +167,40 @@ ieee80211_input(struct ifnet *ifp, struc if (ic->ic_opmode == IEEE80211_M_MONITOR) goto out; + if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_min)) { + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY, + ni->ni_macaddr, NULL, + "too short (1): len %u", m->m_pkthdr.len); + ic->ic_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) { - if (ifp->if_flags & IFF_DEBUG) - if_printf(ifp, "receive packet with wrong version: %x\n", - wh->i_fc[0]); + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY, + ni->ni_macaddr, NULL, "wrong version %x", wh->i_fc[0]); ic->ic_stats.is_rx_badversion++; goto err; } dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; - /* - * NB: We are not yet prepared to handle control frames, - * but permitting drivers to send them to us allows - * them to go through bpf tapping at the 802.11 layer. - */ - if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { - /* XXX statistic */ - IEEE80211_DPRINTF2(("%s: frame too short, len %u\n", - __func__, m->m_pkthdr.len)); - ic->ic_stats.is_rx_tooshort++; - goto out; /* XXX */ - } - if (ic->ic_state != IEEE80211_S_SCAN) { + subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; + if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { switch (ic->ic_opmode) { case IEEE80211_M_STA: - if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid)) { + bssid = wh->i_addr2; + if (!IEEE80211_ADDR_EQ(bssid, ni->ni_bssid)) { /* not interested in */ - IEEE80211_DPRINTF2(("%s: discard frame from " - "bss %6D\n", __func__, - wh->i_addr2, ":")); + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT, + bssid, NULL, "%s", "not to bss"); ic->ic_stats.is_rx_wrongbss++; goto out; } @@ -147,46 +208,106 @@ ieee80211_input(struct ifnet *ifp, struc case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: case IEEE80211_M_HOSTAP: - if (dir == IEEE80211_FC1_DIR_NODS) - bssid = wh->i_addr3; - else + 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(ic, + IEEE80211_MSG_ANY, ni->ni_macaddr, + NULL, "too short (2): len %u", + m->m_pkthdr.len); + ic->ic_stats.is_rx_tooshort++; + goto out; + } + bssid = wh->i_addr3; + } + if (type != IEEE80211_FC0_TYPE_DATA) + break; + /* + * Data frame, validate the bssid. + */ if (!IEEE80211_ADDR_EQ(bssid, ic->ic_bss->ni_bssid) && !IEEE80211_ADDR_EQ(bssid, ifp->if_broadcastaddr)) { /* not interested in */ - IEEE80211_DPRINTF2(("%s: discard frame from " - "bss %6D\n", __func__, - bssid, ":")); + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT, + bssid, NULL, "%s", "not to bss"); ic->ic_stats.is_rx_wrongbss++; goto out; } + /* + * For adhoc mode we cons up a node when it doesn't + * exist. This should probably done after an ACL check. + */ + if (ni == ic->ic_bss && + ic->ic_opmode != IEEE80211_M_HOSTAP && + !IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) { + /* + * Fake up a node for this newly + * discovered member of the IBSS. + */ + ni = ieee80211_fakeup_adhoc_node(&ic->ic_sta, + wh->i_addr2); + if (ni == NULL) { + /* NB: stat kept for alloc failure */ + goto err; + } + } break; - case IEEE80211_M_MONITOR: - goto out; default: - /* XXX catch bad values */ - break; + goto out; } ni->ni_rssi = rssi; ni->ni_rstamp = rstamp; - rxseq = ni->ni_rxseq; - ni->ni_rxseq = - le16toh(*(uint16_t *)wh->i_seq) >> IEEE80211_SEQ_SEQ_SHIFT; - /* TODO: fragment */ - if ((wh->i_fc[1] & IEEE80211_FC1_RETRY) && - rxseq == ni->ni_rxseq) { - /* duplicate, silently discarded */ - ic->ic_stats.is_rx_dup++; /* XXX per-station stat */ - goto out; + if (HAS_SEQ(type)) { + uint8_t tid; + if (IEEE80211_QOS_HAS_SEQ(wh)) { + tid = ((struct ieee80211_qosframe *)wh)-> + i_qos[0] & IEEE80211_QOS_TID; + if (TID_TO_WME_AC(tid) >= WME_AC_VI) + ic->ic_wme.wme_hipri_traffic++; + tid++; + } else + tid = 0; + rxseq = le16toh(*(uint16_t *)wh->i_seq); + if ((wh->i_fc[1] & IEEE80211_FC1_RETRY) && + SEQ_LEQ(rxseq, ni->ni_rxseqs[tid])) { + /* duplicate, discard */ + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT, + bssid, "duplicate", + "seqno <%u,%u> fragno <%u,%u> tid %u", + rxseq >> IEEE80211_SEQ_SEQ_SHIFT, + ni->ni_rxseqs[tid] >> + IEEE80211_SEQ_SEQ_SHIFT, + rxseq & IEEE80211_SEQ_FRAG_MASK, + ni->ni_rxseqs[tid] & + IEEE80211_SEQ_FRAG_MASK, + tid); + ic->ic_stats.is_rx_dup++; + IEEE80211_NODE_STAT(ni, rx_dup); + goto out; + } + ni->ni_rxseqs[tid] = rxseq; } - ni->ni_inact = 0; } 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(ic, IEEE80211_MSG_ANY, + ni->ni_macaddr, NULL, + "data too short: expecting %u", hdrspace); + ic->ic_stats.is_rx_tooshort++; + goto out; /* XXX */ + } switch (ic->ic_opmode) { case IEEE80211_M_STA: if (dir != IEEE80211_FC1_DIR_FROMDS) { + IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, + wh, "data", "%s", "unknown dir 0x%x", dir); ic->ic_stats.is_rx_wrongdir++; goto out; } @@ -199,6 +320,8 @@ ieee80211_input(struct ifnet *ifp, struc * It should be silently discarded for * SIMPLEX interface. */ + IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, + wh, NULL, "%s", "multicast echo"); ic->ic_stats.is_rx_mcastecho++; goto out; } @@ -206,199 +329,436 @@ ieee80211_input(struct ifnet *ifp, struc case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: if (dir != IEEE80211_FC1_DIR_NODS) { + IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, + wh, "data", "%s", "unknown dir 0x%x", dir); ic->ic_stats.is_rx_wrongdir++; goto out; } + /* XXX no power-save support */ break; case IEEE80211_M_HOSTAP: if (dir != IEEE80211_FC1_DIR_TODS) { + IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, + wh, "data", "%s", "unknown dir 0x%x", dir); ic->ic_stats.is_rx_wrongdir++; goto out; } /* check if source STA is associated */ if (ni == ic->ic_bss) { - IEEE80211_DPRINTF(("%s: data from unknown src " - "%6D\n", __func__, - wh->i_addr2, ":")); - /* NB: caller deals with reference */ - ni = ieee80211_dup_bss(ic, wh->i_addr2); - if (ni != NULL) { - IEEE80211_SEND_MGMT(ic, ni, - IEEE80211_FC0_SUBTYPE_DEAUTH, - IEEE80211_REASON_NOT_AUTHED); - ieee80211_free_node(ic, ni); - } + IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, + wh, "data", "%s", "unknown src"); + ieee80211_send_error(ic, ni, wh->i_addr2, + IEEE80211_FC0_SUBTYPE_DEAUTH, + IEEE80211_REASON_NOT_AUTHED); ic->ic_stats.is_rx_notassoc++; goto err; } if (ni->ni_associd == 0) { - IEEE80211_DPRINTF(("ieee80211_input: " - "data from unassoc src %6D\n", - wh->i_addr2, ":")); + IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, + wh, "data", "%s", "unassoc src"); IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DISASSOC, IEEE80211_REASON_NOT_ASSOCED); - ieee80211_unref_node(&ni); ic->ic_stats.is_rx_notassoc++; goto err; } + + /* + * Check for power save state change. + */ + if (((wh->i_fc[1] & IEEE80211_FC1_PWR_MGT) ^ + (ni->ni_flags & IEEE80211_NODE_PWR_MGT))) + ieee80211_node_pwrsave(ni, + wh->i_fc[1] & IEEE80211_FC1_PWR_MGT); break; - case IEEE80211_M_MONITOR: - break; + default: + /* XXX here to keep compiler happy */ + goto out; } + + /* + * Handle privacy requirements. Note that we + * must not be preempted from here until after + * we (potentially) call ieee80211_crypto_demic; + * otherwise we may violate assumptions in the + * crypto cipher modules used to do delayed update + * of replay sequence numbers. + */ if (wh->i_fc[1] & IEEE80211_FC1_WEP) { - if (ic->ic_flags & IEEE80211_F_WEPON) { - m = ieee80211_wep_crypt(ifp, m, 0); - if (m == NULL) { - ic->ic_stats.is_rx_wepfail++; - goto err; - } - wh = mtod(m, struct ieee80211_frame *); - } else { - ic->ic_stats.is_rx_nowep++; + if ((ic->ic_flags & IEEE80211_F_PRIVACY) == 0) { + /* + * Discard encrypted frames when privacy is off. + */ + IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, + wh, "WEP", "%s", "PRIVACY off"); + ic->ic_stats.is_rx_noprivacy++; + IEEE80211_NODE_STAT(ni, rx_noprivacy); + goto out; + } + key = ieee80211_crypto_decap(ic, ni, m, hdrspace); + if (key == NULL) { + /* NB: stats+msgs handled in crypto_decap */ + IEEE80211_NODE_STAT(ni, rx_wepfail); + goto out; + } + wh = mtod(m, struct ieee80211_frame *); + wh->i_fc[1] &= ~IEEE80211_FC1_WEP; + } else { + key = NULL; + } + + /* + * Next up, any fragmentation. + */ + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + m = ieee80211_defrag(ic, ni, m, hdrspace); + if (m == NULL) { + /* Fragment dropped or frame not complete yet */ goto out; } } + wh = NULL; /* no longer valid, catch any uses */ + + /* + * Next strip any MSDU crypto bits. + */ + if (key != NULL && !ieee80211_crypto_demic(ic, key, m, 0)) { + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT, + ni->ni_macaddr, "data", "%s", "demic error"); + IEEE80211_NODE_STAT(ni, rx_demicfail); + goto out; + } + /* copy to listener after decrypt */ - if (ic->ic_rawbpf != NULL) + if (ic->ic_rawbpf) bpf_mtap(ic->ic_rawbpf, m); - m = ieee80211_decap(ifp, m); + /* + * Finally, strip the 802.11 header. + */ + m = ieee80211_decap(ic, m, hdrspace); if (m == NULL) { + /* don't count Null data frames as errors */ + if (subtype == IEEE80211_FC0_SUBTYPE_NODATA) + goto out; + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT, + ni->ni_macaddr, "data", "%s", "decap error"); ic->ic_stats.is_rx_decap++; + IEEE80211_NODE_STAT(ni, rx_decap); goto err; } - ifp->if_ipackets++; - - /* perform as a bridge within the AP */ - m1 = NULL; - if (ic->ic_opmode == IEEE80211_M_HOSTAP) { - eh = mtod(m, struct ether_header *); - if (ETHER_IS_MULTICAST(eh->ether_dhost)) { - m1 = m_copypacket(m, MB_DONTWAIT); - if (m1 == NULL) - ifp->if_oerrors++; - else - m1->m_flags |= M_MCAST; - } else { - ni = ieee80211_find_node(ic, eh->ether_dhost); - if (ni != NULL) { - if (ni->ni_associd != 0) { - m1 = m; - m = NULL; - } - ieee80211_free_node(ic, ni); - } + eh = mtod(m, struct ether_header *); + if (!ieee80211_node_is_authorized(ni)) { + /* + * Deny any non-PAE frames received prior to + * authorization. For open/shared-key + * authentication the port is mark authorized + * after authentication completes. For 802.1x + * the port is not marked authorized by the + * authenticator until the handshake has completed. + */ + if (eh->ether_type != htons(ETHERTYPE_PAE)) { + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT, + eh->ether_shost, "data", + "unauthorized port: ether type 0x%x len %u", + eh->ether_type, m->m_pkthdr.len); + ic->ic_stats.is_rx_unauth++; + IEEE80211_NODE_STAT(ni, rx_unauth); + goto err; } - if (m1 != NULL) { - len = m1->m_pkthdr.len; - IF_ENQUEUE(&ifp->if_snd, m1); - if (m != NULL) - ifp->if_omcasts++; - ifp->if_obytes += len; + } else { + /* + * When denying unencrypted frames, discard + * any non-PAE frames received without encryption. + */ + if ((ic->ic_flags & IEEE80211_F_DROPUNENC) && + key == NULL && + eh->ether_type != htons(ETHERTYPE_PAE)) { + /* + * Drop unencrypted frames. + */ + ic->ic_stats.is_rx_unencrypted++; + IEEE80211_NODE_STAT(ni, rx_unencrypted); + goto out; } } - if (m != NULL) - ifp->if_input(ifp, m); - return; + ifp->if_ipackets++; + IEEE80211_NODE_STAT(ni, rx_data); + IEEE80211_NODE_STAT_ADD(ni, rx_bytes, m->m_pkthdr.len); + + ieee80211_deliver_data(ic, ni, m); + return IEEE80211_FC0_TYPE_DATA; case IEEE80211_FC0_TYPE_MGT: + IEEE80211_NODE_STAT(ni, rx_mgmt); if (dir != IEEE80211_FC1_DIR_NODS) { + IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, + wh, "data", "%s", "unknown dir 0x%x", dir); ic->ic_stats.is_rx_wrongdir++; goto err; } - if (ic->ic_opmode == IEEE80211_M_AHDEMO) { - ic->ic_stats.is_rx_ahdemo_mgt++; + if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY, + ni->ni_macaddr, "mgt", "too short: len %u", + m->m_pkthdr.len); + ic->ic_stats.is_rx_tooshort++; goto out; } - subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; - - /* drop frames without interest */ - if (ic->ic_state == IEEE80211_S_SCAN) { - if (subtype != IEEE80211_FC0_SUBTYPE_BEACON && - subtype != IEEE80211_FC0_SUBTYPE_PROBE_RESP) { - ic->ic_stats.is_rx_mgtdiscard++; +#ifdef IEEE80211_DEBUG + if ((ieee80211_msg_debug(ic) && doprint(ic, subtype)) || + ieee80211_msg_dumppkts(ic)) { + if_printf(ic->ic_ifp, "received %s from %6D rssi %d\n", + ieee80211_mgt_subtype_name[subtype >> + IEEE80211_FC0_SUBTYPE_SHIFT], + wh->i_addr2, ":", rssi); + } +#endif + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + if (subtype != IEEE80211_FC0_SUBTYPE_AUTH) { + /* + * Only shared key auth frames with a challenge + * should be encrypted, discard all others. + */ + IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, + wh, ieee80211_mgt_subtype_name[subtype >> + IEEE80211_FC0_SUBTYPE_SHIFT], + "%s", "WEP set but not permitted"); + ic->ic_stats.is_rx_mgtdiscard++; /* XXX */ goto out; } - } else { - if (ic->ic_opmode != IEEE80211_M_IBSS && - subtype == IEEE80211_FC0_SUBTYPE_BEACON) { - ic->ic_stats.is_rx_mgtdiscard++; + if ((ic->ic_flags & IEEE80211_F_PRIVACY) == 0) { + /* + * Discard encrypted frames when privacy is off. + */ + IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, + wh, "mgt", "%s", "WEP set but PRIVACY off"); + ic->ic_stats.is_rx_noprivacy++; goto out; } - } - - if (ifp->if_flags & IFF_DEBUG) { - /* avoid to print too many frames */ - int doprint = 0; - - switch (subtype) { - case IEEE80211_FC0_SUBTYPE_BEACON: - if (ic->ic_state == IEEE80211_S_SCAN) - doprint = 1; - break; - case IEEE80211_FC0_SUBTYPE_PROBE_REQ: - if (ic->ic_opmode == IEEE80211_M_IBSS) - doprint = 1; - break; - default: - doprint = 1; - break; + hdrspace = ieee80211_hdrspace(ic, wh); + key = ieee80211_crypto_decap(ic, ni, m, hdrspace); + if (key == NULL) { + /* NB: stats+msgs handled in crypto_decap */ + goto out; } -#ifdef IEEE80211_DEBUG - doprint += ieee80211_debug; -#endif - if (doprint) - if_printf(ifp, "received %s from %6D rssi %d\n", - ieee80211_mgt_subtype_name[subtype - >> IEEE80211_FC0_SUBTYPE_SHIFT], - wh->i_addr2, ":", rssi); + wh = mtod(m, struct ieee80211_frame *); + wh->i_fc[1] &= ~IEEE80211_FC1_WEP; } - if (ic->ic_rawbpf != NULL) + if (ic->ic_rawbpf) bpf_mtap(ic->ic_rawbpf, m); (*ic->ic_recv_mgmt)(ic, m, ni, subtype, rssi, rstamp); m_freem(m); - return; + return type; case IEEE80211_FC0_TYPE_CTL: + IEEE80211_NODE_STAT(ni, rx_ctrl); ic->ic_stats.is_rx_ctl++; + if (ic->ic_opmode == IEEE80211_M_HOSTAP) { + switch (subtype) { + case IEEE80211_FC0_SUBTYPE_PS_POLL: + ieee80211_recv_pspoll(ic, ni, m); + break; + } + } goto out; default: - IEEE80211_DPRINTF(("%s: bad type %x\n", __func__, type)); + IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY, + wh, NULL, "bad frame type 0x%x", type); /* should not come here */ break; } - err: +err: ifp->if_ierrors++; +out: + if (m != NULL) { + if (ic->ic_rawbpf) + bpf_mtap(ic->ic_rawbpf, m); + m_freem(m); + } + return type; +#undef SEQ_LEQ +} + +/* + * This function reassemble fragments. + */ +static struct mbuf * +ieee80211_defrag(struct ieee80211com *ic, struct ieee80211_node *ni, + struct mbuf *m, int hdrspace) +{ + 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; + + /* + * 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; + } + mfrag = ni->ni_rxfrag[0]; + ni->ni_rxfrag[0] = NULL; + + /* + * 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 */ + if (rxseq != last_rxseq+1 || + !IEEE80211_ADDR_EQ(wh->i_addr1, lwh->i_addr1) || + !IEEE80211_ADDR_EQ(wh->i_addr2, lwh->i_addr2)) { + /* + * Unrelated fragment or no space for it, + * clear current fragments. + */ + m_freem(mfrag); + mfrag = NULL; + } + } + + if (mfrag == NULL) { + if (fragno != 0) { /* !first fragment, discard */ + IEEE80211_NODE_STAT(ni, rx_defrag); + m_freem(m); + return NULL; + } + mfrag = m; + } else { /* concatenate */ + m_adj(m, hdrspace); /* strip header */ + m_cat(mfrag, m); + /* NB: m_cat doesn't update the packet header */ + mfrag->m_pkthdr.len += m->m_pkthdr.len; + /* track last seqnum and fragno */ + lwh = mtod(mfrag, struct ieee80211_frame *); + *(uint16_t *) lwh->i_seq = *(uint16_t *) wh->i_seq; + } + if (more_frag) { /* more to come, save */ + ni->ni_rxfragstamp = ticks; + ni->ni_rxfrag[0] = mfrag; + mfrag = NULL; + } + return mfrag; +} + +static void +ieee80211_deliver_data(struct ieee80211com *ic, + struct ieee80211_node *ni, struct mbuf *m) +{ + struct ether_header *eh = mtod(m, struct ether_header *); + struct ifnet *ifp = ic->ic_ifp; + + /* perform as a bridge within the AP */ + if (ic->ic_opmode == IEEE80211_M_HOSTAP && + (ic->ic_flags & IEEE80211_F_NOBRIDGE) == 0) { + struct mbuf *m1 = NULL; + + if (ETHER_IS_MULTICAST(eh->ether_dhost)) { + m1 = m_dup(m, MB_DONTWAIT); + if (m1 == NULL) + ifp->if_oerrors++; + else + m1->m_flags |= M_MCAST; + } else { + /* + * Check if the destination is known; if so + * and the port is authorized dispatch directly. + */ + struct ieee80211_node *sta = + ieee80211_find_node(&ic->ic_sta, 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 != ic->ic_bss) { + m1 = m; + m = NULL; + } + } else { + ic->ic_stats.is_rx_unauth++; + IEEE80211_NODE_STAT(sta, rx_unauth); + } + ieee80211_free_node(sta); + } + } + if (m1 != NULL) { + /* XXX bypasses ALTQ */ + ifq_handoff(ifp, m1, NULL); + } + } + if (m != NULL) { +#ifdef FREEBSD_VLAN + if (ni->ni_vlan != 0) { + /* attach vlan tag */ + VLAN_INPUT_TAG_NEW(ifp, m, ni->ni_vlan); + if (m == NULL) + goto out; /* XXX goto err? */ + } +#endif + ifp->if_input(ifp, m); + } + return; + +#ifdef FREEBSD_VLAN out: if (m != NULL) { - if (ic->ic_rawbpf != NULL) + if (ic->ic_rawbpf) bpf_mtap(ic->ic_rawbpf, m); m_freem(m); } +#endif } -struct mbuf * -ieee80211_decap(struct ifnet *ifp, struct mbuf *m) +static struct mbuf * +ieee80211_decap(struct ieee80211com *ic, struct mbuf *m, int hdrlen) { + struct ieee80211_qosframe_addr4 wh; /* Max size address frames */ struct ether_header *eh; - struct ieee80211_frame wh; struct llc *llc; - if (m->m_len < sizeof(wh) + sizeof(*llc)) { - m = m_pullup(m, sizeof(wh) + sizeof(*llc)); - if (m == NULL) - return NULL; + if (m->m_len < hdrlen + sizeof(*llc) && + (m = m_pullup(m, hdrlen + sizeof(*llc))) == NULL) { + /* XXX stat, msg */ + return NULL; } - memcpy(&wh, mtod(m, caddr_t), sizeof(wh)); - llc = (struct llc *)(mtod(m, caddr_t) + sizeof(wh)); + 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) { - m_adj(m, sizeof(wh) + sizeof(struct llc) - sizeof(*eh)); + m_adj(m, hdrlen + sizeof(struct llc) - sizeof(*eh)); llc = NULL; } else { - m_adj(m, sizeof(wh) - sizeof(*eh)); + m_adj(m, hdrlen - sizeof(*eh)); } eh = mtod(m, struct ether_header *); switch (wh.i_fc[1] & IEEE80211_FC1_DIR_MASK) { @@ -415,10 +775,9 @@ ieee80211_decap(struct ifnet *ifp, struc IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr3); break; case IEEE80211_FC1_DIR_DSTODS: - /* not yet supported */ - IEEE80211_DPRINTF(("%s: DS to DS\n", __func__)); - m_freem(m); - return NULL; + IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3); + IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr4); + break; } #ifdef ALIGNED_POINTER if (!ALIGNED_POINTER(mtod(m, caddr_t) + sizeof(*eh), uint32_t)) { @@ -432,7 +791,7 @@ ieee80211_decap(struct ifnet *ifp, struc pktlen = m->m_pkthdr.len; while (pktlen > off) { if (n0 == NULL) { - MGETHDR(n, MB_DONTWAIT, MT_DATA); + MGETHDR(n, M_DONTWAIT, MT_DATA); if (n == NULL) { m_freem(m); return NULL; @@ -440,7 +799,7 @@ ieee80211_decap(struct ifnet *ifp, struc M_MOVE_PKTHDR(n, m); n->m_len = MHLEN; } else { - MGET(n, MB_DONTWAIT, MT_DATA); + MGET(n, M_DONTWAIT, MT_DATA); if (n == NULL) { m_freem(m); m_freem(n0); @@ -449,7 +808,7 @@ ieee80211_decap(struct ifnet *ifp, struc n->m_len = MLEN; } if (pktlen - off >= MINCLSIZE) { - MCLGET(n, MB_DONTWAIT); + MCLGET(n, M_DONTWAIT); if (n->m_flags & M_EXT) n->m_len = n->m_ext.ext_size; } @@ -481,10 +840,11 @@ ieee80211_decap(struct ifnet *ifp, struc /* * Install received rate set information in the node's state block. */ -static int -ieee80211_setup_rates(struct ieee80211com *ic, struct ieee80211_node *ni, - uint8_t *rates, uint8_t *xrates, int flags) +int +ieee80211_setup_rates(struct ieee80211_node *ni, + const uint8_t *rates, const uint8_t *xrates, int flags) { + struct ieee80211com *ic = ni->ni_ic; struct ieee80211_rateset *rs = &ni->ni_rates; memset(rs, 0, sizeof(*rs)); @@ -498,32 +858,389 @@ ieee80211_setup_rates(struct ieee80211co nxrates = xrates[1]; if (rs->rs_nrates + nxrates > IEEE80211_RATE_MAXSIZE) { nxrates = IEEE80211_RATE_MAXSIZE - rs->rs_nrates; - IEEE80211_DPRINTF(("%s: extended rate set too large;" - " only using %u of %u rates\n", - __func__, nxrates, xrates[1])); + IEEE80211_DPRINTF(ic, IEEE80211_MSG_XRATE, + "[%6D] extended rate set too large;" + " only using %u of %u rates\n", + ni->ni_macaddr, ":", nxrates, xrates[1]); ic->ic_stats.is_rx_rstoobig++; } memcpy(rs->rs_rates + rs->rs_nrates, xrates+2, nxrates); rs->rs_nrates += nxrates; } - return ieee80211_fix_rate(ic, ni, flags); + return ieee80211_fix_rate(ni, flags); +} + +static void +ieee80211_auth_open(struct ieee80211com *ic, struct ieee80211_frame *wh, + struct ieee80211_node *ni, int rssi, uint32_t rstamp, uint16_t seq, + uint16_t status) +{ + + if (ni->ni_authmode == IEEE80211_AUTH_SHARED) { + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, + ni->ni_macaddr, "open auth", + "bad sta auth mode %u", ni->ni_authmode); + ic->ic_stats.is_rx_bad_auth++; /* XXX */ + if (ic->ic_opmode == IEEE80211_M_HOSTAP) { + /* XXX hack to workaround calling convention */ + ieee80211_send_error(ic, ni, wh->i_addr2, + IEEE80211_FC0_SUBTYPE_AUTH, + (seq + 1) | (IEEE80211_STATUS_ALG<<16)); + } + return; + } + switch (ic->ic_opmode) { + case IEEE80211_M_IBSS: + case IEEE80211_M_AHDEMO: + case IEEE80211_M_MONITOR: + /* should not come here */ + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, + ni->ni_macaddr, "open auth", + "bad operating mode %u", ic->ic_opmode); + break; + + case IEEE80211_M_HOSTAP: + if (ic->ic_state != IEEE80211_S_RUN || + seq != IEEE80211_AUTH_OPEN_REQUEST) { + ic->ic_stats.is_rx_bad_auth++; + return; + } + /* always accept open authentication requests */ + if (ni == ic->ic_bss) { + ni = ieee80211_dup_bss(&ic->ic_sta, 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; + + IEEE80211_SEND_MGMT(ic, ni, + IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); + IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, + "[%6D] station authenticated (open)\n", + ni->ni_macaddr, ":"); + /* + * 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); + break; + + case IEEE80211_M_STA: + if (ic->ic_state != IEEE80211_S_AUTH || + seq != IEEE80211_AUTH_OPEN_RESPONSE) { + ic->ic_stats.is_rx_bad_auth++; + return; + } + if (status != 0) { + IEEE80211_DPRINTF(ic, + IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, + "[%6D] open auth failed (reason %d)\n", + ni->ni_macaddr, ":", status); + /* XXX can this happen? */ + if (ni != ic->ic_bss) + ni->ni_fails++; + ic->ic_stats.is_rx_auth_fail++; + ieee80211_new_state(ic, IEEE80211_S_SCAN, 0); + } else + ieee80211_new_state(ic, IEEE80211_S_ASSOC, + wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK); + break; + } +} + +/* + * Send a management frame error response to the specified + * station. If ni is associated with the station then use + * it; otherwise allocate a temporary node suitable for + * transmitting the frame and then free the reference so + * it will go away as soon as the frame has been transmitted. + */ +static void +ieee80211_send_error(struct ieee80211com *ic, struct ieee80211_node *ni, + const uint8_t *mac, int subtype, int arg) +{ + int istmp; + + if (ni == ic->ic_bss) { + ni = ieee80211_tmp_node(ic, mac); + if (ni == NULL) { + /* XXX msg */ + return; + } + istmp = 1; + } else + istmp = 0; + IEEE80211_SEND_MGMT(ic, ni, subtype, arg); + if (istmp) + ieee80211_free_node(ni); +} + +static int +alloc_challenge(struct ieee80211com *ic, struct ieee80211_node *ni) +{ + if (ni->ni_challenge == NULL) + MALLOC(ni->ni_challenge, uint32_t*, IEEE80211_CHALLENGE_LEN, + M_DEVBUF, M_NOWAIT); + if (ni->ni_challenge == NULL) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, + "[%6D] shared key challenge alloc failed\n", + ni->ni_macaddr, ":"); + /* XXX statistic */ + } + return (ni->ni_challenge != NULL); +} + +/* XXX TODO: add statistics */ +static void +ieee80211_auth_shared(struct ieee80211com *ic, struct ieee80211_frame *wh, + uint8_t *frm, uint8_t *efrm, struct ieee80211_node *ni, int rssi, + uint32_t rstamp, uint16_t seq, uint16_t status) +{ + uint8_t *challenge; + int allocbs, estatus; + + /* + * 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 ((ic->ic_flags & IEEE80211_F_PRIVACY) == 0) { + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, + ni->ni_macaddr, "shared key auth", + "%s", " PRIVACY is disabled"); + estatus = IEEE80211_STATUS_ALG; + goto bad; + } + /* + * Pre-shared key authentication is evil; accept + * it only if explicitly configured (it is supported + * mainly for compatibility with clients like OS X). + */ + if (ni->ni_authmode != IEEE80211_AUTH_AUTO && + ni->ni_authmode != IEEE80211_AUTH_SHARED) { + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, + ni->ni_macaddr, "shared key auth", + "bad sta auth mode %u", ni->ni_authmode); + ic->ic_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(ic, IEEE80211_MSG_AUTH, + ni->ni_macaddr, "shared key auth", + "ie %d/%d too long", + frm[0], (frm[1] + 2) - (efrm - frm)); + ic->ic_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(ic, IEEE80211_MSG_AUTH, + ni->ni_macaddr, "shared key auth", + "%s", "no challenge"); + ic->ic_stats.is_rx_bad_auth++; + estatus = IEEE80211_STATUS_CHALLENGE; + goto bad; + } + if (challenge[1] != IEEE80211_CHALLENGE_LEN) { + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, + ni->ni_macaddr, "shared key auth", + "bad challenge len %d", challenge[1]); + ic->ic_stats.is_rx_bad_auth++; + estatus = IEEE80211_STATUS_CHALLENGE; + goto bad; + } + default: + break; + } + switch (ic->ic_opmode) { + case IEEE80211_M_MONITOR: + case IEEE80211_M_AHDEMO: + case IEEE80211_M_IBSS: + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, + ni->ni_macaddr, "shared key auth", + "bad operating mode %u", ic->ic_opmode); + return; + case IEEE80211_M_HOSTAP: + if (ic->ic_state != IEEE80211_S_RUN) { + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, + ni->ni_macaddr, "shared key auth", + "bad state %u", ic->ic_state); + estatus = IEEE80211_STATUS_ALG; /* XXX */ + goto bad; + } + switch (seq) { + case IEEE80211_AUTH_SHARED_REQUEST: + if (ni == ic->ic_bss) { + ni = ieee80211_dup_bss(&ic->ic_sta, wh->i_addr2); + if (ni == NULL) { + /* NB: no way to return an error */ + return; + } + allocbs = 1; + } else { + if ((ni->ni_flags & IEEE80211_NODE_AREF) == 0) + (void) ieee80211_ref_node(ni); + allocbs = 0; + } + /* + * 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; + ni->ni_rssi = rssi; + ni->ni_rstamp = rstamp; + if (!alloc_challenge(ic, ni)) { + /* NB: don't return error so they rexmit */ + return; + } + get_random_bytes(ni->ni_challenge, + IEEE80211_CHALLENGE_LEN); + IEEE80211_DPRINTF(ic, + IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, + "[%6D] shared key %sauth request\n", + ni->ni_macaddr, ":", + allocbs ? "" : "re"); + break; + case IEEE80211_AUTH_SHARED_RESPONSE: + if (ni == ic->ic_bss) { + IEEE80211_DISCARD_MAC(ic, 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(ic, IEEE80211_MSG_AUTH, + ni->ni_macaddr, "shared key response", + "%s", "no challenge recorded"); + ic->ic_stats.is_rx_bad_auth++; + estatus = IEEE80211_STATUS_CHALLENGE; + goto bad; + } + if (memcmp(ni->ni_challenge, &challenge[2], + challenge[1]) != 0) { + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, + ni->ni_macaddr, "shared key response", + "%s", "challenge mismatch"); + ic->ic_stats.is_rx_auth_fail++; + estatus = IEEE80211_STATUS_CHALLENGE; + goto bad; + } + IEEE80211_DPRINTF(ic, + IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, + "[%6D] station authenticated (shared key)\n", + ni->ni_macaddr, ":"); + ieee80211_node_authorize(ni); + break; + default: + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, + ni->ni_macaddr, "shared key auth", + "bad seq %d", seq); + ic->ic_stats.is_rx_bad_auth++; + estatus = IEEE80211_STATUS_SEQUENCE; + goto bad; + } + IEEE80211_SEND_MGMT(ic, ni, + IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); + break; + + case IEEE80211_M_STA: + if (ic->ic_state != IEEE80211_S_AUTH) + return; + switch (seq) { + case IEEE80211_AUTH_SHARED_PASS: + if (ni->ni_challenge != NULL) { + FREE(ni->ni_challenge, M_DEVBUF); + ni->ni_challenge = NULL; + } + if (status != 0) { + IEEE80211_DPRINTF(ic, + IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, + "[%6D] shared key auth failed (reason %d)\n", + ieee80211_getbssid(ic, wh), ":", status); + /* XXX can this happen? */ + if (ni != ic->ic_bss) + ni->ni_fails++; + ic->ic_stats.is_rx_auth_fail++; + return; + } + ieee80211_new_state(ic, IEEE80211_S_ASSOC, + wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK); + break; + case IEEE80211_AUTH_SHARED_CHALLENGE: + if (!alloc_challenge(ic, ni)) + return; + /* XXX could optimize by passing recvd challenge */ + memcpy(ni->ni_challenge, &challenge[2], challenge[1]); + IEEE80211_SEND_MGMT(ic, ni, + IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); + break; + default: + IEEE80211_DISCARD(ic, IEEE80211_MSG_AUTH, + wh, "shared key auth", "bad seq %d", seq); + ic->ic_stats.is_rx_bad_auth++; + return; + } + break; + } + return; +bad: + /* + * Send an error response; but only when operating as an AP. + */ + if (ic->ic_opmode == IEEE80211_M_HOSTAP) { + /* XXX hack to workaround calling convention */ + ieee80211_send_error(ic, ni, wh->i_addr2, + IEEE80211_FC0_SUBTYPE_AUTH, + (seq + 1) | (estatus<<16)); + } else if (ic->ic_opmode == IEEE80211_M_STA) { + /* + * Kick the state machine. This short-circuits + * using the mgt frame timeout to trigger the + * state transition. + */ + if (ic->ic_state == IEEE80211_S_AUTH) + ieee80211_new_state(ic, IEEE80211_S_SCAN, 0); + } } /* Verify the existence and length of __elem or get out. */ -#define IEEE80211_VERIFY_ELEMENT(__elem, __maxlen) do { \ +#define IEEE80211_VERIFY_ELEMENT(__elem, __maxlen) do { \ if ((__elem) == NULL) { \ - IEEE80211_DPRINTF(("%s: no " #__elem "in %s frame\n", \ - __func__, ieee80211_mgt_subtype_name[subtype >> \ - IEEE80211_FC0_SUBTYPE_SHIFT])); \ + IEEE80211_DISCARD(ic, IEEE80211_MSG_ELEMID, \ + wh, ieee80211_mgt_subtype_name[subtype >> \ + IEEE80211_FC0_SUBTYPE_SHIFT], \ + "%s", "no " #__elem ); \ ic->ic_stats.is_rx_elem_missing++; \ return; \ } \ if ((__elem)[1] > (__maxlen)) { \ - IEEE80211_DPRINTF(("%s: bad " #__elem " len %d in %s " \ - "frame from %6D\n", __func__, (__elem)[1], \ - ieee80211_mgt_subtype_name[subtype >> \ - IEEE80211_FC0_SUBTYPE_SHIFT], \ - wh->i_addr2, ":")); \ + IEEE80211_DISCARD(ic, IEEE80211_MSG_ELEMID, \ + wh, ieee80211_mgt_subtype_name[subtype >> \ + IEEE80211_FC0_SUBTYPE_SHIFT], \ + "bad " #__elem " len %d", (__elem)[1]); \ ic->ic_stats.is_rx_elem_toobig++; \ return; \ } \ @@ -531,26 +1248,542 @@ ieee80211_setup_rates(struct ieee80211co #define IEEE80211_VERIFY_LENGTH(_len, _minlen) do { \ if ((_len) < (_minlen)) { \ - IEEE80211_DPRINTF(("%s: %s frame too short from %6D\n", \ - __func__, \ - ieee80211_mgt_subtype_name[subtype >> \ - IEEE80211_FC0_SUBTYPE_SHIFT], \ - wh->i_addr2, ":")); \ + IEEE80211_DISCARD(ic, IEEE80211_MSG_ELEMID, \ + wh, ieee80211_mgt_subtype_name[subtype >> \ + IEEE80211_FC0_SUBTYPE_SHIFT], \ + "%s", "ie too short"); \ ic->ic_stats.is_rx_elem_toosmall++; \ return; \ } \ } while (0) +#ifdef IEEE80211_DEBUG +static void +ieee80211_ssid_mismatch(struct ieee80211com *ic, const char *tag, + uint8_t mac[IEEE80211_ADDR_LEN], uint8_t *ssid) +{ + printf("[%6D] discard %s frame, ssid mismatch: ", mac, ":", tag); + ieee80211_print_essid(ssid + 2, ssid[1]); + printf("\n"); +} + +#define IEEE80211_VERIFY_SSID(_ni, _ssid) do { \ + if ((_ssid)[1] != 0 && \ + ((_ssid)[1] != (_ni)->ni_esslen || \ + memcmp((_ssid) + 2, (_ni)->ni_essid, (_ssid)[1]) != 0)) { \ + if (ieee80211_msg_input(ic)) \ + ieee80211_ssid_mismatch(ic, \ + ieee80211_mgt_subtype_name[subtype >> \ + IEEE80211_FC0_SUBTYPE_SHIFT], \ + wh->i_addr2, _ssid); \ + ic->ic_stats.is_rx_ssidmismatch++; \ + return; \ + } \ +} while (0) +#else /* !IEEE80211_DEBUG */ +#define IEEE80211_VERIFY_SSID(_ni, _ssid) do { \ + if ((_ssid)[1] != 0 && \ + ((_ssid)[1] != (_ni)->ni_esslen || \ + memcmp((_ssid) + 2, (_ni)->ni_essid, (_ssid)[1]) != 0)) { \ + ic->ic_stats.is_rx_ssidmismatch++; \ + return; \ + } \ +} while (0) +#endif /* !IEEE80211_DEBUG */ + +/* unalligned little endian access */ +#define LE_READ_2(p) \ + ((uint16_t) \ + ((((const uint8_t *)(p))[0] ) | \ + (((const uint8_t *)(p))[1] << 8))) +#define LE_READ_4(p) \ + ((uint32_t) \ + ((((const uint8_t *)(p))[0] ) | \ + (((const uint8_t *)(p))[1] << 8) | \ + (((const uint8_t *)(p))[2] << 16) | \ + (((const uint8_t *)(p))[3] << 24))) + +static int __inline +iswpaoui(const uint8_t *frm) +{ + return frm[1] > 3 && LE_READ_4(frm+2) == ((WPA_OUI_TYPE<<24)|WPA_OUI); +} + +static int __inline +iswmeoui(const uint8_t *frm) +{ + return frm[1] > 3 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI); +} + +static int __inline +iswmeparam(const uint8_t *frm) +{ + return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) && + frm[6] == WME_PARAM_OUI_SUBTYPE; +} + +static int __inline +iswmeinfo(const uint8_t *frm) +{ + return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) && + frm[6] == WME_INFO_OUI_SUBTYPE; +} + +static int __inline +isatherosoui(const uint8_t *frm) +{ + return frm[1] > 3 && LE_READ_4(frm+2) == ((ATH_OUI_TYPE<<24)|ATH_OUI); +} + +/* + * Convert a WPA cipher selector OUI to an internal + * cipher algorithm. Where appropriate we also + * record any key length. + */ +static int +wpa_cipher(uint8_t *sel, uint8_t *keylen) +{ +#define WPA_SEL(x) (((x)<<24)|WPA_OUI) + uint32_t w = LE_READ_4(sel); + + switch (w) { + case WPA_SEL(WPA_CSE_NULL): + return IEEE80211_CIPHER_NONE; + case WPA_SEL(WPA_CSE_WEP40): + if (keylen) + *keylen = 40 / NBBY; + return IEEE80211_CIPHER_WEP; + case WPA_SEL(WPA_CSE_WEP104): + if (keylen) + *keylen = 104 / NBBY; + return IEEE80211_CIPHER_WEP; + case WPA_SEL(WPA_CSE_TKIP): + return IEEE80211_CIPHER_TKIP; + case WPA_SEL(WPA_CSE_CCMP): + return IEEE80211_CIPHER_AES_CCM; + } + return 32; /* NB: so 1<< is discarded */ +#undef WPA_SEL +} + +/* + * Convert a WPA key management/authentication algorithm + * to an internal code. + */ +static int +wpa_keymgmt(uint8_t *sel) +{ +#define WPA_SEL(x) (((x)<<24)|WPA_OUI) + uint32_t w = LE_READ_4(sel); + + switch (w) { + case WPA_SEL(WPA_ASE_8021X_UNSPEC): + return WPA_ASE_8021X_UNSPEC; + case WPA_SEL(WPA_ASE_8021X_PSK): + return WPA_ASE_8021X_PSK; + case WPA_SEL(WPA_ASE_NONE): + return WPA_ASE_NONE; + } + return 0; /* NB: so is discarded */ +#undef WPA_SEL +} + +/* + * Parse a WPA information element to collect parameters + * and validate the parameters against what has been + * configured for the system. + */ +static int +ieee80211_parse_wpa(struct ieee80211com *ic, uint8_t *frm, + struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh) +{ + uint8_t len = frm[1]; + uint32_t w; + int n; + + /* + * Check the length once for fixed parts: OUI, type, + * version, mcast cipher, and 2 selector counts. + * Other, variable-length data, must be checked separately. + */ + if ((ic->ic_flags & IEEE80211_F_WPA1) == 0) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "WPA", "not WPA, flags 0x%x", ic->ic_flags); + return IEEE80211_REASON_IE_INVALID; + } + if (len < 14) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "WPA", "too short, len %u", len); + return IEEE80211_REASON_IE_INVALID; + } + frm += 6, len -= 4; /* NB: len is payload only */ + /* NB: iswapoui already validated the OUI and type */ + w = LE_READ_2(frm); + if (w != WPA_VERSION) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "WPA", "bad version %u", w); + return IEEE80211_REASON_IE_INVALID; + } + frm += 2, len -= 2; + + /* multicast/group cipher */ + w = wpa_cipher(frm, &rsn->rsn_mcastkeylen); + if (w != rsn->rsn_mcastcipher) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "WPA", "mcast cipher mismatch; got %u, expected %u", + w, rsn->rsn_mcastcipher); + return IEEE80211_REASON_IE_INVALID; + } + frm += 4, len -= 4; + + /* unicast ciphers */ + n = LE_READ_2(frm); + frm += 2, len -= 2; + if (len < n*4+2) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "WPA", "ucast cipher data too short; len %u, n %u", + len, n); + return IEEE80211_REASON_IE_INVALID; + } + w = 0; + for (; n > 0; n--) { + w |= 1<rsn_ucastkeylen); + frm += 4, len -= 4; + } + w &= rsn->rsn_ucastcipherset; + if (w == 0) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "WPA", "%s", "ucast cipher set empty"); + return IEEE80211_REASON_IE_INVALID; + } + if (w & (1<rsn_ucastcipher = IEEE80211_CIPHER_TKIP; + else + rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; + + /* key management algorithms */ + n = LE_READ_2(frm); + frm += 2, len -= 2; + if (len < n*4) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "WPA", "key mgmt alg data too short; len %u, n %u", + len, n); + return IEEE80211_REASON_IE_INVALID; + } + w = 0; + for (; n > 0; n--) { + w |= wpa_keymgmt(frm); + frm += 4, len -= 4; + } + w &= rsn->rsn_keymgmtset; + if (w == 0) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "WPA", "%s", "no acceptable key mgmt alg"); + return IEEE80211_REASON_IE_INVALID; + } + if (w & WPA_ASE_8021X_UNSPEC) + rsn->rsn_keymgmt = WPA_ASE_8021X_UNSPEC; + else + rsn->rsn_keymgmt = WPA_ASE_8021X_PSK; + + if (len > 2) /* optional capabilities */ + rsn->rsn_caps = LE_READ_2(frm); + + return 0; +} + +/* + * Convert an RSN cipher selector OUI to an internal + * cipher algorithm. Where appropriate we also + * record any key length. + */ +static int +rsn_cipher(uint8_t *sel, uint8_t *keylen) +{ +#define RSN_SEL(x) (((x)<<24)|RSN_OUI) + uint32_t w = LE_READ_4(sel); + + switch (w) { + case RSN_SEL(RSN_CSE_NULL): + return IEEE80211_CIPHER_NONE; + case RSN_SEL(RSN_CSE_WEP40): + if (keylen) + *keylen = 40 / NBBY; + return IEEE80211_CIPHER_WEP; + case RSN_SEL(RSN_CSE_WEP104): + if (keylen) + *keylen = 104 / NBBY; + return IEEE80211_CIPHER_WEP; + case RSN_SEL(RSN_CSE_TKIP): + return IEEE80211_CIPHER_TKIP; + case RSN_SEL(RSN_CSE_CCMP): + return IEEE80211_CIPHER_AES_CCM; + case RSN_SEL(RSN_CSE_WRAP): + return IEEE80211_CIPHER_AES_OCB; + } + return 32; /* NB: so 1<< is discarded */ +#undef WPA_SEL +} + +/* + * Convert an RSN key management/authentication algorithm + * to an internal code. + */ +static int +rsn_keymgmt(uint8_t *sel) +{ +#define RSN_SEL(x) (((x)<<24)|RSN_OUI) + uint32_t w = LE_READ_4(sel); + + switch (w) { + case RSN_SEL(RSN_ASE_8021X_UNSPEC): + return RSN_ASE_8021X_UNSPEC; + case RSN_SEL(RSN_ASE_8021X_PSK): + return RSN_ASE_8021X_PSK; + case RSN_SEL(RSN_ASE_NONE): + return RSN_ASE_NONE; + } + return 0; /* NB: so is discarded */ +#undef RSN_SEL +} + +/* + * Parse a WPA/RSN information element to collect parameters + * and validate the parameters against what has been + * configured for the system. + */ +static int +ieee80211_parse_rsn(struct ieee80211com *ic, uint8_t *frm, + struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh) +{ + uint8_t len = frm[1]; + uint32_t w; + int n; + + /* + * Check the length once for fixed parts: + * version, mcast cipher, and 2 selector counts. + * Other, variable-length data, must be checked separately. + */ + if ((ic->ic_flags & IEEE80211_F_WPA2) == 0) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "WPA", "not RSN, flags 0x%x", ic->ic_flags); + return IEEE80211_REASON_IE_INVALID; + } + if (len < 10) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "RSN", "too short, len %u", len); + return IEEE80211_REASON_IE_INVALID; + } + frm += 2; + w = LE_READ_2(frm); + if (w != RSN_VERSION) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "RSN", "bad version %u", w); + return IEEE80211_REASON_IE_INVALID; + } + frm += 2, len -= 2; + + /* multicast/group cipher */ + w = rsn_cipher(frm, &rsn->rsn_mcastkeylen); + if (w != rsn->rsn_mcastcipher) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "RSN", "mcast cipher mismatch; got %u, expected %u", + w, rsn->rsn_mcastcipher); + return IEEE80211_REASON_IE_INVALID; + } + frm += 4, len -= 4; + + /* unicast ciphers */ + n = LE_READ_2(frm); + frm += 2, len -= 2; + if (len < n*4+2) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "RSN", "ucast cipher data too short; len %u, n %u", + len, n); + return IEEE80211_REASON_IE_INVALID; + } + w = 0; + for (; n > 0; n--) { + w |= 1<rsn_ucastkeylen); + frm += 4, len -= 4; + } + w &= rsn->rsn_ucastcipherset; + if (w == 0) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "RSN", "%s", "ucast cipher set empty"); + return IEEE80211_REASON_IE_INVALID; + } + if (w & (1<rsn_ucastcipher = IEEE80211_CIPHER_TKIP; + else + rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; + + /* key management algorithms */ + n = LE_READ_2(frm); + frm += 2, len -= 2; + if (len < n*4) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "RSN", "key mgmt alg data too short; len %u, n %u", + len, n); + return IEEE80211_REASON_IE_INVALID; + } + w = 0; + for (; n > 0; n--) { + w |= rsn_keymgmt(frm); + frm += 4, len -= 4; + } + w &= rsn->rsn_keymgmtset; + if (w == 0) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, + wh, "RSN", "%s", "no acceptable key mgmt alg"); + return IEEE80211_REASON_IE_INVALID; + } + if (w & RSN_ASE_8021X_UNSPEC) + rsn->rsn_keymgmt = RSN_ASE_8021X_UNSPEC; + else + rsn->rsn_keymgmt = RSN_ASE_8021X_PSK; + + /* optional RSN capabilities */ + if (len > 2) + rsn->rsn_caps = LE_READ_2(frm); + /* XXXPMKID */ + + return 0; +} + +static int +ieee80211_parse_wmeparams(struct ieee80211com *ic, uint8_t *frm, + const struct ieee80211_frame *wh) +{ +#define MS(_v, _f) (((_v) & _f) >> _f##_S) + struct ieee80211_wme_state *wme = &ic->ic_wme; + u_int len = frm[1], qosinfo; + int i; + + if (len < sizeof(struct ieee80211_wme_param)-2) { + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME, + wh, "WME", "too short, len %u", len); + return -1; + } + qosinfo = frm[__offsetof(struct ieee80211_wme_param, param_qosInfo)]; + qosinfo &= WME_QOSINFO_COUNT; + /* XXX do proper check for wraparound */ + if (qosinfo == wme->wme_wmeChanParams.cap_info) + return 0; + frm += __offsetof(struct ieee80211_wme_param, params_acParams); + for (i = 0; i < WME_NUM_AC; i++) { + struct wmeParams *wmep = + &wme->wme_wmeChanParams.cap_wmeParams[i]; + /* NB: ACI not used */ + wmep->wmep_acm = MS(frm[0], WME_PARAM_ACM); + wmep->wmep_aifsn = MS(frm[0], WME_PARAM_AIFSN); + wmep->wmep_logcwmin = MS(frm[1], WME_PARAM_LOGCWMIN); + wmep->wmep_logcwmax = MS(frm[1], WME_PARAM_LOGCWMAX); + wmep->wmep_txopLimit = LE_READ_2(frm+2); + frm += 4; + } + wme->wme_wmeChanParams.cap_info = qosinfo; + return 1; +#undef MS +} + +void +ieee80211_saveie(uint8_t **iep, const uint8_t *ie) +{ + u_int ielen = ie[1]+2; + /* + * Record information element for later use. + */ + if (*iep == NULL || (*iep)[1] != ie[1]) { + if (*iep != NULL) + FREE(*iep, M_DEVBUF); + MALLOC(*iep, void*, ielen, M_DEVBUF, M_NOWAIT); + } + if (*iep != NULL) + memcpy(*iep, ie, ielen); + /* XXX note failure */ +} + +/* 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 ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct mbuf *m; + struct l2_update_frame *l2uf; + struct ether_header *eh; + + m = m_gethdr(M_NOWAIT, MT_DATA); + if (m == NULL) { + IEEE80211_NOTE(ic, IEEE80211_MSG_ASSOC, ni, + "%s", "no mbuf for l2uf frame"); + ic->ic_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); + m->m_pkthdr.rcvif = ifp; + ieee80211_deliver_data(ic, ni, m); +} + void ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, struct ieee80211_node *ni, int subtype, int rssi, uint32_t rstamp) { - struct ifnet *ifp = &ic->ic_if; +#define ISPROBE(_st) ((_st) == IEEE80211_FC0_SUBTYPE_PROBE_RESP) +#define ISREASSOC(_st) ((_st) == IEEE80211_FC0_SUBTYPE_REASSOC_RESP) struct ieee80211_frame *wh; uint8_t *frm, *efrm; - uint8_t *ssid, *rates, *xrates; - int reassoc, resp, newassoc, allocbs; + uint8_t *ssid, *rates, *xrates, *wpa, *wme; + int reassoc, resp, allocbs; + uint8_t rate; wh = mtod(m0, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; @@ -558,18 +1791,22 @@ ieee80211_recv_mgmt(struct ieee80211com switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: case IEEE80211_FC0_SUBTYPE_BEACON: { - uint8_t *tstamp, *bintval, *capinfo, *country; - uint8_t chan, bchan, fhindex, erp; - uint16_t fhdwell; - int isprobe; + struct ieee80211_scanparams scan; - if (ic->ic_opmode != IEEE80211_M_IBSS && - ic->ic_state != IEEE80211_S_SCAN) { - /* XXX: may be useful for background scan */ + /* + * We process beacon/probe response frames: + * o when scanning, or + * o station mode when associated (to collect state + * updates such as 802.11g slot time), or + * o adhoc mode (to discover neighbors) + * Frames otherwise received are discarded. + */ + if (!((ic->ic_flags & IEEE80211_F_SCAN) || + (ic->ic_opmode == IEEE80211_M_STA && ni->ni_associd) || + ic->ic_opmode == IEEE80211_M_IBSS)) { + ic->ic_stats.is_rx_mgtdiscard++; return; } - isprobe = (subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP); - /* * beacon/probe response frame format * [8] time stamp @@ -581,33 +1818,34 @@ ieee80211_recv_mgmt(struct ieee80211com * [tlv] parameter set (FH/DS) * [tlv] erp information * [tlv] extended supported rates + * [tlv] WME + * [tlv] WPA or RSN */ IEEE80211_VERIFY_LENGTH(efrm - frm, 12); - tstamp = frm; frm += 8; - bintval = frm; frm += 2; - capinfo = frm; frm += 2; - ssid = rates = xrates = country = NULL; - bchan = ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan); - chan = bchan; - fhdwell = 0; - fhindex = 0; - erp = 0; + 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, ic->ic_curchan); + scan.chan = scan.bchan; + while (frm < efrm) { + IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1]); switch (*frm) { case IEEE80211_ELEMID_SSID: - ssid = frm; + scan.ssid = frm; break; case IEEE80211_ELEMID_RATES: - rates = frm; + scan.rates = frm; break; case IEEE80211_ELEMID_COUNTRY: - country = frm; + scan.country = frm; break; case IEEE80211_ELEMID_FHPARMS: if (ic->ic_phytype == IEEE80211_T_FH) { - fhdwell = (frm[3] << 8) | frm[2]; - chan = IEEE80211_FH_CHAN(frm[4], frm[5]); - fhindex = frm[6]; + scan.fhdwell = LE_READ_2(&frm[2]); + scan.chan = IEEE80211_FH_CHAN(frm[4], frm[5]); + scan.fhindex = frm[6]; } break; case IEEE80211_ELEMID_DSPARMS: @@ -616,48 +1854,64 @@ ieee80211_recv_mgmt(struct ieee80211com * is problematic for multi-mode devices. */ if (ic->ic_phytype != IEEE80211_T_FH) - chan = frm[2]; + scan.chan = frm[2]; break; case IEEE80211_ELEMID_TIM: + /* XXX ATIM? */ + scan.tim = frm; + scan.timoff = frm - mtod(m0, uint8_t *); break; case IEEE80211_ELEMID_IBSSPARMS: break; case IEEE80211_ELEMID_XRATES: - xrates = frm; + scan.xrates = frm; break; case IEEE80211_ELEMID_ERP: if (frm[1] != 1) { - IEEE80211_DPRINTF(("%s: invalid ERP " - "element; length %u, expecting " - "1\n", __func__, frm[1])); + IEEE80211_DISCARD_IE(ic, + IEEE80211_MSG_ELEMID, wh, "ERP", + "bad len %u", frm[1]); ic->ic_stats.is_rx_elem_toobig++; break; } - erp = frm[2]; + scan.erp = frm[2]; + break; + case IEEE80211_ELEMID_RSN: + scan.wpa = frm; + break; + case IEEE80211_ELEMID_VENDOR: + if (iswpaoui(frm)) + scan.wpa = frm; + else if (iswmeparam(frm) || iswmeinfo(frm)) + scan.wme = frm; + /* XXX Atheros OUI support */ break; default: - IEEE80211_DPRINTF2(("%s: element id %u/len %u " - "ignored\n", __func__, *frm, frm[1])); + IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID, + wh, "unhandled", + "id %u, len %u", *frm, frm[1]); ic->ic_stats.is_rx_elem_unknown++; break; } frm += frm[1] + 2; } - IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE); - IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN); + IEEE80211_VERIFY_ELEMENT(scan.rates, IEEE80211_RATE_MAXSIZE); + IEEE80211_VERIFY_ELEMENT(scan.ssid, IEEE80211_NWID_LEN); if ( #if IEEE80211_CHAN_MAX < 255 - chan > IEEE80211_CHAN_MAX || + scan.chan > IEEE80211_CHAN_MAX || #endif - isclr(ic->ic_chan_active, chan)) { - IEEE80211_DPRINTF(("%s: ignore %s with invalid channel " - "%u\n", __func__, - isprobe ? "probe response" : "beacon", - chan)); + isclr(ic->ic_chan_active, scan.chan)) { + IEEE80211_DISCARD(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, + wh, ieee80211_mgt_subtype_name[subtype >> + IEEE80211_FC0_SUBTYPE_SHIFT], + "invalid channel %u", scan.chan); ic->ic_stats.is_rx_badchan++; return; } - if (chan != bchan && ic->ic_phytype != IEEE80211_T_FH) { + 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; @@ -668,108 +1922,151 @@ ieee80211_recv_mgmt(struct ieee80211com * the rssi value should be correct even for * different hop pattern in FH. */ - IEEE80211_DPRINTF(("%s: ignore %s on channel %u marked " - "for channel %u\n", __func__, - isprobe ? "probe response" : "beacon", - bchan, chan)); + IEEE80211_DISCARD(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, + wh, ieee80211_mgt_subtype_name[subtype >> + IEEE80211_FC0_SUBTYPE_SHIFT], + "for off-channel %u", scan.chan); ic->ic_stats.is_rx_chanmismatch++; return; } + if (!(IEEE80211_BINTVAL_MIN <= scan.bintval && + scan.bintval <= IEEE80211_BINTVAL_MAX)) { + IEEE80211_DISCARD(ic, + IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, + wh, ieee80211_mgt_subtype_name[subtype >> + IEEE80211_FC0_SUBTYPE_SHIFT], + "bogus beacon interval", scan.bintval); + ic->ic_stats.is_rx_badbintval++; + return; + } /* - * Use mac and channel for lookup so we collect all - * potential AP's when scanning. Otherwise we may - * see the same AP on multiple channels and will only - * record the last one. We could filter APs here based - * on rssi, etc. but leave that to the end of the scan - * so we can keep the selection criteria in one spot. - * This may result in a bloat of the scanned AP list but - * it shouldn't be too much. + * Count frame now that we know it's to be processed. */ - ni = ieee80211_lookup_node(ic, wh->i_addr2, - &ic->ic_channels[chan]); -#ifdef IEEE80211_DEBUG - if (ieee80211_debug && - (ni == NULL || ic->ic_state == IEEE80211_S_SCAN)) { - printf("%s: %s%s on chan %u (bss chan %u) ", - __func__, (ni == NULL ? "new " : ""), - isprobe ? "probe response" : "beacon", - chan, bchan); - ieee80211_print_essid(ssid + 2, ssid[1]); - printf(" from %6D\n", wh->i_addr2, ":"); - printf("%s: caps 0x%x bintval %u erp 0x%x\n", - __func__, le16toh(*(uint16_t *)capinfo), - le16toh(*(uint16_t *)bintval), erp); - if (country) - printf("%s: country info %*D\n", - __func__, country[1], country+2, " "); - } -#endif - if (ni == NULL) { - ni = ieee80211_alloc_node(ic, wh->i_addr2); - if (ni == NULL) - return; - ni->ni_esslen = ssid[1]; - memset(ni->ni_essid, 0, sizeof(ni->ni_essid)); - memcpy(ni->ni_essid, ssid + 2, ssid[1]); - allocbs = 1; - } else if (ssid[1] != 0 && isprobe) { - /* - * Update ESSID at probe response to adopt hidden AP by - * Lucent/Cisco, which announces null ESSID in beacon. - */ - ni->ni_esslen = ssid[1]; - memset(ni->ni_essid, 0, sizeof(ni->ni_essid)); - memcpy(ni->ni_essid, ssid + 2, ssid[1]); - allocbs = 0; + if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) { + ic->ic_stats.is_rx_beacon++; /* XXX remove */ + IEEE80211_NODE_STAT(ni, rx_beacons); } else - allocbs = 0; - IEEE80211_ADDR_COPY(ni->ni_bssid, wh->i_addr3); - ni->ni_rssi = rssi; - ni->ni_rstamp = rstamp; - memcpy(ni->ni_tstamp, tstamp, sizeof(ni->ni_tstamp)); - ni->ni_intval = le16toh(*(uint16_t *)bintval); - ni->ni_capinfo = le16toh(*(uint16_t *)capinfo); - /* XXX validate channel # */ - ni->ni_chan = &ic->ic_channels[chan]; - ni->ni_fhdwell = fhdwell; - ni->ni_fhindex = fhindex; - ni->ni_erp = erp; - /* NB: must be after ni_chan is setup */ - ieee80211_setup_rates(ic, ni, rates, xrates, IEEE80211_F_DOSORT); - /* - * When scanning we record results (nodes) with a zero - * refcnt. Otherwise we want to hold the reference for - * ibss neighbors so the nodes don't get released prematurely. - * Anything else can be discarded (XXX and should be handled - * above so we don't do so much work). - */ - if (ic->ic_state == IEEE80211_S_SCAN) - ieee80211_unref_node(&ni); /* NB: do not free */ - else if (ic->ic_opmode == IEEE80211_M_IBSS && - allocbs && isprobe) { - /* - * Fake an association so the driver can setup it's - * private state. The rate set has been setup above; - * there is no handshake as in ap/station operation. - */ - if (ic->ic_newassoc) - (*ic->ic_newassoc)(ic, ni, 1); - /* NB: hold reference */ - } else { - /* XXX optimize to avoid work done above */ - ieee80211_free_node(ic, ni); + IEEE80211_NODE_STAT(ni, rx_proberesp); + + /* + * When operating in station mode, check for state updates. + * Be careful to ignore beacons received while doing a + * background scan. We consider only 11g/WMM stuff right now. + */ + if (ic->ic_opmode == IEEE80211_M_STA && + ni->ni_associd != 0 && + ((ic->ic_flags & IEEE80211_F_SCAN) == 0 || + IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid))) { + /* record tsf of last beacon */ + memcpy(ni->ni_tstamp.data, scan.tstamp, + sizeof(ni->ni_tstamp)); + /* count beacon frame for s/w bmiss handling */ + ic->ic_swbmiss_count++; + ic->ic_bmiss_count = 0; + if (ni->ni_erp != scan.erp) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%6D] erp change: was 0x%x, now 0x%x\n", + wh->i_addr2, ":", ni->ni_erp, scan.erp); + if (ic->ic_curmode == IEEE80211_MODE_11G && + (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) + ic->ic_flags |= IEEE80211_F_USEPROT; + else + ic->ic_flags &= ~IEEE80211_F_USEPROT; + ni->ni_erp = scan.erp; + /* XXX statistic */ + } + if ((ni->ni_capinfo ^ scan.capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%6D] capabilities change: before 0x%x," + " now 0x%x\n", + wh->i_addr2, ":", + ni->ni_capinfo, scan.capinfo); + /* + * NB: we assume short preamble doesn't + * change dynamically + */ + ieee80211_set_shortslottime(ic, + ic->ic_curmode == IEEE80211_MODE_11A || + (scan.capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); + ni->ni_capinfo = scan.capinfo; + /* XXX statistic */ + } + if (scan.wme != NULL && + (ni->ni_flags & IEEE80211_NODE_QOS) && + ieee80211_parse_wmeparams(ic, scan.wme, wh) > 0) + ieee80211_wme_updateparams(ic); + if (scan.tim != NULL) { + struct ieee80211_tim_ie *ie = + (struct ieee80211_tim_ie *) scan.tim; + + ni->ni_dtim_count = ie->tim_count; + ni->ni_dtim_period = ie->tim_period; + } + if (ic->ic_flags & IEEE80211_F_SCAN) + ieee80211_add_scan(ic, &scan, wh, + subtype, rssi, rstamp); + return; + } + /* + * If scanning, just pass information to the scan module. + */ + if (ic->ic_flags & IEEE80211_F_SCAN) { + if (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN) { + /* + * Actively scanning a channel marked passive; + * send a probe request now that we know there + * is 802.11 traffic present. + * + * XXX check if the beacon we recv'd gives + * us what we need and suppress the probe req + */ + ieee80211_probe_curchan(ic, 1); + ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN; + } + ieee80211_add_scan(ic, &scan, wh, + subtype, rssi, rstamp); + return; + } + if (scan.capinfo & IEEE80211_CAPINFO_IBSS) { + if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) { + /* + * Create a new entry in the neighbor table. + */ + ni = ieee80211_add_neighbor(ic, wh, &scan); + } else if (ni->ni_capinfo == 0) { + /* + * Update faked node created on transmit. + * Note this also updates the tsf. + */ + ieee80211_init_neighbor(ni, wh, &scan); + } else { + /* + * Record tsf for potential resync. + */ + memcpy(ni->ni_tstamp.data, scan.tstamp, + sizeof(ni->ni_tstamp)); + } + if (ni != NULL) { + ni->ni_rssi = rssi; + ni->ni_rstamp = rstamp; + } } break; } - case IEEE80211_FC0_SUBTYPE_PROBE_REQ: { - uint8_t rate; - - if (ic->ic_opmode == IEEE80211_M_STA) + case IEEE80211_FC0_SUBTYPE_PROBE_REQ: + if (ic->ic_opmode == IEEE80211_M_STA || + ic->ic_state != IEEE80211_S_RUN) { + ic->ic_stats.is_rx_mgtdiscard++; return; - if (ic->ic_state != IEEE80211_S_RUN) + } + if (IEEE80211_IS_MULTICAST(wh->i_addr2)) { + /* frame must be directed */ + ic->ic_stats.is_rx_mgtdiscard++; /* XXX stat */ return; + } /* * prreq frame format @@ -779,6 +2076,7 @@ ieee80211_recv_mgmt(struct ieee80211com */ ssid = rates = xrates = NULL; while (frm < efrm) { + IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1]); switch (*frm) { case IEEE80211_ELEMID_SSID: ssid = frm; @@ -794,45 +2092,58 @@ ieee80211_recv_mgmt(struct ieee80211com } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE); IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN); - if (ssid[1] != 0 && - (ssid[1] != ic->ic_bss->ni_esslen || - memcmp(ssid + 2, ic->ic_bss->ni_essid, ic->ic_bss->ni_esslen) != 0)) { -#ifdef IEEE80211_DEBUG - if (ieee80211_debug) { - printf("%s: ssid unmatch ", __func__); - ieee80211_print_essid(ssid + 2, ssid[1]); - printf(" from %6D\n", wh->i_addr2, ":"); - } -#endif - ic->ic_stats.is_rx_ssidmismatch++; + IEEE80211_VERIFY_SSID(ic->ic_bss, ssid); + if ((ic->ic_flags & IEEE80211_F_HIDESSID) && ssid[1] == 0) { + IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, + wh, ieee80211_mgt_subtype_name[subtype >> + IEEE80211_FC0_SUBTYPE_SHIFT], + "%s", "no ssid with ssid suppression enabled"); + ic->ic_stats.is_rx_ssidmismatch++; /*XXX*/ return; } + allocbs = 0; if (ni == ic->ic_bss) { - ni = ieee80211_dup_bss(ic, wh->i_addr2); + if (ic->ic_opmode != IEEE80211_M_IBSS) { + ni = ieee80211_tmp_node(ic, wh->i_addr2); + allocbs = 1; + } else if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) { + /* + * XXX Cannot tell if the sender is operating + * in ibss mode. But we need a new node to + * send the response so blindly add them to the + * neighbor table. + */ + ni = ieee80211_fakeup_adhoc_node(&ic->ic_sta, + wh->i_addr2); + } if (ni == NULL) return; - IEEE80211_DPRINTF(("%s: new req from %6D\n", - __func__, wh->i_addr2, ":")); - allocbs = 1; - } else - allocbs = 0; + } + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%6D] recv probe req\n", wh->i_addr2, ":"); ni->ni_rssi = rssi; ni->ni_rstamp = rstamp; - rate = ieee80211_setup_rates(ic, ni, rates, xrates, - IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE - | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); + rate = ieee80211_setup_rates(ni, rates, xrates, + IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE + | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); if (rate & IEEE80211_RATE_BASIC) { - IEEE80211_DPRINTF(("%s: rate negotiation failed: %6D\n", - __func__, wh->i_addr2, ":")); + IEEE80211_DISCARD(ic, IEEE80211_MSG_XRATE, + wh, ieee80211_mgt_subtype_name[subtype >> + IEEE80211_FC0_SUBTYPE_SHIFT], + "%s", "recv'd rate set invalid"); } else { IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_PROBE_RESP, 0); } - if (allocbs) - ieee80211_free_node(ic, ni); + if (allocbs) { + /* + * Temporary node created just to send a + * response, reclaim immediately. + */ + ieee80211_free_node(ni); + } break; - } case IEEE80211_FC0_SUBTYPE_AUTH: { uint16_t algo, seq, status; @@ -847,94 +2158,68 @@ ieee80211_recv_mgmt(struct ieee80211com algo = le16toh(*(uint16_t *)frm); seq = le16toh(*(uint16_t *)(frm + 2)); status = le16toh(*(uint16_t *)(frm + 4)); - if (algo != IEEE80211_AUTH_ALG_OPEN) { - /* TODO: shared key auth */ - IEEE80211_DPRINTF(("%s: unsupported auth %d from %6D\n", - __func__, algo, wh->i_addr2, ":")); - ic->ic_stats.is_rx_auth_unsupported++; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_AUTH, + "[%6D] recv auth frame with algorithm %d seq %d\n", + wh->i_addr2, ":", algo, seq); + /* + * Consult the ACL policy module if setup. + */ + if (ic->ic_acl != NULL && + !ic->ic_acl->iac_check(ic, wh->i_addr2)) { + IEEE80211_DISCARD(ic, IEEE80211_MSG_ACL, + wh, "auth", "%s", "disallowed by ACL"); + ic->ic_stats.is_rx_acl++; + if (ic->ic_opmode == IEEE80211_M_HOSTAP) { + IEEE80211_SEND_MGMT(ic, ni, + IEEE80211_FC0_SUBTYPE_AUTH, + (seq+1) | (IEEE80211_STATUS_UNSPECIFIED<<16)); + } return; } - switch (ic->ic_opmode) { - case IEEE80211_M_IBSS: - if (ic->ic_state != IEEE80211_S_RUN || seq != 1) { - IEEE80211_DPRINTF(("%s: discard auth from %6D; " - "state %u, seq %u\n", __func__, - wh->i_addr2, ":", - ic->ic_state, seq)); - ic->ic_stats.is_rx_bad_auth++; - break; - } - ieee80211_new_state(ic, IEEE80211_S_AUTH, - wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK); - break; - - case IEEE80211_M_AHDEMO: - /* should not come here */ - break; - - case IEEE80211_M_HOSTAP: - if (ic->ic_state != IEEE80211_S_RUN || seq != 1) { - IEEE80211_DPRINTF(("%s: discard auth from %6D; " - "state %u, seq %u\n", __func__, - wh->i_addr2, ":", - ic->ic_state, seq)); - ic->ic_stats.is_rx_bad_auth++; - break; - } - if (ni == ic->ic_bss) { - ni = ieee80211_alloc_node(ic, wh->i_addr2); - if (ni == NULL) - return; - IEEE80211_ADDR_COPY(ni->ni_bssid, ic->ic_bss->ni_bssid); - ni->ni_rssi = rssi; - ni->ni_rstamp = rstamp; - ni->ni_chan = ic->ic_bss->ni_chan; - allocbs = 1; - } else - allocbs = 0; - IEEE80211_SEND_MGMT(ic, ni, - IEEE80211_FC0_SUBTYPE_AUTH, 2); - if (ifp->if_flags & IFF_DEBUG) - if_printf(ifp, "station %6D %s authenticated\n", - ni->ni_macaddr, ":", - (allocbs ? "newly" : "already")); - break; - - case IEEE80211_M_STA: - if (ic->ic_state != IEEE80211_S_AUTH || seq != 2) { - IEEE80211_DPRINTF(("%s: discard auth from %6D; " - "state %u, seq %u\n", __func__, - wh->i_addr2, ":", - ic->ic_state, seq)); - ic->ic_stats.is_rx_bad_auth++; - break; + if (ic->ic_flags & IEEE80211_F_COUNTERM) { + IEEE80211_DISCARD(ic, + IEEE80211_MSG_AUTH | IEEE80211_MSG_CRYPTO, + wh, "auth", "%s", "TKIP countermeasures enabled"); + ic->ic_stats.is_rx_auth_countermeasures++; + if (ic->ic_opmode == IEEE80211_M_HOSTAP) { + IEEE80211_SEND_MGMT(ic, ni, + IEEE80211_FC0_SUBTYPE_AUTH, + IEEE80211_REASON_MIC_FAILURE); } - if (status != 0) { - if_printf(&ic->ic_if, - "authentication failed (reason %d) for %6D\n", - status, - wh->i_addr3, ":"); - if (ni != ic->ic_bss) - ni->ni_fails++; - ic->ic_stats.is_rx_auth_fail++; - return; + return; + } + if (algo == IEEE80211_AUTH_ALG_SHARED) + ieee80211_auth_shared(ic, wh, frm + 6, efrm, ni, rssi, + rstamp, seq, status); + else if (algo == IEEE80211_AUTH_ALG_OPEN) + ieee80211_auth_open(ic, wh, ni, rssi, rstamp, seq, + status); + else { + IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY, + wh, "auth", "unsupported alg %d", algo); + ic->ic_stats.is_rx_auth_unsupported++; + if (ic->ic_opmode == IEEE80211_M_HOSTAP) { + /* XXX not right */ + IEEE80211_SEND_MGMT(ic, ni, + IEEE80211_FC0_SUBTYPE_AUTH, + (seq+1) | (IEEE80211_STATUS_ALG<<16)); } - ieee80211_new_state(ic, IEEE80211_S_ASSOC, - wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK); - break; - case IEEE80211_M_MONITOR: - break; + return; } break; } case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: { - uint16_t capinfo, bintval; + uint16_t capinfo, lintval; + struct ieee80211_rsnparms rsn; + uint8_t reason; if (ic->ic_opmode != IEEE80211_M_HOSTAP || - (ic->ic_state != IEEE80211_S_RUN)) + ic->ic_state != IEEE80211_S_RUN) { + ic->ic_stats.is_rx_mgtdiscard++; return; + } if (subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) { reassoc = 1; @@ -951,20 +2236,24 @@ ieee80211_recv_mgmt(struct ieee80211com * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates + * [tlv] WPA or RSN */ IEEE80211_VERIFY_LENGTH(efrm - frm, (reassoc ? 10 : 4)); if (!IEEE80211_ADDR_EQ(wh->i_addr3, ic->ic_bss->ni_bssid)) { - IEEE80211_DPRINTF(("%s: ignore other bss from %6D\n", - __func__, wh->i_addr2, ":")); + IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY, + wh, ieee80211_mgt_subtype_name[subtype >> + IEEE80211_FC0_SUBTYPE_SHIFT], + "%s", "wrong bssid"); ic->ic_stats.is_rx_assoc_bss++; return; } capinfo = le16toh(*(uint16_t *)frm); frm += 2; - bintval = le16toh(*(uint16_t *)frm); frm += 2; + lintval = le16toh(*(uint16_t *)frm); frm += 2; if (reassoc) frm += 6; /* ignore current AP info */ - ssid = rates = xrates = NULL; + ssid = rates = xrates = wpa = wme = NULL; while (frm < efrm) { + IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1]); switch (*frm) { case IEEE80211_ELEMID_SSID: ssid = frm; @@ -975,97 +2264,169 @@ ieee80211_recv_mgmt(struct ieee80211com case IEEE80211_ELEMID_XRATES: xrates = frm; break; + /* XXX verify only one of RSN and WPA ie's? */ + case IEEE80211_ELEMID_RSN: + wpa = frm; + break; + case IEEE80211_ELEMID_VENDOR: + if (iswpaoui(frm)) + wpa = frm; + else if (iswmeinfo(frm)) + wme = frm; + /* XXX Atheros OUI support */ + break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE); IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN); - if (ssid[1] != ic->ic_bss->ni_esslen || - memcmp(ssid + 2, ic->ic_bss->ni_essid, ssid[1]) != 0) { -#ifdef IEEE80211_DEBUG - if (ieee80211_debug) { - printf("%s: ssid unmatch ", __func__); - ieee80211_print_essid(ssid + 2, ssid[1]); - printf(" from %6D\n", wh->i_addr2, ":"); - } -#endif - ic->ic_stats.is_rx_ssidmismatch++; - return; - } + IEEE80211_VERIFY_SSID(ic->ic_bss, ssid); + if (ni == ic->ic_bss) { - IEEE80211_DPRINTF(("%s: not authenticated for %6D\n", - __func__, wh->i_addr2, ":")); - ni = ieee80211_dup_bss(ic, wh->i_addr2); - if (ni != NULL) { - IEEE80211_SEND_MGMT(ic, ni, - IEEE80211_FC0_SUBTYPE_DEAUTH, - IEEE80211_REASON_ASSOC_NOT_AUTHED); - ieee80211_free_node(ic, ni); - } + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY, + "[%6D] deny %s request, sta not authenticated\n", + wh->i_addr2, ":", reassoc ? "reassoc" : "assoc"); + ieee80211_send_error(ic, ni, wh->i_addr2, + IEEE80211_FC0_SUBTYPE_DEAUTH, + IEEE80211_REASON_ASSOC_NOT_AUTHED); ic->ic_stats.is_rx_assoc_notauth++; return; } - /* XXX per-node cipher suite */ - /* XXX some stations use the privacy bit for handling APs - that suport both encrypted and unencrypted traffic */ - if ((capinfo & IEEE80211_CAPINFO_ESS) == 0 || - (capinfo & IEEE80211_CAPINFO_PRIVACY) != - ((ic->ic_flags & IEEE80211_F_WEPON) ? - IEEE80211_CAPINFO_PRIVACY : 0)) { - IEEE80211_DPRINTF(("%s: capability mismatch %x for %6D\n", - __func__, capinfo, wh->i_addr2, ":")); - ni->ni_associd = 0; + /* assert right associstion security credentials */ + if (wpa == NULL && (ic->ic_flags & IEEE80211_F_WPA)) { + IEEE80211_DPRINTF(ic, + IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, + "[%6D] no WPA/RSN IE in association request\n", + wh->i_addr2, ":"); + IEEE80211_SEND_MGMT(ic, ni, + IEEE80211_FC0_SUBTYPE_DEAUTH, + IEEE80211_REASON_RSN_REQUIRED); + ieee80211_node_leave(ic, ni); + /* XXX distinguish WPA/RSN? */ + ic->ic_stats.is_rx_assoc_badwpaie++; + return; + } + if (wpa != NULL) { + /* + * Parse WPA information element. Note that + * we initialize the param block from the node + * state so that information in the IE overrides + * our defaults. The resulting parameters are + * installed below after the association is assured. + */ + rsn = ni->ni_rsn; + if (wpa[0] != IEEE80211_ELEMID_RSN) + reason = ieee80211_parse_wpa(ic, wpa, &rsn, wh); + else + reason = ieee80211_parse_rsn(ic, wpa, &rsn, wh); + if (reason != 0) { + IEEE80211_SEND_MGMT(ic, ni, + IEEE80211_FC0_SUBTYPE_DEAUTH, reason); + ieee80211_node_leave(ic, ni); + /* XXX distinguish WPA/RSN? */ + ic->ic_stats.is_rx_assoc_badwpaie++; + return; + } + IEEE80211_DPRINTF(ic, + IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, + "[%6D] %s ie: mc %u/%u uc %u/%u key %u caps 0x%x\n", + wh->i_addr2, ":", + wpa[0] != IEEE80211_ELEMID_RSN ? "WPA" : "RSN", + rsn.rsn_mcastcipher, rsn.rsn_mcastkeylen, + rsn.rsn_ucastcipher, rsn.rsn_ucastkeylen, + rsn.rsn_keymgmt, rsn.rsn_caps); + } + /* discard challenge after association */ + if (ni->ni_challenge != NULL) { + FREE(ni->ni_challenge, M_DEVBUF); + ni->ni_challenge = NULL; + } + /* NB: 802.11 spec says to ignore station's privacy bit */ + if ((capinfo & IEEE80211_CAPINFO_ESS) == 0) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY, + "[%6D] deny %s request, capability mismatch 0x%x\n", + wh->i_addr2, ":", + reassoc ? "reassoc" : "assoc", capinfo); IEEE80211_SEND_MGMT(ic, ni, resp, IEEE80211_STATUS_CAPINFO); + ieee80211_node_leave(ic, ni); ic->ic_stats.is_rx_assoc_capmismatch++; return; } - ieee80211_setup_rates(ic, ni, rates, xrates, + rate = ieee80211_setup_rates(ni, rates, xrates, IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); - if (ni->ni_rates.rs_nrates == 0) { - IEEE80211_DPRINTF(("%s: rate unmatch for %6D\n", - __func__, wh->i_addr2, ":")); - ni->ni_associd = 0; + /* + * If constrained to 11g-only stations reject an + * 11b-only station. We cheat a bit here by looking + * at the max negotiated xmit rate and assuming anyone + * with a best rate <24Mb/s is an 11b station. + */ + if ((rate & IEEE80211_RATE_BASIC) || + ((ic->ic_flags & IEEE80211_F_PUREG) && rate < 48)) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY, + "[%6D] deny %s request, rate set mismatch\n", + wh->i_addr2, ":", + reassoc ? "reassoc" : "assoc"); IEEE80211_SEND_MGMT(ic, ni, resp, IEEE80211_STATUS_BASIC_RATE); + ieee80211_node_leave(ic, ni); ic->ic_stats.is_rx_assoc_norate++; return; } ni->ni_rssi = rssi; ni->ni_rstamp = rstamp; - ni->ni_intval = bintval; + ni->ni_intval = lintval; ni->ni_capinfo = capinfo; ni->ni_chan = ic->ic_bss->ni_chan; ni->ni_fhdwell = ic->ic_bss->ni_fhdwell; ni->ni_fhindex = ic->ic_bss->ni_fhindex; - if (ni->ni_associd == 0) { - /* XXX handle rollover at 2007 */ - /* XXX guarantee uniqueness */ - ni->ni_associd = 0xc000 | ic->ic_bss->ni_associd++; - newassoc = 1; - } else - newassoc = 0; - /* XXX for 11g must turn off short slot time if long - slot time sta associates */ - IEEE80211_SEND_MGMT(ic, ni, resp, IEEE80211_STATUS_SUCCESS); - if (ifp->if_flags & IFF_DEBUG) - if_printf(ifp, "station %6D %s associated\n", - ni->ni_macaddr, ":", - (newassoc ? "newly" : "already")); - /* give driver a chance to setup state like ni_txrate */ - if (ic->ic_newassoc) - (*ic->ic_newassoc)(ic, ni, newassoc); + if (wpa != NULL) { + /* + * Record WPA/RSN parameters for station, mark + * node as using WPA and record information element + * for applications that require it. + */ + ni->ni_rsn = rsn; + ieee80211_saveie(&ni->ni_wpa_ie, wpa); + } else if (ni->ni_wpa_ie != NULL) { + /* + * Flush any state from a previous association. + */ + FREE(ni->ni_wpa_ie, M_DEVBUF); + ni->ni_wpa_ie = NULL; + } + if (wme != NULL) { + /* + * Record WME parameters for station, mark node + * as capable of QoS and record information + * element for applications that require it. + */ + ieee80211_saveie(&ni->ni_wme_ie, wme); + ni->ni_flags |= IEEE80211_NODE_QOS; + } else if (ni->ni_wme_ie != NULL) { + /* + * Flush any state from a previous association. + */ + FREE(ni->ni_wme_ie, M_DEVBUF); + ni->ni_wme_ie = NULL; + ni->ni_flags &= ~IEEE80211_NODE_QOS; + } + ieee80211_deliver_l2uf(ni); + ieee80211_node_join(ic, ni, resp); break; } case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: { + uint16_t capinfo, associd; uint16_t status; if (ic->ic_opmode != IEEE80211_M_STA || - ic->ic_state != IEEE80211_S_ASSOC) + ic->ic_state != IEEE80211_S_ASSOC) { + ic->ic_stats.is_rx_mgtdiscard++; return; + } /* * asresp frame format @@ -1074,27 +2435,30 @@ ieee80211_recv_mgmt(struct ieee80211com * [2] association ID * [tlv] supported rates * [tlv] extended supported rates + * [tlv] WME */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6); ni = ic->ic_bss; - ni->ni_capinfo = le16toh(*(uint16_t *)frm); + capinfo = le16toh(*(uint16_t *)frm); frm += 2; - status = le16toh(*(uint16_t *)frm); frm += 2; if (status != 0) { - if_printf(ifp, "association failed (reason %d) for %6D\n", - status, wh->i_addr3, ":"); - if (ni != ic->ic_bss) + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%6D] %sassoc failed (reason %d)\n", + wh->i_addr2, ":", + ISREASSOC(subtype) ? "re" : "", status); + if (ni != ic->ic_bss) /* XXX never true? */ ni->ni_fails++; - ic->ic_stats.is_rx_auth_fail++; + ic->ic_stats.is_rx_auth_fail++; /* XXX */ return; } - ni->ni_associd = le16toh(*(uint16_t *)frm); + associd = le16toh(*(uint16_t *)frm); frm += 2; - rates = xrates = NULL; + rates = xrates = wpa = wme = NULL; while (frm < efrm) { + IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1]); switch (*frm) { case IEEE80211_ELEMID_RATES: rates = frm; @@ -1102,22 +2466,86 @@ ieee80211_recv_mgmt(struct ieee80211com case IEEE80211_ELEMID_XRATES: xrates = frm; break; + case IEEE80211_ELEMID_VENDOR: + if (iswmeoui(frm)) + wme = frm; + /* XXX Atheros OUI support */ + break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE); - ieee80211_setup_rates(ic, ni, rates, xrates, + rate = ieee80211_setup_rates(ni, rates, xrates, IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); - if (ni->ni_rates.rs_nrates != 0) - ieee80211_new_state(ic, IEEE80211_S_RUN, - wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK); + if (rate & IEEE80211_RATE_BASIC) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%6D] %sassoc failed (rate set mismatch)\n", + wh->i_addr2, ":", + ISREASSOC(subtype) ? "re" : ""); + if (ni != ic->ic_bss) /* XXX never true? */ + ni->ni_fails++; + ic->ic_stats.is_rx_assoc_norate++; + ieee80211_new_state(ic, IEEE80211_S_SCAN, 0); + return; + } + + ni->ni_capinfo = capinfo; + ni->ni_associd = associd; + if (wme != NULL && + ieee80211_parse_wmeparams(ic, wme, wh) >= 0) { + ni->ni_flags |= IEEE80211_NODE_QOS; + ieee80211_wme_updateparams(ic); + } else + ni->ni_flags &= ~IEEE80211_NODE_QOS; + /* + * Configure state now that we are associated. + * + * XXX may need different/additional driver callbacks? + */ + if (ic->ic_curmode == IEEE80211_MODE_11A || + (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE)) { + ic->ic_flags |= IEEE80211_F_SHPREAMBLE; + ic->ic_flags &= ~IEEE80211_F_USEBARKER; + } else { + ic->ic_flags &= ~IEEE80211_F_SHPREAMBLE; + ic->ic_flags |= IEEE80211_F_USEBARKER; + } + ieee80211_set_shortslottime(ic, + ic->ic_curmode == IEEE80211_MODE_11A || + (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); + /* + * Honor ERP protection. + * + * NB: ni_erp should zero for non-11g operation. + * XXX check ic_curmode anyway? + */ + if (ic->ic_curmode == IEEE80211_MODE_11G && + (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) + ic->ic_flags |= IEEE80211_F_USEPROT; + else + ic->ic_flags &= ~IEEE80211_F_USEPROT; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%6D] %sassoc success: %s preamble, %s slot time%s%s\n", + wh->i_addr2, ":", + ISREASSOC(subtype) ? "re" : "", + ic->ic_flags&IEEE80211_F_SHPREAMBLE ? "short" : "long", + ic->ic_flags&IEEE80211_F_SHSLOT ? "short" : "long", + ic->ic_flags&IEEE80211_F_USEPROT ? ", protection" : "", + ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : "" + ); + ieee80211_new_state(ic, IEEE80211_S_RUN, subtype); break; } case IEEE80211_FC0_SUBTYPE_DEAUTH: { uint16_t reason; + + if (ic->ic_state == IEEE80211_S_SCAN) { + ic->ic_stats.is_rx_mgtdiscard++; + return; + } /* * deauth frame format * [2] reason @@ -1125,22 +2553,22 @@ ieee80211_recv_mgmt(struct ieee80211com IEEE80211_VERIFY_LENGTH(efrm - frm, 2); reason = le16toh(*(uint16_t *)frm); ic->ic_stats.is_rx_deauth++; + IEEE80211_NODE_STAT(ni, rx_deauth); + + IEEE80211_DPRINTF(ic, IEEE80211_MSG_AUTH, + "[%6D] recv deauthenticate (reason %d)\n", + ni->ni_macaddr, ":", reason); switch (ic->ic_opmode) { case IEEE80211_M_STA: ieee80211_new_state(ic, IEEE80211_S_AUTH, wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK); break; case IEEE80211_M_HOSTAP: - if (ni != ic->ic_bss) { - if (ifp->if_flags & IFF_DEBUG) - if_printf(ifp, "station %6D deauthenticated" - " by peer (reason %d)\n", - ni->ni_macaddr, ":", reason); - /* node will be free'd on return */ - ieee80211_unref_node(&ni); - } + if (ni != ic->ic_bss) + ieee80211_node_leave(ic, ni); break; default: + ic->ic_stats.is_rx_mgtdiscard++; break; } break; @@ -1148,6 +2576,13 @@ ieee80211_recv_mgmt(struct ieee80211com case IEEE80211_FC0_SUBTYPE_DISASSOC: { uint16_t reason; + + if (ic->ic_state != IEEE80211_S_RUN && + ic->ic_state != IEEE80211_S_ASSOC && + ic->ic_state != IEEE80211_S_AUTH) { + ic->ic_stats.is_rx_mgtdiscard++; + return; + } /* * disassoc frame format * [2] reason @@ -1155,32 +2590,286 @@ ieee80211_recv_mgmt(struct ieee80211com IEEE80211_VERIFY_LENGTH(efrm - frm, 2); reason = le16toh(*(uint16_t *)frm); ic->ic_stats.is_rx_disassoc++; + IEEE80211_NODE_STAT(ni, rx_disassoc); + + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%6D] recv disassociate (reason %d)\n", + ni->ni_macaddr, ":", reason); switch (ic->ic_opmode) { case IEEE80211_M_STA: ieee80211_new_state(ic, IEEE80211_S_ASSOC, wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK); break; case IEEE80211_M_HOSTAP: - if (ni != ic->ic_bss) { - if (ifp->if_flags & IFF_DEBUG) - if_printf(ifp, "station %6D disassociated" - " by peer (reason %d)\n", - ni->ni_macaddr, ":", reason); - ni->ni_associd = 0; - /* XXX node reclaimed how? */ - } + if (ni != ic->ic_bss) + ieee80211_node_leave(ic, ni); break; default: + ic->ic_stats.is_rx_mgtdiscard++; break; } break; } default: - IEEE80211_DPRINTF(("%s: mgmt frame with subtype 0x%x not " - "handled\n", __func__, subtype)); + IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY, + wh, "mgt", "subtype 0x%x not handled", subtype); ic->ic_stats.is_rx_badsubtype++; break; } +#undef ISREASSOC +#undef ISPROBE } #undef IEEE80211_VERIFY_LENGTH #undef IEEE80211_VERIFY_ELEMENT + +/* + * Handle station power-save state change. + */ +static void +ieee80211_node_pwrsave(struct ieee80211_node *ni, int enable) +{ + struct ieee80211com *ic = ni->ni_ic; + struct mbuf *m; + + ASSERT_SERIALIZED(ic->ic_ifp->if_serializer); + + if (enable) { + if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) + ic->ic_ps_sta++; + ni->ni_flags |= IEEE80211_NODE_PWR_MGT; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, + "[%6D] power save mode on, %u sta's in ps mode\n", + ni->ni_macaddr, ":", ic->ic_ps_sta); + return; + } + + if (ni->ni_flags & IEEE80211_NODE_PWR_MGT) + ic->ic_ps_sta--; + ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, + "[%6D] power save mode off, %u sta's in ps mode\n", + ni->ni_macaddr, ":", ic->ic_ps_sta); + /* XXX if no stations in ps mode, flush mc frames */ + + /* + * Flush queued unicast frames. + */ + if (IEEE80211_NODE_SAVEQ_QLEN(ni) == 0) { + if (ic->ic_set_tim != NULL) + ic->ic_set_tim(ni, 0); /* just in case */ + return; + } + IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, + "[%6D] flush ps queue, %u packets queued\n", + ni->ni_macaddr, ":", IEEE80211_NODE_SAVEQ_QLEN(ni)); + for (;;) { + int qlen; + + IEEE80211_NODE_SAVEQ_DEQUEUE(ni, m, qlen); + if (m == NULL) + break; + /* + * If this is the last packet, turn off the TIM bit. + * If there are more packets, set the more packets bit + * in the mbuf so ieee80211_encap will mark the 802.11 + * head to indicate more data frames will follow. + */ + if (qlen != 0) + m->m_flags |= M_MORE_DATA; + /* XXX need different driver interface */ + /* XXX bypasses q max */ + /* XXX bypasses ALTQ */ + ifq_enqueue(&ic->ic_ifp->if_snd, m, NULL); + } + if (ic->ic_set_tim != NULL) + ic->ic_set_tim(ni, 0); +} + +/* + * Process a received ps-poll frame. + */ +static void +ieee80211_recv_pspoll(struct ieee80211com *ic, + struct ieee80211_node *ni, struct mbuf *m0) +{ + struct ieee80211_frame_min *wh; + struct mbuf *m; + uint16_t aid; + int qlen; + + ASSERT_SERIALIZED(ic->ic_ifp->if_serializer); + + wh = mtod(m0, struct ieee80211_frame_min *); + if (ni->ni_associd == 0) { + IEEE80211_DISCARD(ic, IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG, + (struct ieee80211_frame *) wh, "ps-poll", + "%s", "unassociated station"); + ic->ic_stats.is_ps_unassoc++; + IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH, + IEEE80211_REASON_NOT_ASSOCED); + return; + } + + aid = le16toh(*(uint16_t *)wh->i_dur); + if (aid != ni->ni_associd) { + IEEE80211_DISCARD(ic, IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG, + (struct ieee80211_frame *) wh, "ps-poll", + "aid mismatch: sta aid 0x%x poll aid 0x%x", + ni->ni_associd, aid); + ic->ic_stats.is_ps_badaid++; + IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH, + IEEE80211_REASON_NOT_ASSOCED); + return; + } + + /* Okay, take the first queued packet and put it out... */ + IEEE80211_NODE_SAVEQ_DEQUEUE(ni, m, qlen); + if (m == NULL) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, + "[%6D] recv ps-poll, but queue empty\n", + wh->i_addr2, ":"); + ieee80211_send_nulldata(ieee80211_ref_node(ni)); + ic->ic_stats.is_ps_qempty++; /* XXX node stat */ + if (ic->ic_set_tim != NULL) + ic->ic_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_DPRINTF(ic, IEEE80211_MSG_POWER, + "[%6D] recv ps-poll, send packet, %u still queued\n", + ni->ni_macaddr, ":", qlen); + m->m_flags |= M_MORE_DATA; + } else { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, + "[%6D] recv ps-poll, send packet, queue empty\n", + ni->ni_macaddr, ":"); + if (ic->ic_set_tim != NULL) + ic->ic_set_tim(ni, 0); + } + m->m_flags |= M_PWR_SAV; /* bypass PS handling */ + ifq_enqueue(&ic->ic_ifp->if_snd, m, NULL); /* XXX bypasses ALTQ */ +} + +#ifdef IEEE80211_DEBUG +/* + * Debugging support. + */ + +/* + * Return the bssid of a frame. + */ +static const uint8_t * +ieee80211_getbssid(struct ieee80211com *ic, const struct ieee80211_frame *wh) +{ + if (ic->ic_opmode == IEEE80211_M_STA) + return wh->i_addr2; + if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) != IEEE80211_FC1_DIR_NODS) + return wh->i_addr1; + if ((wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == IEEE80211_FC0_SUBTYPE_PS_POLL) + return wh->i_addr1; + return wh->i_addr3; +} + +void +ieee80211_note(struct ieee80211com *ic, const char *fmt, ...) +{ + char buf[128]; /* XXX */ + __va_list ap; + + __va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + __va_end(ap); + + if_printf(ic->ic_ifp, "%s", buf); /* NB: no \n */ +} + +void +ieee80211_note_frame(struct ieee80211com *ic, + const struct ieee80211_frame *wh, + const char *fmt, ...) +{ + char buf[128]; /* XXX */ + __va_list ap; + + __va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + __va_end(ap); + if_printf(ic->ic_ifp, "[%6D] %s\n", + ieee80211_getbssid(ic, wh), ":", buf); +} + +void +ieee80211_note_mac(struct ieee80211com *ic, + const uint8_t mac[IEEE80211_ADDR_LEN], + const char *fmt, ...) +{ + char buf[128]; /* XXX */ + __va_list ap; + + __va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + __va_end(ap); + if_printf(ic->ic_ifp, "[%6D] %s\n", mac, ":", buf); +} + +static void +ieee80211_discard_frame(struct ieee80211com *ic, + const struct ieee80211_frame *wh, + const char *type, const char *fmt, ...) +{ + __va_list ap; + + printf("[%s:%6D] discard ", ic->ic_ifp->if_xname, + ieee80211_getbssid(ic, wh), ":"); + if (type != NULL) + printf("%s frame, ", type); + else + printf("frame, "); + __va_start(ap, fmt); + vprintf(fmt, ap); + __va_end(ap); + printf("\n"); +} + +static void +ieee80211_discard_ie(struct ieee80211com *ic, + const struct ieee80211_frame *wh, + const char *type, const char *fmt, ...) +{ + __va_list ap; + + printf("[%s:%6D] discard ", ic->ic_ifp->if_xname, + ieee80211_getbssid(ic, wh), ":"); + if (type != NULL) + printf("%s information element, ", type); + else + printf("information element, "); + __va_start(ap, fmt); + vprintf(fmt, ap); + __va_end(ap); + printf("\n"); +} + +static void +ieee80211_discard_mac(struct ieee80211com *ic, + const uint8_t mac[IEEE80211_ADDR_LEN], + const char *type, const char *fmt, ...) +{ + __va_list ap; + + printf("[%s:%6D] discard ", ic->ic_ifp->if_xname, mac, ":"); + if (type != NULL) + printf("%s frame, ", type); + else + printf("frame, "); + __va_start(ap, fmt); + vprintf(fmt, ap); + __va_end(ap); + printf("\n"); +} +#endif /* IEEE80211_DEBUG */