diff options
author | sam <sam@FreeBSD.org> | 2004-12-08 17:26:47 +0000 |
---|---|---|
committer | sam <sam@FreeBSD.org> | 2004-12-08 17:26:47 +0000 |
commit | 2843bf259ec9ca475410e4e9e53e7cd47cd55333 (patch) | |
tree | 704c955cb68020dd8237664b417e3ee6141f028b /sys/net80211/ieee80211_input.c | |
parent | d9ec783dcb4653bc5f6993ab5f154827f5a58922 (diff) | |
download | FreeBSD-src-2843bf259ec9ca475410e4e9e53e7cd47cd55333.zip FreeBSD-src-2843bf259ec9ca475410e4e9e53e7cd47cd55333.tar.gz |
Update 802.11 support; too much new functionality to fully describe
here but it includes completed 802.11g, WPA, 802.11i, 802.1x, WME/WMM,
AP-side power-save, crypto plugin framework, authenticator plugin framework,
and access control plugin frameowrk.
Diffstat (limited to 'sys/net80211/ieee80211_input.c')
-rw-r--r-- | sys/net80211/ieee80211_input.c | 2333 |
1 files changed, 1916 insertions, 417 deletions
diff --git a/sys/net80211/ieee80211_input.c b/sys/net80211/ieee80211_input.c index 74aa1c4..c3f8d03 100644 --- a/sys/net80211/ieee80211_input.c +++ b/sys/net80211/ieee80211_input.c @@ -1,6 +1,6 @@ /*- * Copyright (c) 2001 Atsushi Onoe - * Copyright (c) 2002, 2003 Sam Leffler, Errno Consulting + * Copyright (c) 2002-2004 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,38 +33,85 @@ #include <sys/cdefs.h> __FBSDID("$FreeBSD$"); -#include "opt_inet.h" - #include <sys/param.h> -#include <sys/systm.h> +#include <sys/systm.h> #include <sys/mbuf.h> #include <sys/malloc.h> -#include <sys/kernel.h> -#include <sys/socket.h> -#include <sys/sockio.h> #include <sys/endian.h> -#include <sys/errno.h> -#include <sys/bus.h> -#include <sys/proc.h> -#include <sys/sysctl.h> - -#include <machine/atomic.h> +#include <sys/socket.h> + #include <net/if.h> -#include <net/if_dl.h> #include <net/if_media.h> -#include <net/if_arp.h> #include <net/ethernet.h> #include <net/if_llc.h> +#include <net/if_vlan_var.h> #include <net80211/ieee80211_var.h> #include <net/bpf.h> -#ifdef INET -#include <netinet/in.h> -#include <netinet/if_ether.h> -#endif +#ifdef IEEE80211_DEBUG +#include <machine/stdarg.h> + +/* + * 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 u_int8_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 u_int8_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 *); +static struct mbuf *ieee80211_decap(struct ieee80211com *, 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 @@ -77,19 +124,22 @@ __FBSDID("$FreeBSD$"); * by the 802.11 layer. */ void -ieee80211_input(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni, - int rssi, u_int32_t rstamp) +ieee80211_input(struct ieee80211com *ic, struct mbuf *m, + struct ieee80211_node *ni, int rssi, u_int32_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 len, hdrsize, off; u_int8_t dir, type, subtype; u_int8_t *bssid; u_int16_t rxseq; KASSERT(ni != NULL, ("null node")); + ni->ni_inact = ni->ni_inact_reload; /* trim CRC here so WEP can find its own CRC at the end of packet. */ if (m->m_flags & M_HASFCS) { @@ -106,38 +156,40 @@ ieee80211_input(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni, 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 %s\n", __func__, - ether_sprintf(wh->i_addr2))); + IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT, + bssid, NULL, "%s", "not to bss"); ic->ic_stats.is_rx_wrongbss++; goto out; } @@ -145,43 +197,119 @@ ieee80211_input(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni, 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 %s\n", __func__, - ether_sprintf(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) { + /* + * Fake up a node for this newly + * discovered member of the IBSS. + */ + ni = ieee80211_fakeup_adhoc_node(ic->ic_sta, + type == IEEE80211_FC0_TYPE_CTL ? + wh->i_addr1 : 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(*(u_int16_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)) { + u_int8_t tid; + if (IEEE80211_QOS_HAS_SEQ(wh)) { + tid = ((struct ieee80211_qosframe *)wh)-> + i_qos[0] & IEEE80211_QOS_TID; + if (tid >= WME_AC_VI) + ic->ic_wme.wme_hipri_traffic++; + tid++; + } else + tid = 0; + rxseq = le16toh(*(u_int16_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: + hdrsize = ieee80211_hdrsize(wh); + if (ic->ic_flags & IEEE80211_F_DATAPAD) + hdrsize = roundup(hdrsize, sizeof(u_int32_t)); + if (m->m_len < hdrsize && + (m = m_pullup(m, hdrsize)) == NULL) { + IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY, + wh, "data", "too short: len %u, expecting %u", + m->m_pkthdr.len, hdrsize); + ic->ic_stats.is_rx_tooshort++; + goto out; /* XXX */ + } + if (subtype & IEEE80211_FC0_SUBTYPE_QOS) { + /* XXX discard if node w/o IEEE80211_NODE_QOS? */ + /* + * Strip QoS control and any padding so only a + * stock 802.11 header is at the front. + */ + /* XXX 4-address QoS frame */ + off = hdrsize - sizeof(struct ieee80211_frame); + ovbcopy(mtod(m, u_int8_t *), mtod(m, u_int8_t *) + off, + hdrsize - off); + m_adj(m, off); + wh = mtod(m, struct ieee80211_frame *); + wh->i_fc[0] &= ~IEEE80211_FC0_SUBTYPE_QOS; + } else { + /* XXX copy up for 4-address frames w/ padding */ + } switch (ic->ic_opmode) { case IEEE80211_M_STA: if (dir != IEEE80211_FC1_DIR_FROMDS) { @@ -197,6 +325,8 @@ ieee80211_input(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni, * 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; } @@ -207,6 +337,7 @@ ieee80211_input(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni, ic->ic_stats.is_rx_wrongdir++; goto out; } + /* XXX no power-save support */ break; case IEEE80211_M_HOSTAP: if (dir != IEEE80211_FC1_DIR_TODS) { @@ -215,62 +346,156 @@ ieee80211_input(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni, } /* check if source STA is associated */ if (ni == ic->ic_bss) { - IEEE80211_DPRINTF(("%s: data from unknown src " - "%s\n", __func__, - ether_sprintf(wh->i_addr2))); + IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, + wh, "data", "%s", "unknown src"); /* NB: caller deals with reference */ - ni = ieee80211_dup_bss(ic, wh->i_addr2); + ni = ieee80211_dup_bss(ic->ic_sta, 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_free_node(ni); } ic->ic_stats.is_rx_notassoc++; goto err; } if (ni->ni_associd == 0) { - IEEE80211_DPRINTF(("ieee80211_input: " - "data from unassoc src %s\n", - ether_sprintf(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); + if (key == NULL) { + /* NB: stats+msgs handled in crypto_decap */ + IEEE80211_NODE_STAT(ni, rx_wepfail); goto out; } + wh = mtod(m, struct ieee80211_frame *); + } else { + key = NULL; + } + + /* + * Next up, any fragmentation. + */ + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + m = ieee80211_defrag(ic, ni, m); + 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)) { + 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) bpf_mtap(ic->ic_rawbpf, m); - m = ieee80211_decap(ifp, m); + + /* + * Finally, strip the 802.11 header. + */ + m = ieee80211_decap(ic, m); 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; } + 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; + } + } 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; + } + } ifp->if_ipackets++; + IEEE80211_NODE_STAT(ni, rx_data); + IEEE80211_NODE_STAT_ADD(ni, rx_bytes, m->m_pkthdr.len); /* perform as a bridge within the AP */ - m1 = NULL; - if (ic->ic_opmode == IEEE80211_M_HOSTAP) { - eh = mtod(m, struct ether_header *); + 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_copypacket(m, M_DONTWAIT); if (m1 == NULL) @@ -278,13 +503,19 @@ ieee80211_input(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni, else m1->m_flags |= M_MCAST; } else { - ni = ieee80211_find_node(ic, eh->ether_dhost); - if (ni != NULL) { - if (ni->ni_associd != 0) { + /* XXX this dups work done in ieee80211_encap */ + /* check if destination is associated */ + struct ieee80211_node *ni1 = + ieee80211_find_node(ic->ic_sta, + eh->ether_dhost); + if (ni1 != NULL) { + /* XXX check if authorized */ + if (ni1->ni_associd != 0) { m1 = m; m = NULL; } - ieee80211_free_node(ic, ni); + /* XXX statistic? */ + ieee80211_free_node(ni1); } } if (m1 != NULL) { @@ -295,61 +526,65 @@ ieee80211_input(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni, ifp->if_obytes += len; } } - if (m != NULL) + if (m != NULL) { + if (ni->ni_vlan != 0) { + /* attach vlan tag */ + /* XXX goto err? */ + VLAN_INPUT_TAG(ifp, m, ni->ni_vlan, goto out); + } (*ifp->if_input)(ifp, m); + } return; case IEEE80211_FC0_TYPE_MGT: + IEEE80211_NODE_STAT(ni, rx_mgmt); if (dir != IEEE80211_FC1_DIR_NODS) { 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 %s rssi %d\n", + ieee80211_mgt_subtype_name[subtype >> + IEEE80211_FC0_SUBTYPE_SHIFT], + ether_sprintf(wh->i_addr2), rssi); + } +#endif + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + 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; + key = ieee80211_crypto_decap(ic, ni, m); + 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 %s rssi %d\n", - ieee80211_mgt_subtype_name[subtype - >> IEEE80211_FC0_SUBTYPE_SHIFT], - ether_sprintf(wh->i_addr2), rssi); } if (ic->ic_rawbpf) bpf_mtap(ic->ic_rawbpf, m); @@ -358,10 +593,19 @@ ieee80211_input(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni, return; 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; } @@ -373,19 +617,108 @@ ieee80211_input(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni, bpf_mtap(ic->ic_rawbpf, m); m_freem(m); } +#undef SEQ_LEQ +} + +/* + * This function reassemble fragments. + */ +static struct mbuf * +ieee80211_defrag(struct ieee80211com *ic, struct ieee80211_node *ni, + struct mbuf *m) +{ + struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); + struct ieee80211_frame *lwh; + u_int16_t rxseq; + u_int8_t fragno; + u_int8_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(*(u_int16_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; + } + IEEE80211_NODE_LOCK(ni->ni_table); + mfrag = ni->ni_rxfrag[0]; + ni->ni_rxfrag[0] = NULL; + IEEE80211_NODE_UNLOCK(ni->ni_table); + + /* + * Validate new fragment is in order and + * related to the previous ones. + */ + if (mfrag != NULL) { + u_int16_t last_rxseq; + + lwh = mtod(mfrag, struct ieee80211_frame *); + last_rxseq = le16toh(*(u_int16_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_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 *); + *(u_int16_t *) lwh->i_seq = *(u_int16_t *) wh->i_seq; + } + if (more_frag) { /* more to come, save */ + ni->ni_rxfrag[0] = mfrag; + mfrag = NULL; + } + return mfrag; } -struct mbuf * -ieee80211_decap(struct ifnet *ifp, struct mbuf *m) +static struct mbuf * +ieee80211_decap(struct ieee80211com *ic, struct mbuf *m) { + struct ieee80211_frame wh; /* NB: QoS stripped above */ 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 < sizeof(wh) + sizeof(*llc) && + (m = m_pullup(m, sizeof(wh) + 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)); @@ -413,7 +746,8 @@ ieee80211_decap(struct ifnet *ifp, struct mbuf *m) break; case IEEE80211_FC1_DIR_DSTODS: /* not yet supported */ - IEEE80211_DPRINTF(("%s: DS to DS\n", __func__)); + IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY, + &wh, "data", "%s", "DS to DS not supported"); m_freem(m); return NULL; } @@ -495,9 +829,10 @@ ieee80211_setup_rates(struct ieee80211com *ic, struct ieee80211_node *ni, 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, + "[%s] extended rate set too large;" + " only using %u of %u rates\n", + ether_sprintf(ni->ni_macaddr), nxrates, xrates[1]); ic->ic_stats.is_rx_rstoobig++; } memcpy(rs->rs_rates + rs->rs_nrates, xrates+2, nxrates); @@ -506,21 +841,318 @@ ieee80211_setup_rates(struct ieee80211com *ic, struct ieee80211_node *ni, return ieee80211_fix_rate(ic, ni, flags); } +static void +ieee80211_auth_open(struct ieee80211com *ic, struct ieee80211_frame *wh, + struct ieee80211_node *ni, int rssi, u_int32_t rstamp, u_int16_t seq, + u_int16_t status) +{ + + switch (ic->ic_opmode) { + case IEEE80211_M_IBSS: + if (ic->ic_state != IEEE80211_S_RUN || + seq != IEEE80211_AUTH_OPEN_REQUEST) { + ic->ic_stats.is_rx_bad_auth++; + return; + } + 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 != 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 + (void) ieee80211_ref_node(ni); + ni->ni_inact_reload = ic->ic_inact_auth; + IEEE80211_SEND_MGMT(ic, ni, + IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); + IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, + "[%s] station authenticated (open)\n", + ether_sprintf(ni->ni_macaddr)); + 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, + "[%s] open auth failed (reason %d)\n", + ether_sprintf(ni->ni_macaddr), 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_M_MONITOR: + break; + } +} + +static int +alloc_challenge(struct ieee80211com *ic, struct ieee80211_node *ni) +{ + if (ni->ni_challenge == NULL) + MALLOC(ni->ni_challenge, u_int32_t*, IEEE80211_CHALLENGE_LEN, + M_DEVBUF, M_NOWAIT); + if (ni->ni_challenge == NULL) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, + "[%s] shared key challenge alloc failed\n", + ether_sprintf(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, + u_int8_t *frm, u_int8_t *efrm, struct ieee80211_node *ni, int rssi, + u_int32_t rstamp, u_int16_t seq, u_int16_t status) +{ + u_int8_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 { + (void) ieee80211_ref_node(ni); + allocbs = 0; + } + 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, + "[%s] shared key %sauth request\n", + ether_sprintf(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; + } + ni->ni_inact_reload = ic->ic_inact_auth; + IEEE80211_DPRINTF(ic, + IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, + "[%s] station authenticated (shared key)\n", + ether_sprintf(ni->ni_macaddr)); + 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, + "[%s] shared key auth failed (reason %d)\n", + ether_sprintf(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_MGMT(ic, ni, + IEEE80211_FC0_SUBTYPE_AUTH, + (seq + 1) | (estatus<<16)); + } +} + /* Verify the existence and length of __elem or get out. */ #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 %s\n", __func__, (__elem)[1], \ - ieee80211_mgt_subtype_name[subtype >> \ - IEEE80211_FC0_SUBTYPE_SHIFT], \ - ether_sprintf(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; \ } \ @@ -528,26 +1160,516 @@ ieee80211_setup_rates(struct ieee80211com *ic, struct ieee80211_node *ni, #define IEEE80211_VERIFY_LENGTH(_len, _minlen) do { \ if ((_len) < (_minlen)) { \ - IEEE80211_DPRINTF(("%s: %s frame too short from %s\n", \ - __func__, \ - ieee80211_mgt_subtype_name[subtype >> \ - IEEE80211_FC0_SUBTYPE_SHIFT], \ - ether_sprintf(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, + u_int8_t mac[IEEE80211_ADDR_LEN], u_int8_t *ssid) +{ + printf("[%s] discard %s frame, ssid mismatch: ", + ether_sprintf(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) \ + ((u_int16_t) \ + ((((const u_int8_t *)(p))[0] ) | \ + (((const u_int8_t *)(p))[1] << 8))) +#define LE_READ_4(p) \ + ((u_int32_t) \ + ((((const u_int8_t *)(p))[0] ) | \ + (((const u_int8_t *)(p))[1] << 8) | \ + (((const u_int8_t *)(p))[2] << 16) | \ + (((const u_int8_t *)(p))[3] << 24))) + +static int __inline +iswpaoui(const u_int8_t *frm) +{ + return frm[1] > 3 && LE_READ_4(frm+2) == ((WPA_OUI_TYPE<<24)|WPA_OUI); +} + +static int __inline +iswmeoui(const u_int8_t *frm) +{ + return frm[1] > 3 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI); +} + +static int __inline +iswmeparam(const u_int8_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 u_int8_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 u_int8_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(u_int8_t *sel, u_int8_t *keylen) +{ +#define WPA_SEL(x) (((x)<<24)|WPA_OUI) + u_int32_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(u_int8_t *sel) +{ +#define WPA_SEL(x) (((x)<<24)|WPA_OUI) + u_int32_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, u_int8_t *frm, + struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh) +{ + u_int8_t len = frm[1]; + u_int32_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. + */ + KASSERT(ic->ic_flags & IEEE80211_F_WPA1, + ("not WPA, flags 0x%x", ic->ic_flags)); + 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<<wpa_cipher(frm, &rsn->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<<IEEE80211_CIPHER_TKIP)) + rsn->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(u_int8_t *sel, u_int8_t *keylen) +{ +#define RSN_SEL(x) (((x)<<24)|RSN_OUI) + u_int32_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(u_int8_t *sel) +{ +#define RSN_SEL(x) (((x)<<24)|RSN_OUI) + u_int32_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, u_int8_t *frm, + struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh) +{ + u_int8_t len = frm[1]; + u_int32_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. + */ + KASSERT(ic->ic_flags & IEEE80211_F_WPA2, + ("not RSN, flags 0x%x", ic->ic_flags)); + 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_cipher(frm, &rsn->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<<IEEE80211_CIPHER_TKIP)) + rsn->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, u_int8_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 0; + } + 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 +} + +static void +ieee80211_saveie(u_int8_t **iep, const u_int8_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 */ +} + +#ifdef IEEE80211_DEBUG +static void +dump_probe_beacon(u_int8_t subtype, int isnew, + const u_int8_t mac[IEEE80211_ADDR_LEN], + u_int8_t chan, u_int8_t bchan, u_int16_t capinfo, u_int16_t bintval, + u_int8_t erp, u_int8_t *ssid, u_int8_t *country) +{ + printf("[%s] %s%s on chan %u (bss chan %u) ", + ether_sprintf(mac), isnew ? "new " : "", + ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], + chan, bchan); + ieee80211_print_essid(ssid + 2, ssid[1]); + printf("\n"); + + if (isnew) { + printf("[%s] caps 0x%x bintval %u erp 0x%x", + ether_sprintf(mac), capinfo, bintval, erp); + if (country) { +#ifdef __FreeBSD__ + printf(" country info %*D", country[1], country+2, " "); +#else + int i; + printf(" country info"); + for (i = 0; i < country[1]; i++) + printf(" %02x", country[i+2]); +#endif + } + printf("\n"); + } +} +#endif /* IEEE80211_DEBUG */ + void ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, struct ieee80211_node *ni, int subtype, int rssi, u_int32_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; u_int8_t *frm, *efrm; - u_int8_t *ssid, *rates, *xrates; - int reassoc, resp, newassoc, allocbs; + u_int8_t *ssid, *rates, *xrates, *wpa, *wme; + int reassoc, resp, allocbs; wh = mtod(m0, struct ieee80211_frame *); frm = (u_int8_t *)&wh[1]; @@ -555,18 +1677,34 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: case IEEE80211_FC0_SUBTYPE_BEACON: { - u_int8_t *tstamp, *bintval, *capinfo, *country; + u_int8_t *tstamp, *country; u_int8_t chan, bchan, fhindex, erp; + u_int16_t capinfo, bintval, timoff; u_int16_t fhdwell; - int isprobe; - if (ic->ic_opmode != IEEE80211_M_IBSS && - ic->ic_state != IEEE80211_S_SCAN) { - /* XXX: may be useful for background scan */ + if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) { + /* + * Count beacon frames specially, some drivers + * use this info to do things like update LED's. + */ + ic->ic_stats.is_rx_beacon++; + IEEE80211_NODE_STAT(ni, rx_beacons); + } else + IEEE80211_NODE_STAT(ni, rx_proberesp); + /* + * 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 @@ -578,17 +1716,20 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, * [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; + tstamp = frm; frm += 8; + bintval = le16toh(*(u_int16_t *)frm); frm += 2; + capinfo = le16toh(*(u_int16_t *)frm); frm += 2; + ssid = rates = xrates = country = wpa = wme = NULL; bchan = ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan); chan = bchan; fhdwell = 0; fhindex = 0; erp = 0; + timoff = 0; while (frm < efrm) { switch (*frm) { case IEEE80211_ELEMID_SSID: @@ -602,7 +1743,7 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, break; case IEEE80211_ELEMID_FHPARMS: if (ic->ic_phytype == IEEE80211_T_FH) { - fhdwell = (frm[3] << 8) | frm[2]; + fhdwell = LE_READ_2(&frm[2]); chan = IEEE80211_FH_CHAN(frm[4], frm[5]); fhindex = frm[6]; } @@ -616,6 +1757,8 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, chan = frm[2]; break; case IEEE80211_ELEMID_TIM: + /* XXX ATIM? */ + timoff = frm - mtod(m0, u_int8_t *); break; case IEEE80211_ELEMID_IBSSPARMS: break; @@ -624,17 +1767,28 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, 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]; break; + case IEEE80211_ELEMID_RSN: + wpa = frm; + break; + case IEEE80211_ELEMID_VENDOR: + if (iswpaoui(frm)) + wpa = frm; + else if (iswmeparam(frm) || iswmeinfo(frm)) + 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; } @@ -647,10 +1801,10 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, 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)); + IEEE80211_DISCARD(ic, IEEE80211_MSG_ELEMID, + wh, ieee80211_mgt_subtype_name[subtype >> + IEEE80211_FC0_SUBTYPE_SHIFT], + "invalid channel %u", chan); ic->ic_stats.is_rx_badchan++; return; } @@ -665,108 +1819,146 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, * 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, + wh, ieee80211_mgt_subtype_name[subtype >> + IEEE80211_FC0_SUBTYPE_SHIFT], + "for off-channel %u\n", chan); ic->ic_stats.is_rx_chanmismatch++; 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. + * 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. */ - 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 %s\n", ether_sprintf(wh->i_addr2)); - printf("%s: caps 0x%x bintval %u erp 0x%x\n", - __func__, le16toh(*(u_int16_t *)capinfo), - le16toh(*(u_int16_t *)bintval), erp); - if (country) - printf("%s: country info %*D\n", - __func__, country[1], country+2, " "); + 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))) { + if (ni->ni_erp != erp) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%s] erp change: was 0x%x, now 0x%x\n", + ether_sprintf(wh->i_addr2), + ni->ni_erp, erp); + if (erp & IEEE80211_ERP_USE_PROTECTION) + ic->ic_flags |= IEEE80211_F_USEPROT; + else + ic->ic_flags &= ~IEEE80211_F_USEPROT; + ni->ni_erp = erp; + /* XXX statistic */ + } + if ((ni->ni_capinfo ^ capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%s] capabilities change: before 0x%x," + " now 0x%x\n", + ether_sprintf(wh->i_addr2), + ni->ni_capinfo, capinfo); + /* + * NB: we assume short preamble doesn't + * change dynamically + */ + ieee80211_set_shortslottime(ic, + ic->ic_curmode == IEEE80211_MODE_11A || + (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); + ni->ni_capinfo = capinfo; + /* XXX statistic */ + } + if (wme != NULL && + ieee80211_parse_wmeparams(ic, wme, wh)) + ieee80211_wme_updateparams(ic); + /* NB: don't need the rest of this */ + return; } + + if (ni == ic->ic_bss) { +#ifdef IEEE80211_DEBUG + if (ieee80211_msg_scan(ic)) + dump_probe_beacon(subtype, 1, + wh->i_addr2, chan, bchan, capinfo, + bintval, erp, ssid, country); #endif - if (ni == NULL) { - ni = ieee80211_alloc_node(ic, wh->i_addr2); + /* + * Create a new entry. If scanning the entry goes + * in the scan cache. Otherwise, be particular when + * operating in adhoc mode--only take nodes marked + * as ibss participants so we don't populate our + * neighbor table with unintersting sta's. + */ + if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { + if ((capinfo & IEEE80211_CAPINFO_IBSS) == 0) + return; + ni = ieee80211_fakeup_adhoc_node(ic->ic_sta, + wh->i_addr2); + } else + ni = ieee80211_dup_bss(&ic->ic_scan, 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) { + } else if (ssid[1] != 0 && + (ISPROBE(subtype) || ni->ni_esslen == 0)) { /* - * Update ESSID at probe response to adopt hidden AP by - * Lucent/Cisco, which announces null ESSID in beacon. + * Update ESSID at probe response to adopt + * hidden AP by Lucent/Cisco, which announces + * null ESSID in beacon. */ +#ifdef IEEE80211_DEBUG + if (ieee80211_msg_scan(ic) || + ieee80211_msg_debug(ic)) + dump_probe_beacon(subtype, 0, + wh->i_addr2, chan, bchan, capinfo, + bintval, erp, ssid, country); +#endif ni->ni_esslen = ssid[1]; memset(ni->ni_essid, 0, sizeof(ni->ni_essid)); memcpy(ni->ni_essid, ssid + 2, ssid[1]); - allocbs = 0; - } else - allocbs = 0; + } + ni->ni_scangen = ic->ic_scan.nt_scangen; 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(*(u_int16_t *)bintval); - ni->ni_capinfo = le16toh(*(u_int16_t *)capinfo); - /* XXX validate channel # */ + memcpy(ni->ni_tstamp.data, tstamp, sizeof(ni->ni_tstamp)); + ni->ni_intval = bintval; + ni->ni_capinfo = capinfo; 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). + * Record the byte offset from the mac header to + * the start of the TIM information element for + * use by hardware and/or to speedup software + * processing of beacon frames. */ - 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); - } + ni->ni_timoff = timoff; + /* + * Record optional information elements that might be + * used by applications or drivers. + */ + if (wme != NULL) + ieee80211_saveie(&ni->ni_wme_ie, wme); + if (wpa != NULL) + ieee80211_saveie(&ni->ni_wpa_ie, wpa); + /* NB: must be after ni_chan is setup */ + ieee80211_setup_rates(ic, ni, rates, xrates, IEEE80211_F_DOSORT); break; } case IEEE80211_FC0_SUBTYPE_PROBE_REQ: { u_int8_t rate; - if (ic->ic_opmode == IEEE80211_M_STA) + 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 @@ -791,43 +1983,45 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, } 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 %s\n", ether_sprintf(wh->i_addr2)); - } -#endif - ic->ic_stats.is_rx_ssidmismatch++; - return; - } + IEEE80211_VERIFY_SSID(ic->ic_bss, ssid); if (ni == ic->ic_bss) { - ni = ieee80211_dup_bss(ic, wh->i_addr2); + if (ic->ic_opmode == IEEE80211_M_IBSS) { + /* + * 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); + } else + ni = ieee80211_dup_bss(ic->ic_sta, wh->i_addr2); if (ni == NULL) return; - IEEE80211_DPRINTF(("%s: new req from %s\n", - __func__, ether_sprintf(wh->i_addr2))); allocbs = 1; } else allocbs = 0; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%s] recv probe req\n", ether_sprintf(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); + IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE + | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); if (rate & IEEE80211_RATE_BASIC) { - IEEE80211_DPRINTF(("%s: rate negotiation failed: %s\n", - __func__,ether_sprintf(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 && ic->ic_opmode != IEEE80211_M_IBSS) { + /* reclaim immediately */ + ieee80211_free_node(ni); + } break; } @@ -844,94 +2038,63 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, algo = le16toh(*(u_int16_t *)frm); seq = le16toh(*(u_int16_t *)(frm + 2)); status = le16toh(*(u_int16_t *)(frm + 4)); - if (algo != IEEE80211_AUTH_ALG_OPEN) { - /* TODO: shared key auth */ - IEEE80211_DPRINTF(("%s: unsupported auth %d from %s\n", - __func__, algo, ether_sprintf(wh->i_addr2))); - ic->ic_stats.is_rx_auth_unsupported++; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_AUTH, + "[%s] recv auth frame with algorithm %d seq %d\n", + ether_sprintf(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++; return; } - switch (ic->ic_opmode) { - case IEEE80211_M_IBSS: - if (ic->ic_state != IEEE80211_S_RUN || seq != 1) { - IEEE80211_DPRINTF(("%s: discard auth from %s; " - "state %u, seq %u\n", __func__, - ether_sprintf(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 %s; " - "state %u, seq %u\n", __func__, - ether_sprintf(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 %s %s authenticated\n", - (allocbs ? "newly" : "already"), - ether_sprintf(ni->ni_macaddr)); - break; - - case IEEE80211_M_STA: - if (ic->ic_state != IEEE80211_S_AUTH || seq != 2) { - IEEE80211_DPRINTF(("%s: discard auth from %s; " - "state %u, seq %u\n", __func__, - ether_sprintf(wh->i_addr2), - ic->ic_state, seq)); - ic->ic_stats.is_rx_bad_auth++; - break; - } - if (status != 0) { - if_printf(&ic->ic_if, - "authentication failed (reason %d) for %s\n", - status, - ether_sprintf(wh->i_addr3)); - if (ni != ic->ic_bss) - ni->ni_fails++; - ic->ic_stats.is_rx_auth_fail++; - return; + 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); } - ieee80211_new_state(ic, IEEE80211_S_ASSOC, - wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK); - break; - case IEEE80211_M_MONITOR: - break; + 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)); + } + return; + } break; } case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: { u_int16_t capinfo, bintval; + struct ieee80211_rsnparms rsn; + u_int8_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; @@ -948,11 +2111,14 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, * [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 %s\n", - __func__, ether_sprintf(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; } @@ -960,7 +2126,7 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, bintval = le16toh(*(u_int16_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) { switch (*frm) { case IEEE80211_ELEMID_SSID: @@ -972,48 +2138,88 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, 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)) { + if (ic->ic_flags & IEEE80211_F_WPA1) + 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 %s\n", ether_sprintf(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 %s\n", - __func__, ether_sprintf(wh->i_addr2))); - ni = ieee80211_dup_bss(ic, wh->i_addr2); + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY, + "[%s] deny %s request, sta not authenticated\n", + ether_sprintf(wh->i_addr2), + reassoc ? "reassoc" : "assoc"); + ni = ieee80211_dup_bss(ic->ic_sta, 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_free_node(ni); } ic->ic_stats.is_rx_assoc_notauth++; return; } - /* XXX per-node cipher suite */ + 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, + "[%s] %s ie: mc %u/%u uc %u/%u key %u caps 0x%x\n", + ether_sprintf(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; + } /* XXX some stations use the privacy bit for handling APs that suport both encrypted and unencrypted traffic */ + /* NB: PRIVACY flag bits are assumed to match */ 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 %s\n", - __func__, capinfo, ether_sprintf(wh->i_addr2))); - ni->ni_associd = 0; + (capinfo & IEEE80211_CAPINFO_PRIVACY) ^ + (ic->ic_flags & IEEE80211_F_PRIVACY)) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY, + "[%s] deny %s request, capability mismatch 0x%x\n", + ether_sprintf(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; } @@ -1021,11 +2227,13 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, 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 %s\n", - __func__, ether_sprintf(wh->i_addr2))); - ni->ni_associd = 0; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY, + "[%s] deny %s request, rate set mismatch\n", + ether_sprintf(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; } @@ -1036,33 +2244,51 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, 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 %s %s associated\n", - (newassoc ? "newly" : "already"), - ether_sprintf(ni->ni_macaddr)); - /* 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_node_join(ic, ni, resp); break; } case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: { + u_int16_t capinfo, associd; u_int16_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 @@ -1071,26 +2297,28 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, * [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(*(u_int16_t *)frm); + capinfo = le16toh(*(u_int16_t *)frm); frm += 2; - status = le16toh(*(u_int16_t *)frm); frm += 2; if (status != 0) { - if_printf(ifp, "association failed (reason %d) for %s\n", - status, ether_sprintf(wh->i_addr3)); - if (ni != ic->ic_bss) + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%s] %sassoc failed (reason %d)\n", + ether_sprintf(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(*(u_int16_t *)frm); + associd = le16toh(*(u_int16_t *)frm); frm += 2; - rates = xrates = NULL; + rates = xrates = wpa = wme = NULL; while (frm < efrm) { switch (*frm) { case IEEE80211_ELEMID_RATES: @@ -1099,6 +2327,11 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, 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; } @@ -1107,14 +2340,70 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, ieee80211_setup_rates(ic, 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 (ni->ni_rates.rs_nrates == 0) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%s] %sassoc failed (rate set mismatch)\n", + ether_sprintf(wh->i_addr2), + ISREASSOC(subtype) ? "re" : ""); + if (ni != ic->ic_bss) /* XXX never true? */ + ni->ni_fails++; + ic->ic_stats.is_rx_assoc_norate++; + return; + } + + ni->ni_capinfo = capinfo; + ni->ni_associd = associd; + if (wme != NULL && ieee80211_parse_wmeparams(ic, wme, wh)) { + 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 (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, + "[%s] %sassoc success: %s preamble, %s slot time%s%s\n", + ether_sprintf(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: { u_int16_t reason; + + if (ic->ic_state == IEEE80211_S_SCAN) { + ic->ic_stats.is_rx_mgtdiscard++; + return; + } /* * deauth frame format * [2] reason @@ -1122,6 +2411,7 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, IEEE80211_VERIFY_LENGTH(efrm - frm, 2); reason = le16toh(*(u_int16_t *)frm); ic->ic_stats.is_rx_deauth++; + IEEE80211_NODE_STAT(ni, rx_deauth); switch (ic->ic_opmode) { case IEEE80211_M_STA: ieee80211_new_state(ic, IEEE80211_S_AUTH, @@ -1129,15 +2419,15 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, break; case IEEE80211_M_HOSTAP: if (ni != ic->ic_bss) { - if (ifp->if_flags & IFF_DEBUG) - if_printf(ifp, "station %s deauthenticated" - " by peer (reason %d)\n", - ether_sprintf(ni->ni_macaddr), reason); - /* node will be free'd on return */ - ieee80211_unref_node(&ni); + IEEE80211_DPRINTF(ic, IEEE80211_MSG_AUTH, + "station %s deauthenticated by peer " + "(reason %d)\n", + ether_sprintf(ni->ni_macaddr), reason); + ieee80211_node_leave(ic, ni); } break; default: + ic->ic_stats.is_rx_mgtdiscard++; break; } break; @@ -1145,6 +2435,12 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, case IEEE80211_FC0_SUBTYPE_DISASSOC: { u_int16_t reason; + + if (ic->ic_state != IEEE80211_S_RUN && + ic->ic_state != IEEE80211_S_AUTH) { + ic->ic_stats.is_rx_mgtdiscard++; + return; + } /* * disassoc frame format * [2] reason @@ -1152,6 +2448,7 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, IEEE80211_VERIFY_LENGTH(efrm - frm, 2); reason = le16toh(*(u_int16_t *)frm); ic->ic_stats.is_rx_disassoc++; + IEEE80211_NODE_STAT(ni, rx_disassoc); switch (ic->ic_opmode) { case IEEE80211_M_STA: ieee80211_new_state(ic, IEEE80211_S_ASSOC, @@ -1159,25 +2456,227 @@ ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, break; case IEEE80211_M_HOSTAP: if (ni != ic->ic_bss) { - if (ifp->if_flags & IFF_DEBUG) - if_printf(ifp, "station %s disassociated" - " by peer (reason %d)\n", - ether_sprintf(ni->ni_macaddr), reason); - ni->ni_associd = 0; - /* XXX node reclaimed how? */ + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%s] sta disassociated by peer (reason %d)\n", + ether_sprintf(ni->ni_macaddr), reason); + 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; + + 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, + "[%s] power save mode on, %u sta's in ps mode\n", + ether_sprintf(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, + "[%s] power save mode off, %u sta's in ps mode\n", + ether_sprintf(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) { + ic->ic_set_tim(ic, ni, 0); /* just in case */ + return; + } + IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, + "[%s] flush ps queue, %u packets queued\n", + ether_sprintf(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 packet dispatched to the station. + */ + if (qlen != 0) { + struct ieee80211_frame_min *wh = + mtod(m, struct ieee80211_frame_min *); + wh->i_fc[1] |= IEEE80211_FC1_MORE_DATA; + } + /* XXX need different driver interface */ + /* XXX bypasses q max */ + IF_ENQUEUE(&ic->ic_ifp->if_snd, m); + } +} + +/* + * 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; + u_int16_t aid; + int qlen; + + 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(*(u_int16_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, + "[%s] recv ps-poll, but queue empty\n", + ether_sprintf(wh->i_addr2)); + ieee80211_send_nulldata(ic, ni); + ic->ic_stats.is_ps_qempty++; /* XXX node stat */ + ic->ic_set_tim(ic, 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, + "[%s] recv ps-poll, send packet, %u still queued\n", + ether_sprintf(ni->ni_macaddr), qlen); + wh = mtod(m, struct ieee80211_frame_min *); + wh->i_fc[1] |= IEEE80211_FC1_MORE_DATA; + } else { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, + "[%s] recv ps-poll, send packet, queue empty\n", + ether_sprintf(ni->ni_macaddr)); + ic->ic_set_tim(ic, ni, 0); + } + m->m_flags |= M_PWR_SAV; /* bypass PS handling */ + IF_ENQUEUE(&ic->ic_ifp->if_snd, m); +} + +#ifdef IEEE80211_DEBUG +/* + * Debugging support. + */ + +/* + * Return the bssid of a frame. + */ +static const u_int8_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; +} + +static void +ieee80211_discard_frame(struct ieee80211com *ic, + const struct ieee80211_frame *wh, + const char *type, const char *fmt, ...) +{ + va_list ap; + + printf("[%s] discard ", ether_sprintf(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] discard ", ether_sprintf(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 u_int8_t mac[IEEE80211_ADDR_LEN], + const char *type, const char *fmt, ...) +{ + va_list ap; + + printf("[%s] discard ", ether_sprintf(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 */ |