#include <sys/cdefs.h>
#ifdef __FreeBSD__
__FBSDID("$FreeBSD: src/sys/net80211/ieee80211_input.c,v 1.81 2005/08/10 16:22:29 sam Exp $");
#endif
#ifdef __NetBSD__
__KERNEL_RCSID(0, "$NetBSD: ieee80211_input.c,v 1.118 2025/10/18 07:35:33 mlelstv Exp $");
#endif
#ifdef _KERNEL_OPT
#include "opt_inet.h"
#endif
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/endian.h>
#include <sys/errno.h>
#include <sys/proc.h>
#include <sys/sysctl.h>
#include <sys/cpu.h>
#include <net/if.h>
#include <net/if_media.h>
#include <net/if_arp.h>
#include <net/if_ether.h>
#include <net/if_llc.h>
#include <net80211/ieee80211_var.h>
#include <net/bpf.h>
#ifdef INET
#include <netinet/in.h>
#include <net/if_ether.h>
#endif
const struct timeval ieee80211_merge_print_intvl = {.tv_sec = 1, .tv_usec = 0};
#ifdef IEEE80211_DEBUG
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;
}
#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)
#define IEEE80211_DEBUGVAR(a) a
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, ...)
#define IEEE80211_DEBUGVAR(a)
#endif
static struct mbuf *ieee80211_defrag(struct ieee80211_node *,
struct mbuf *, int);
static struct mbuf *ieee80211_decap(struct mbuf *, int);
static void ieee80211_send_error(struct ieee80211com *, struct ieee80211_node *,
const u_int8_t *mac, int subtype, int arg);
static void ieee80211_deliver_data(struct ieee80211com *,
struct ieee80211_node *, struct mbuf *);
#ifndef IEEE80211_NO_HOSTAP
static void ieee80211_node_pwrsave(struct ieee80211_node *, int enable);
static void ieee80211_recv_pspoll(struct ieee80211com *,
struct ieee80211_node *, struct mbuf *);
#endif
static void ieee80211_update_adhoc_node(struct ieee80211com *,
struct ieee80211_node *, struct ieee80211_frame *,
struct ieee80211_scanparams *, int, u_int32_t);
static int
ieee80211_input_data(struct ieee80211com *ic, struct mbuf **mp,
struct ieee80211_node *ni)
{
struct ifnet *ifp = ic->ic_ifp;
struct ieee80211_key *key;
struct ieee80211_frame *wh;
u_int8_t dir, subtype;
struct ether_header *eh;
struct mbuf *m = *mp;
int hdrspace;
wh = mtod(m, struct ieee80211_frame *);
dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK;
subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
hdrspace = ieee80211_hdrspace(ic, wh);
if (m->m_len < hdrspace &&
(m = m_pullup(m, hdrspace)) == NULL) {
ic->ic_stats.is_rx_tooshort++;
goto out;
}
wh = mtod(m, struct ieee80211_frame *);
switch (ic->ic_opmode) {
case IEEE80211_M_STA:
if (dir != IEEE80211_FC1_DIR_FROMDS) {
IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
wh, "data", "unknown dir 0x%x", dir);
ic->ic_stats.is_rx_wrongdir++;
goto out;
}
if ((ifp->if_flags & IFF_SIMPLEX) &&
IEEE80211_IS_MULTICAST(wh->i_addr1) &&
IEEE80211_ADDR_EQ(wh->i_addr3, ic->ic_myaddr)) {
IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
wh, NULL, "%s", "multicast echo");
ic->ic_stats.is_rx_mcastecho++;
goto out;
}
break;
case IEEE80211_M_IBSS:
case IEEE80211_M_AHDEMO:
if (dir != IEEE80211_FC1_DIR_NODS) {
IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
wh, "data", "unknown dir 0x%x", dir);
ic->ic_stats.is_rx_wrongdir++;
goto out;
}
break;
case IEEE80211_M_HOSTAP:
#ifndef IEEE80211_NO_HOSTAP
if (dir != IEEE80211_FC1_DIR_TODS) {
IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
wh, "data", "unknown dir 0x%x", dir);
ic->ic_stats.is_rx_wrongdir++;
goto out;
}
if (ni == ic->ic_bss) {
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_DISCARD(ic, IEEE80211_MSG_INPUT,
wh, "data", "%s", "unassoc src");
IEEE80211_SEND_MGMT(ic, ni,
IEEE80211_FC0_SUBTYPE_DISASSOC,
IEEE80211_REASON_NOT_ASSOCED);
ic->ic_stats.is_rx_notassoc++;
goto err;
}
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);
#endif
break;
default:
goto out;
}
if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
if ((ic->ic_flags & IEEE80211_F_PRIVACY) == 0) {
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) {
IEEE80211_NODE_STAT(ni, rx_wepfail);
goto out;
}
} else {
key = NULL;
}
if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
m = ieee80211_defrag(ni, m, hdrspace);
if (m == NULL) {
goto out;
}
}
if (key != NULL) {
if (!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;
}
wh = mtod(m, struct ieee80211_frame *);
wh->i_fc[1] &= ~IEEE80211_FC1_WEP;
}
bpf_mtap3(ic->ic_rawbpf, m, BPF_D_IN);
m = ieee80211_decap(m, hdrspace);
if (m == NULL) {
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)) {
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 {
if ((ic->ic_flags & IEEE80211_F_DROPUNENC) &&
key == NULL && eh->ether_type != htons(ETHERTYPE_PAE)) {
ic->ic_stats.is_rx_unencrypted++;
IEEE80211_NODE_STAT(ni, rx_unencrypted);
goto out;
}
}
IEEE80211_NODE_STAT(ni, rx_data);
IEEE80211_NODE_STAT_ADD(ni, rx_bytes, m->m_pkthdr.len);
ieee80211_deliver_data(ic, ni, m);
*mp = NULL;
return 0;
err:
if_statinc(ifp, if_ierrors);
out:
*mp = m;
return -1;
}
static int
ieee80211_input_management(struct ieee80211com *ic, struct mbuf **mp,
struct ieee80211_node *ni, int rssi, u_int32_t rstamp)
{
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
struct ifnet *ifp = ic->ic_ifp;
struct ieee80211_key *key;
struct ieee80211_frame *wh;
u_int8_t dir, subtype;
struct mbuf *m = *mp;
int hdrspace;
wh = mtod(m, struct ieee80211_frame *);
dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK;
subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
IEEE80211_NODE_STAT(ni, rx_mgmt);
if (dir != IEEE80211_FC1_DIR_NODS) {
IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
wh, "data", "unknown dir 0x%x", dir);
ic->ic_stats.is_rx_wrongdir++;
goto err;
}
if (m->m_len < sizeof(struct ieee80211_frame)) {
IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY, ni->ni_macaddr,
"mgt", "too short: len %u", m->m_len);
ic->ic_stats.is_rx_tooshort++;
goto out;
}
#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_snprintf(ebuf, sizeof(ebuf), wh->i_addr2),
rssi);
}
#endif
if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
if (subtype != IEEE80211_FC0_SUBTYPE_AUTH) {
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++;
goto out;
}
if ((ic->ic_flags & IEEE80211_F_PRIVACY) == 0) {
IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
wh, "mgt", "%s", "WEP set but PRIVACY off");
ic->ic_stats.is_rx_noprivacy++;
goto out;
}
hdrspace = ieee80211_hdrspace(ic, wh);
key = ieee80211_crypto_decap(ic, ni, &m, hdrspace);
if (key == NULL) {
goto out;
}
wh = mtod(m, struct ieee80211_frame *);
wh->i_fc[1] &= ~IEEE80211_FC1_WEP;
}
bpf_mtap3(ic->ic_rawbpf, m, BPF_D_IN);
(*ic->ic_recv_mgmt)(ic, m, ni, subtype, rssi, rstamp);
m_freem(m);
*mp = NULL;
return 0;
err:
if_statinc(ifp, if_ierrors);
out:
*mp = m;
return -1;
}
static void
ieee80211_input_control(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni)
{
IEEE80211_NODE_STAT(ni, rx_ctrl);
ic->ic_stats.is_rx_ctl++;
#ifndef IEEE80211_NO_HOSTAP
if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
struct ieee80211_frame *wh;
u_int8_t subtype;
wh = mtod(m, struct ieee80211_frame *);
subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
switch (subtype) {
case IEEE80211_FC0_SUBTYPE_PS_POLL:
ieee80211_recv_pspoll(ic, ni, m);
break;
}
}
#endif
}
int
ieee80211_input(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni, int rssi, u_int32_t rstamp)
{
#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;
u_int8_t dir, type;
u_int16_t rxseq;
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
int ret;
KASSERT(!cpu_intr_p());
IASSERT(ni != NULL, ("null node"));
ni->ni_inact = ni->ni_inact_reload;
if (m->m_flags & M_HASFCS) {
m_adj(m, -IEEE80211_CRC_LEN);
m->m_flags &= ~M_HASFCS;
}
type = -1;
if (ic->ic_opmode == IEEE80211_M_MONITOR)
goto out;
if (m->m_len < sizeof(struct ieee80211_frame_min)) {
IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY,
ni->ni_macaddr, NULL,
"too short (1): len %u", m->m_len);
ic->ic_stats.is_rx_tooshort++;
goto out;
}
wh = mtod(m, struct ieee80211_frame *);
if ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) !=
IEEE80211_FC0_VERSION_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;
if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) {
u_int8_t *bssid;
switch (ic->ic_opmode) {
case IEEE80211_M_STA:
bssid = wh->i_addr2;
if (!IEEE80211_ADDR_EQ(bssid, ni->ni_bssid)) {
IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT,
bssid, NULL, "node %s, %s",
ether_snprintf(ebuf, sizeof(ebuf),
ni->ni_bssid), "not to bss");
ic->ic_stats.is_rx_wrongbss++;
goto out;
}
if (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
!IEEE80211_ADDR_EQ(wh->i_addr1, ic->ic_myaddr)) {
IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT,
bssid, NULL, "not to cur sta: lladdr=%6D, addr1=%6D",
ic->ic_myaddr, ":", wh->i_addr1, ":");
ic->ic_stats.is_rx_wrongbss++;
goto out;
}
break;
case IEEE80211_M_IBSS:
case IEEE80211_M_AHDEMO:
case IEEE80211_M_HOSTAP:
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_len < sizeof(struct ieee80211_frame)) {
IEEE80211_DISCARD_MAC(ic,
IEEE80211_MSG_ANY, ni->ni_macaddr,
NULL, "too short (2): len %u",
m->m_len);
ic->ic_stats.is_rx_tooshort++;
goto out;
}
bssid = wh->i_addr3;
}
if (type != IEEE80211_FC0_TYPE_DATA)
break;
if (!IEEE80211_ADDR_EQ(bssid, ic->ic_bss->ni_bssid) &&
!IEEE80211_ADDR_EQ(bssid, ifp->if_broadcastaddr)) {
IEEE80211_DEBUGVAR(
char bbuf[3 * ETHER_ADDR_LEN]);
IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT,
bssid, NULL, "bss %s, broadcast %s, %s",
ether_snprintf(ebuf, sizeof(ebuf),
ic->ic_bss->ni_bssid),
ether_snprintf(bbuf, sizeof(bbuf),
ifp->if_broadcastaddr), "not to bss");
ic->ic_stats.is_rx_wrongbss++;
goto out;
}
if (ni == ic->ic_bss &&
ic->ic_opmode != IEEE80211_M_HOSTAP &&
!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) {
ni = ieee80211_fakeup_adhoc_node(&ic->ic_sta,
wh->i_addr2);
if (ni == NULL) {
goto err;
}
}
break;
default:
goto out;
}
ni->ni_rssi = rssi;
ni->ni_rstamp = rstamp;
if (HAS_SEQ(type) && (ic->ic_opmode != IEEE80211_M_STA ||
!IEEE80211_IS_MULTICAST(wh->i_addr1))) {
u_int8_t tid, retry;
u_int16_t rxno, orxno;
if (ieee80211_has_qos(wh)) {
struct ieee80211_qosframe *qosf;
if (m->m_len < sizeof(struct ieee80211_qosframe)) {
IEEE80211_DISCARD_MAC(ic,
IEEE80211_MSG_ANY,
ni->ni_macaddr, NULL,
"too short (1): len %u", m->m_len);
ic->ic_stats.is_rx_tooshort++;
goto out;
}
qosf = mtod(m, struct ieee80211_qosframe *);
tid = qosf->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(*(u_int16_t *)wh->i_seq);
retry = wh->i_fc[1] & IEEE80211_FC1_RETRY;
rxno = rxseq >> IEEE80211_SEQ_SEQ_SHIFT;
orxno = ni->ni_rxseqs[tid] >> IEEE80211_SEQ_SEQ_SHIFT;
if (retry && (
(orxno == 4095 && rxno == orxno) ||
(orxno != 4095 &&
SEQ_LEQ(rxseq, ni->ni_rxseqs[tid]))
)) {
IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT,
bssid, "duplicate",
"seqno <%u,%u> fragno <%u,%u> tid %u",
rxno,
orxno,
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;
}
}
switch (type) {
case IEEE80211_FC0_TYPE_DATA:
ret = ieee80211_input_data(ic, &m, ni);
if (ret == -1) {
goto out;
}
return IEEE80211_FC0_TYPE_DATA;
case IEEE80211_FC0_TYPE_MGT:
ret = ieee80211_input_management(ic, &m, ni, rssi, rstamp);
if (ret == -1) {
goto out;
}
return IEEE80211_FC0_TYPE_MGT;
case IEEE80211_FC0_TYPE_CTL:
ieee80211_input_control(ic, m, ni);
goto out;
default:
IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY,
wh, NULL, "bad frame type 0x%x", type);
break;
}
err:
if_statinc(ifp, if_ierrors);
out:
if (m != NULL) {
bpf_mtap3(ic->ic_rawbpf, m, BPF_D_IN);
m_freem(m);
}
return type;
#undef SEQ_LEQ
}
static struct mbuf *
ieee80211_defrag(struct ieee80211_node *ni, struct mbuf *m, int hdrspace)
{
struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *);
struct ieee80211_frame *lwh;
u_int16_t rxseq, iseq;
u_int8_t fragno;
const u_int8_t more_frag = wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG;
struct mbuf *mfrag;
IASSERT(!IEEE80211_IS_MULTICAST(wh->i_addr1), ("multicast fragm?"));
iseq = *(u_int16_t *)wh->i_seq;
rxseq = le16toh(iseq);
fragno = rxseq & IEEE80211_SEQ_FRAG_MASK;
if (!more_frag && fragno == 0 && ni->ni_rxfrag[0] == NULL)
return m;
if (ni->ni_table == NULL) {
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);
if (mfrag != NULL) {
u_int16_t last_rxseq;
lwh = mtod(mfrag, struct ieee80211_frame *);
last_rxseq = le16toh(*(u_int16_t *)lwh->i_seq);
if (rxseq != last_rxseq+1 ||
!IEEE80211_ADDR_EQ(wh->i_addr1, lwh->i_addr1) ||
!IEEE80211_ADDR_EQ(wh->i_addr2, lwh->i_addr2) ||
((wh->i_fc[1] ^ lwh->i_fc[1]) & IEEE80211_FC1_WEP)) {
m_freem(mfrag);
mfrag = NULL;
}
}
if (mfrag == NULL) {
if (fragno != 0) {
IEEE80211_NODE_STAT(ni, rx_defrag);
m_freem(m);
return NULL;
}
mfrag = m;
} else {
int mlen;
m_adj(m, hdrspace);
mlen = m->m_pkthdr.len;
m_cat(mfrag, m);
mfrag->m_pkthdr.len += mlen;
lwh = mtod(mfrag, struct ieee80211_frame *);
*(u_int16_t *)lwh->i_seq = iseq;
}
if (more_frag) {
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;
int error;
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)
if_statinc(ifp, if_oerrors);
else
m1->m_flags |= M_MCAST;
} else {
struct ieee80211_node *sta =
ieee80211_find_node(&ic->ic_sta, eh->ether_dhost);
if (sta != NULL) {
if (ieee80211_node_is_authorized(sta)) {
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) {
int len;
#ifdef ALTQ
if (ALTQ_IS_ENABLED(&ifp->if_snd)) {
altq_etherclassify(&ifp->if_snd, m1);
}
#endif
len = m1->m_pkthdr.len;
IFQ_ENQUEUE(&ifp->if_snd, m1, error);
if (error) {
if_statinc(ifp, if_oerrors);
m_freem(m);
m = NULL;
}
if_statadd(ifp, if_obytes, len);
}
}
if (m != NULL) {
if (ni->ni_vlan != 0)
vlan_set_tag(m, ni->ni_vlan);
KASSERT(ifp->if_percpuq);
if_percpuq_enqueue(ifp->if_percpuq, m);
}
return;
}
static struct mbuf *
ieee80211_decap(struct mbuf *m, int hdrlen)
{
struct ieee80211_qosframe_addr4 wh;
struct ether_header *eh;
struct llc *llc;
if (m->m_len < hdrlen + sizeof(*llc) &&
(m = m_pullup(m, hdrlen + sizeof(*llc))) == NULL) {
return NULL;
}
memcpy(&wh, mtod(m, void *), hdrlen);
llc = (struct llc *)(mtod(m, char *) + 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, hdrlen + sizeof(struct llc) - sizeof(*eh));
llc = NULL;
} else {
m_adj(m, hdrlen - sizeof(*eh));
}
eh = mtod(m, struct ether_header *);
switch (wh.i_fc[1] & IEEE80211_FC1_DIR_MASK) {
case IEEE80211_FC1_DIR_NODS:
IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr1);
IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr2);
break;
case IEEE80211_FC1_DIR_TODS:
IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3);
IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr2);
break;
case IEEE80211_FC1_DIR_FROMDS:
IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr1);
IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr3);
break;
case IEEE80211_FC1_DIR_DSTODS:
IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3);
IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr4);
break;
}
#ifdef ALIGNED_POINTER
if (!ALIGNED_POINTER(mtod(m, char *) + sizeof(*eh), u_int32_t)) {
struct mbuf *n, *n0, **np;
char *newdata;
int off, pktlen;
n0 = NULL;
np = &n0;
off = 0;
pktlen = m->m_pkthdr.len;
while (pktlen > off) {
if (n0 == NULL) {
MGETHDR(n, M_DONTWAIT, MT_DATA);
if (n == NULL) {
m_freem(m);
return NULL;
}
m_move_pkthdr(n, m);
n->m_len = MHLEN;
} else {
MGET(n, M_DONTWAIT, MT_DATA);
if (n == NULL) {
m_freem(m);
m_freem(n0);
return NULL;
}
n->m_len = MLEN;
}
if (pktlen - off >= MINCLSIZE) {
MCLGET(n, M_DONTWAIT);
if (n->m_flags & M_EXT)
n->m_len = n->m_ext.ext_size;
}
if (n0 == NULL) {
newdata =
(char *)ALIGN(n->m_data + sizeof(*eh)) -
sizeof(*eh);
n->m_len -= newdata - n->m_data;
n->m_data = newdata;
}
if (n->m_len > pktlen - off)
n->m_len = pktlen - off;
m_copydata(m, off, n->m_len, mtod(n, void *));
off += n->m_len;
*np = n;
np = &n->m_next;
}
m_freem(m);
m = n0;
}
#endif
if (llc != NULL) {
eh = mtod(m, struct ether_header *);
eh->ether_type = htons(m->m_pkthdr.len - sizeof(*eh));
}
return m;
}
int
ieee80211_setup_rates(struct ieee80211_node *ni, const u_int8_t *rates,
const u_int8_t *xrates, int flags)
{
struct ieee80211com *ic = ni->ni_ic;
struct ieee80211_rateset *rs = &ni->ni_rates;
memset(rs, 0, sizeof(*rs));
rs->rs_nrates = rates[1];
memcpy(rs->rs_rates, rates + 2, rs->rs_nrates);
if (xrates != NULL) {
u_int8_t nxrates;
size_t totalrate;
nxrates = xrates[1];
totalrate = (size_t)rs->rs_nrates + (size_t)nxrates;
if (totalrate > IEEE80211_RATE_MAXSIZE) {
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
nxrates = IEEE80211_RATE_MAXSIZE - rs->rs_nrates;
IEEE80211_DPRINTF(ic, IEEE80211_MSG_XRATE,
"[%s] extended rate set too large;"
" only using %u of %u rates\n",
ether_snprintf(ebuf, sizeof(ebuf), 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(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)
{
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
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++;
if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
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:
IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
ni->ni_macaddr, "open auth",
"bad operating mode %u", ic->ic_opmode);
break;
case IEEE80211_M_HOSTAP:
#ifndef IEEE80211_NO_HOSTAP
if (ic->ic_state != IEEE80211_S_RUN ||
seq != IEEE80211_AUTH_OPEN_REQUEST) {
ic->ic_stats.is_rx_bad_auth++;
return;
}
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);
}
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,
"[%s] station authenticated (open)\n",
ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr));
if (ni->ni_authmode != IEEE80211_AUTH_8021X)
ieee80211_node_authorize(ni);
#endif
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_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr),
status);
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;
}
}
static void
ieee80211_send_error(struct ieee80211com *ic, struct ieee80211_node *ni,
const u_int8_t *mac, int subtype, int arg)
{
bool istmp;
if (ni == ic->ic_bss) {
ni = ieee80211_tmp_node(ic, mac);
if (ni == NULL) {
return;
}
istmp = true;
} else {
istmp = false;
}
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) {
ni->ni_challenge = malloc(IEEE80211_CHALLENGE_LEN,
M_DEVBUF, M_NOWAIT);
}
if (ni->ni_challenge == NULL) {
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH,
"[%s] shared key challenge alloc failed\n",
ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr));
}
return (ni->ni_challenge != NULL);
}
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 estatus;
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
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;
}
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++;
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:
#ifndef IEEE80211_NO_HOSTAP
{
int allocbs;
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;
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) {
return;
}
allocbs = 1;
} else {
if ((ni->ni_flags & IEEE80211_NODE_AREF) == 0)
(void)ieee80211_ref_node(ni);
allocbs = 0;
}
__USE(allocbs);
ni->ni_flags |= IEEE80211_NODE_AREF;
ni->ni_rssi = rssi;
ni->ni_rstamp = rstamp;
if (!alloc_challenge(ic, ni)) {
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_snprintf(ebuf, sizeof(ebuf),
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");
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,
"[%s] station authenticated (shared key)\n",
ether_snprintf(ebuf, sizeof(ebuf), 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);
}
#endif
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_snprintf(ebuf, sizeof(ebuf),
ieee80211_getbssid(ic, wh)),
status);
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;
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:
#ifndef IEEE80211_NO_HOSTAP
if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
ieee80211_send_error(ic, ni, wh->i_addr2,
IEEE80211_FC0_SUBTYPE_AUTH,
(seq + 1) | (estatus<<16));
} else if (ic->ic_opmode == IEEE80211_M_STA) {
if (ic->ic_state == IEEE80211_S_AUTH)
ieee80211_new_state(ic, IEEE80211_S_SCAN, 0);
}
#else
;
#endif
}
#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)
{
char ebuf[3 * ETHER_ADDR_LEN];
printf("[%s] discard %s frame, ssid mismatch: ",
ether_snprintf(ebuf, sizeof(ebuf), 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
#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
#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 __inline int
iswpaoui(const u_int8_t *frm)
{
return frm[1] > 3 && LE_READ_4(frm+2) == ((WPA_OUI_TYPE<<24)|WPA_OUI);
}
static __inline int
iswmeoui(const u_int8_t *frm)
{
return frm[1] > 3 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI);
}
static __inline int
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 __inline int
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
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;
#undef WPA_SEL
}
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;
#undef WPA_SEL
}
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;
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 += 2;
frm += 4, len -= 4;
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;
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;
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;
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)
rsn->rsn_caps = LE_READ_2(frm);
return 0;
}
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;
#undef WPA_SEL
}
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;
#undef RSN_SEL
}
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;
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;
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;
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;
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;
if (len > 2)
rsn->rsn_caps = LE_READ_2(frm);
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 -1;
}
qosinfo = frm[offsetof(struct ieee80211_wme_param, param_qosInfo)];
qosinfo &= WME_QOSINFO_COUNT;
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];
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(u_int8_t **iep, const u_int8_t *ie)
{
u_int ielen = ie[1]+2;
if (*iep == NULL || (*iep)[1] != ie[1]) {
if (*iep != NULL)
free(*iep, M_DEVBUF);
*iep = malloc(ielen, M_DEVBUF, M_NOWAIT);
}
if (*iep != NULL)
memcpy(*iep, ie, ielen);
}
static void
ieee80211_update_adhoc_node(struct ieee80211com *ic, struct ieee80211_node *ni,
struct ieee80211_frame *wh, struct ieee80211_scanparams *scan, int rssi,
u_int32_t rstamp)
{
if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) {
if ((ni = ieee80211_add_neighbor(ic, wh, scan)) == NULL)
return;
} else if (ni->ni_capinfo == 0) {
ieee80211_init_neighbor(ic, ni, wh, scan, 0);
} else {
memcpy(ni->ni_tstamp.data, scan->sp_tstamp, sizeof(ni->ni_tstamp));
}
ni->ni_rssi = rssi;
ni->ni_rstamp = rstamp;
if (IEEE80211_ADDR_EQ(wh->i_addr3, ni->ni_bssid))
return;
IEEE80211_ADDR_COPY(ni->ni_bssid, wh->i_addr3);
if (ni != ic->ic_bss)
return;
else if (ic->ic_flags & IEEE80211_F_DESBSSID) {
ieee80211_new_state(ic, IEEE80211_S_SCAN, 0);
return;
} else {
ieee80211_new_state(ic, IEEE80211_S_RUN, 0);
}
}
#define IEEE80211_VERIFY_ELEMENT(__elem, __maxlen) do { \
if ((__elem) == NULL) { \
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_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; \
} \
} while (0)
#define IEEE80211_VERIFY_LENGTH(_len, _minlen) do { \
if ((size_t)(_len) < (size_t)(_minlen)) { \
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)
static void
ieee80211_recv_mgmt_beacon(struct ieee80211com *ic, struct mbuf *m0,
struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
struct ieee80211_frame *wh;
u_int8_t *frm, *efrm;
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
struct ieee80211_scanparams scan;
wh = mtod(m0, struct ieee80211_frame *);
frm = (u_int8_t *)(wh + 1);
efrm = mtod(m0, u_int8_t *) + m0->m_len;
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;
}
IEEE80211_VERIFY_LENGTH(efrm - frm, 12);
memset(&scan, 0, sizeof(scan));
scan.sp_tstamp = frm; frm += 8;
scan.sp_bintval = le16toh(*(u_int16_t *)frm); frm += 2;
scan.sp_capinfo = le16toh(*(u_int16_t *)frm); frm += 2;
scan.sp_bchan = ieee80211_chan2ieee(ic, ic->ic_curchan);
scan.sp_chan = scan.sp_bchan;
while (frm + 1 < efrm) {
IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2);
switch (*frm) {
case IEEE80211_ELEMID_SSID:
scan.sp_ssid = frm;
break;
case IEEE80211_ELEMID_RATES:
scan.sp_rates = frm;
break;
case IEEE80211_ELEMID_COUNTRY:
scan.sp_country = frm;
break;
case IEEE80211_ELEMID_FHPARMS:
IEEE80211_VERIFY_LENGTH(frm[1], 5);
if (ic->ic_phytype == IEEE80211_T_FH) {
scan.sp_fhdwell = LE_READ_2(&frm[2]);
scan.sp_chan = IEEE80211_FH_CHAN(frm[4], frm[5]);
scan.sp_fhindex = frm[6];
}
break;
case IEEE80211_ELEMID_DSPARMS:
IEEE80211_VERIFY_LENGTH(frm[1], 1);
if (ic->ic_phytype != IEEE80211_T_FH)
scan.sp_chan = frm[2];
break;
case IEEE80211_ELEMID_TIM:
IEEE80211_VERIFY_LENGTH(frm[1], 4);
scan.sp_tim = frm;
scan.sp_timoff = frm - mtod(m0, u_int8_t *);
break;
case IEEE80211_ELEMID_IBSSPARMS:
break;
case IEEE80211_ELEMID_XRATES:
scan.sp_xrates = frm;
break;
case IEEE80211_ELEMID_ERP:
if (frm[1] != 1) {
IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID,
wh, "ERP", "bad len %u", frm[1]);
ic->ic_stats.is_rx_elem_toobig++;
break;
}
scan.sp_erp = frm[2];
break;
case IEEE80211_ELEMID_RSN:
scan.sp_wpa = frm;
break;
case IEEE80211_ELEMID_VENDOR:
if (iswpaoui(frm))
scan.sp_wpa = frm;
else if (iswmeparam(frm) || iswmeinfo(frm))
scan.sp_wme = frm;
break;
default:
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(scan.sp_rates, IEEE80211_RATE_MAXSIZE);
IEEE80211_VERIFY_ELEMENT(scan.sp_ssid, IEEE80211_NWID_LEN);
if (
#if IEEE80211_CHAN_MAX < 255
scan.sp_chan > IEEE80211_CHAN_MAX ||
#endif
isclr(ic->ic_chan_active, scan.sp_chan)) {
IEEE80211_DISCARD(ic,
IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT,
wh, ieee80211_mgt_subtype_name[subtype >>
IEEE80211_FC0_SUBTYPE_SHIFT],
"invalid channel %u", scan.sp_chan);
ic->ic_stats.is_rx_badchan++;
return;
}
if (scan.sp_chan != scan.sp_bchan &&
ic->ic_phytype != IEEE80211_T_FH) {
IEEE80211_DISCARD(ic,
IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT,
wh, ieee80211_mgt_subtype_name[subtype >>
IEEE80211_FC0_SUBTYPE_SHIFT],
"for off-channel %u", scan.sp_chan);
ic->ic_stats.is_rx_chanmismatch++;
return;
}
if (!(IEEE80211_BINTVAL_MIN <= scan.sp_bintval &&
scan.sp_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.sp_bintval);
ic->ic_stats.is_rx_badbintval++;
return;
}
if (ni != ic->ic_bss) {
ni = ieee80211_refine_node_for_beacon(ic, ni,
&ic->ic_channels[scan.sp_chan], scan.sp_ssid);
}
if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) {
ic->ic_stats.is_rx_beacon++;
IEEE80211_NODE_STAT(ni, rx_beacons);
} else {
IEEE80211_NODE_STAT(ni, rx_proberesp);
}
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))) {
memcpy(ni->ni_tstamp.data, scan.sp_tstamp, sizeof(ni->ni_tstamp));
if (ni->ni_erp != scan.sp_erp) {
IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC,
"[%s] erp change: was 0x%x, now 0x%x\n",
ether_snprintf(ebuf, sizeof(ebuf),
wh->i_addr2), ni->ni_erp, scan.sp_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.sp_erp;
}
if ((ni->ni_capinfo ^ scan.sp_capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME) {
IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC,
"[%s] capabilities change: before 0x%x,"
" now 0x%x\n",
ether_snprintf(ebuf, sizeof(ebuf),
wh->i_addr2),
ni->ni_capinfo, scan.sp_capinfo);
ieee80211_set_shortslottime(ic,
ic->ic_curmode == IEEE80211_MODE_11A ||
(ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME));
ni->ni_capinfo = scan.sp_capinfo;
}
if (scan.sp_wme != NULL && (ni->ni_flags & IEEE80211_NODE_QOS) &&
ieee80211_parse_wmeparams(ic, scan.sp_wme, wh) > 0) {
ieee80211_wme_updateparams(ic);
}
if (scan.sp_tim != NULL) {
struct ieee80211_tim_ie *ie =
(struct ieee80211_tim_ie *)scan.sp_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);
}
ic->ic_bmiss_count = 0;
return;
}
if (ic->ic_flags & IEEE80211_F_SCAN) {
if (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN) {
ieee80211_probe_curchan(ic, 1);
ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN;
}
ieee80211_add_scan(ic, &scan, wh, subtype, rssi, rstamp);
return;
}
if (scan.sp_capinfo & IEEE80211_CAPINFO_IBSS) {
ieee80211_update_adhoc_node(ic, ni, wh, &scan, rssi, rstamp);
}
}
static void
ieee80211_recv_mgmt_probe_req(struct ieee80211com *ic, struct mbuf *m0,
struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
struct ieee80211_frame *wh;
u_int8_t *frm, *efrm;
u_int8_t *ssid, *rates, *xrates;
bool need_free = false;
u_int8_t rate;
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
wh = mtod(m0, struct ieee80211_frame *);
frm = (u_int8_t *)(wh + 1);
efrm = mtod(m0, u_int8_t *) + m0->m_len;
if (ic->ic_opmode == IEEE80211_M_STA ||
ic->ic_state != IEEE80211_S_RUN) {
ic->ic_stats.is_rx_mgtdiscard++;
return;
}
if (IEEE80211_IS_MULTICAST(wh->i_addr2)) {
ic->ic_stats.is_rx_mgtdiscard++;
return;
}
ssid = rates = xrates = NULL;
while (frm + 1 < efrm) {
IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2);
switch (*frm) {
case IEEE80211_ELEMID_SSID:
ssid = frm;
break;
case IEEE80211_ELEMID_RATES:
rates = frm;
break;
case IEEE80211_ELEMID_XRATES:
xrates = frm;
break;
}
frm += frm[1] + 2;
}
IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE);
IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN);
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++;
return;
}
if (ni == ic->ic_bss) {
if (ic->ic_opmode != IEEE80211_M_IBSS) {
ni = ieee80211_tmp_node(ic, wh->i_addr2);
need_free = true;
} else if (IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) {
;
} else {
ni = ieee80211_fakeup_adhoc_node(&ic->ic_sta,
wh->i_addr2);
}
if (ni == NULL)
return;
}
IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, "[%s] recv probe req\n",
ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2));
ni->ni_rssi = rssi;
ni->ni_rstamp = rstamp;
rate = ieee80211_setup_rates(ni, rates, xrates,
IEEE80211_R_DOSORT | IEEE80211_R_DOFRATE |
IEEE80211_R_DONEGO | IEEE80211_R_DODEL);
if (rate & IEEE80211_RATE_BASIC) {
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 (need_free) {
ieee80211_free_node(ni);
}
}
static void
ieee80211_recv_mgmt_auth(struct ieee80211com *ic, struct mbuf *m0,
struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
struct ieee80211_frame *wh;
u_int8_t *frm, *efrm;
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
u_int16_t algo, seq, status;
wh = mtod(m0, struct ieee80211_frame *);
frm = (u_int8_t *)(wh + 1);
efrm = mtod(m0, u_int8_t *) + m0->m_len;
IEEE80211_VERIFY_LENGTH(efrm - frm, 6);
algo = le16toh(*(u_int16_t *)frm);
seq = le16toh(*(u_int16_t *)(frm + 2));
status = le16toh(*(u_int16_t *)(frm + 4));
IEEE80211_DPRINTF(ic, IEEE80211_MSG_AUTH,
"[%s] recv auth frame with algorithm %d seq %d\n",
ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2), algo, seq);
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;
}
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++;
#ifndef IEEE80211_NO_HOSTAP
if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
IEEE80211_SEND_MGMT(ic, ni,
IEEE80211_FC0_SUBTYPE_AUTH,
IEEE80211_REASON_MIC_FAILURE);
}
#endif
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++;
#ifndef IEEE80211_NO_HOSTAP
if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
IEEE80211_SEND_MGMT(ic, ni,
IEEE80211_FC0_SUBTYPE_AUTH,
(seq+1) | (IEEE80211_STATUS_ALG<<16));
}
#endif
}
}
static void
ieee80211_recv_mgmt_assoc_req(struct ieee80211com *ic, struct mbuf *m0,
struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
struct ieee80211_frame *wh;
u_int8_t *frm, *efrm;
u_int8_t *ssid, *rates, *xrates, *wpa, *wme;
int reassoc, resp;
u_int8_t rate;
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
u_int16_t capinfo, lintval;
struct ieee80211_rsnparms rsn;
u_int8_t reason;
wh = mtod(m0, struct ieee80211_frame *);
frm = (u_int8_t *)(wh + 1);
efrm = mtod(m0, u_int8_t *) + m0->m_len;
if (ic->ic_opmode != IEEE80211_M_HOSTAP ||
ic->ic_state != IEEE80211_S_RUN) {
ic->ic_stats.is_rx_mgtdiscard++;
return;
}
if (subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) {
reassoc = 1;
resp = IEEE80211_FC0_SUBTYPE_REASSOC_RESP;
} else {
reassoc = 0;
resp = IEEE80211_FC0_SUBTYPE_ASSOC_RESP;
}
IEEE80211_VERIFY_LENGTH(efrm - frm, (reassoc ? 10 : 4));
if (!IEEE80211_ADDR_EQ(wh->i_addr3, ic->ic_bss->ni_bssid)) {
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(*(u_int16_t *)frm); frm += 2;
lintval = le16toh(*(u_int16_t *)frm); frm += 2;
if (reassoc)
frm += 6;
ssid = rates = xrates = wpa = wme = NULL;
while (frm + 1 < efrm) {
IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2);
switch (*frm) {
case IEEE80211_ELEMID_SSID:
ssid = frm;
break;
case IEEE80211_ELEMID_RATES:
rates = frm;
break;
case IEEE80211_ELEMID_XRATES:
xrates = frm;
break;
case IEEE80211_ELEMID_RSN:
wpa = frm;
break;
case IEEE80211_ELEMID_VENDOR:
if (iswpaoui(frm))
wpa = frm;
else if (iswmeinfo(frm))
wme = frm;
break;
}
frm += frm[1] + 2;
}
IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE);
IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN);
IEEE80211_VERIFY_SSID(ic->ic_bss, ssid);
if (ni == ic->ic_bss) {
IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY,
"[%s] deny %s request, sta not authenticated\n",
ether_snprintf(ebuf, sizeof(ebuf), 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;
}
if (wpa == NULL && (ic->ic_flags & IEEE80211_F_WPA)) {
IEEE80211_DPRINTF(ic,
IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA,
"[%s] no WPA/RSN IE in association request\n",
ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2));
IEEE80211_SEND_MGMT(ic, ni,
IEEE80211_FC0_SUBTYPE_DEAUTH,
IEEE80211_REASON_RSN_REQUIRED);
ieee80211_node_leave(ic, ni);
ic->ic_stats.is_rx_assoc_badwpaie++;
return;
}
if (wpa != NULL) {
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);
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_snprintf(ebuf, sizeof(ebuf), 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);
}
if (ni->ni_challenge != NULL) {
free(ni->ni_challenge, M_DEVBUF);
ni->ni_challenge = NULL;
}
if ((capinfo & IEEE80211_CAPINFO_ESS) == 0) {
IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY,
"[%s] deny %s request, capability mismatch 0x%x\n",
ether_snprintf(ebuf, sizeof(ebuf), 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;
}
rate = ieee80211_setup_rates(ni, rates, xrates,
IEEE80211_R_DOSORT | IEEE80211_R_DOFRATE |
IEEE80211_R_DONEGO | IEEE80211_R_DODEL);
if ((rate & IEEE80211_RATE_BASIC) ||
((ic->ic_flags & IEEE80211_F_PUREG) && rate < 48)) {
IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY,
"[%s] deny %s request, rate set mismatch\n",
ether_snprintf(ebuf, sizeof(ebuf), 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 = 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 (wpa != NULL) {
ni->ni_rsn = rsn;
ieee80211_saveie(&ni->ni_wpa_ie, wpa);
} else if (ni->ni_wpa_ie != NULL) {
free(ni->ni_wpa_ie, M_DEVBUF);
ni->ni_wpa_ie = NULL;
}
if (wme != NULL) {
ieee80211_saveie(&ni->ni_wme_ie, wme);
ni->ni_flags |= IEEE80211_NODE_QOS;
} else if (ni->ni_wme_ie != NULL) {
free(ni->ni_wme_ie, M_DEVBUF);
ni->ni_wme_ie = NULL;
ni->ni_flags &= ~IEEE80211_NODE_QOS;
}
ieee80211_node_join(ic, ni, resp);
}
#define ISREASSOC(_st) ((_st) == IEEE80211_FC0_SUBTYPE_REASSOC_RESP)
static void
ieee80211_recv_mgmt_assoc_resp(struct ieee80211com *ic, struct mbuf *m0,
struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
struct ieee80211_frame *wh;
u_int8_t *frm, *efrm;
u_int8_t *rates, *xrates, *wme;
u_int8_t rate;
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
u_int16_t capinfo, associd;
u_int16_t status;
wh = mtod(m0, struct ieee80211_frame *);
frm = (u_int8_t *)(wh + 1);
efrm = mtod(m0, u_int8_t *) + m0->m_len;
if (ic->ic_opmode != IEEE80211_M_STA ||
ic->ic_state != IEEE80211_S_ASSOC) {
ic->ic_stats.is_rx_mgtdiscard++;
return;
}
IEEE80211_VERIFY_LENGTH(efrm - frm, 6);
ni = ic->ic_bss;
capinfo = le16toh(*(u_int16_t *)frm);
frm += 2;
status = le16toh(*(u_int16_t *)frm);
frm += 2;
if (status != 0) {
IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC,
"[%s] %sassoc failed (reason %d)\n",
ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2),
ISREASSOC(subtype) ? "re" : "", status);
if (ni != ic->ic_bss)
ni->ni_fails++;
ic->ic_stats.is_rx_auth_fail++;
return;
}
associd = le16toh(*(u_int16_t *)frm);
frm += 2;
rates = xrates = wme = NULL;
while (frm + 1 < efrm) {
IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2);
switch (*frm) {
case IEEE80211_ELEMID_RATES:
rates = frm;
break;
case IEEE80211_ELEMID_XRATES:
xrates = frm;
break;
case IEEE80211_ELEMID_VENDOR:
if (iswmeoui(frm))
wme = frm;
break;
}
frm += frm[1] + 2;
}
IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE);
rate = ieee80211_setup_rates(ni, rates, xrates,
IEEE80211_R_DOSORT | IEEE80211_R_DOFRATE |
IEEE80211_R_DONEGO | IEEE80211_R_DODEL);
if (rate & IEEE80211_RATE_BASIC) {
IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC,
"[%s] %sassoc failed (rate set mismatch)\n",
ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2),
ISREASSOC(subtype) ? "re" : "");
if (ni != ic->ic_bss)
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;
}
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));
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,
"[%s] %sassoc success: %s preamble, %s slot time%s%s\n",
ether_snprintf(ebuf, sizeof(ebuf), 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);
}
static void
ieee80211_recv_mgmt_deauth(struct ieee80211com *ic, struct mbuf *m0,
struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
struct ieee80211_frame *wh;
u_int8_t *frm, *efrm;
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
u_int16_t reason;
wh = mtod(m0, struct ieee80211_frame *);
frm = (u_int8_t *)(wh + 1);
efrm = mtod(m0, u_int8_t *) + m0->m_len;
if (ic->ic_state == IEEE80211_S_SCAN) {
ic->ic_stats.is_rx_mgtdiscard++;
return;
}
IEEE80211_VERIFY_LENGTH(efrm - frm, 2);
reason = le16toh(*(u_int16_t *)frm);
__USE(reason);
ic->ic_stats.is_rx_deauth++;
IEEE80211_NODE_STAT(ni, rx_deauth);
if (!IEEE80211_ADDR_EQ(wh->i_addr1, ic->ic_myaddr)) {
ic->ic_stats.is_rx_mgtdiscard++;
return;
}
IEEE80211_DPRINTF(ic, IEEE80211_MSG_AUTH,
"[%s] recv deauthenticate (reason %d)\n",
ether_snprintf(ebuf, sizeof(ebuf), 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:
#ifndef IEEE80211_NO_HOSTAP
if (ni != ic->ic_bss)
ieee80211_node_leave(ic, ni);
#endif
break;
default:
ic->ic_stats.is_rx_mgtdiscard++;
break;
}
}
static void
ieee80211_recv_mgmt_disassoc(struct ieee80211com *ic, struct mbuf *m0,
struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
struct ieee80211_frame *wh;
u_int8_t *frm, *efrm;
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
u_int16_t reason;
wh = mtod(m0, struct ieee80211_frame *);
frm = (u_int8_t *)(wh + 1);
efrm = mtod(m0, u_int8_t *) + m0->m_len;
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;
}
IEEE80211_VERIFY_LENGTH(efrm - frm, 2);
reason = le16toh(*(u_int16_t *)frm);
__USE(reason);
ic->ic_stats.is_rx_disassoc++;
IEEE80211_NODE_STAT(ni, rx_disassoc);
if (!IEEE80211_ADDR_EQ(wh->i_addr1, ic->ic_myaddr)) {
ic->ic_stats.is_rx_mgtdiscard++;
return;
}
IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC,
"[%s] recv disassociate (reason %d)\n",
ether_snprintf(ebuf, sizeof(ebuf), 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:
#ifndef IEEE80211_NO_HOSTAP
if (ni != ic->ic_bss)
ieee80211_node_leave(ic, ni);
#endif
break;
default:
ic->ic_stats.is_rx_mgtdiscard++;
break;
}
}
#undef ISREASSOC
#undef IEEE80211_VERIFY_LENGTH
#undef IEEE80211_VERIFY_ELEMENT
void
ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0,
struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
struct ieee80211_frame *wh;
wh = mtod(m0, struct ieee80211_frame *);
switch (subtype) {
case IEEE80211_FC0_SUBTYPE_PROBE_RESP:
case IEEE80211_FC0_SUBTYPE_BEACON:
ieee80211_recv_mgmt_beacon(ic, m0, ni, subtype, rssi, rstamp);
return;
case IEEE80211_FC0_SUBTYPE_PROBE_REQ:
ieee80211_recv_mgmt_probe_req(ic, m0, ni, subtype, rssi, rstamp);
return;
case IEEE80211_FC0_SUBTYPE_AUTH:
ieee80211_recv_mgmt_auth(ic, m0, ni, subtype, rssi, rstamp);
return;
case IEEE80211_FC0_SUBTYPE_ASSOC_REQ:
case IEEE80211_FC0_SUBTYPE_REASSOC_REQ:
ieee80211_recv_mgmt_assoc_req(ic, m0, ni, subtype, rssi, rstamp);
return;
case IEEE80211_FC0_SUBTYPE_ASSOC_RESP:
case IEEE80211_FC0_SUBTYPE_REASSOC_RESP:
ieee80211_recv_mgmt_assoc_resp(ic, m0, ni, subtype, rssi, rstamp);
return;
case IEEE80211_FC0_SUBTYPE_DEAUTH:
ieee80211_recv_mgmt_deauth(ic, m0, ni, subtype, rssi, rstamp);
return;
case IEEE80211_FC0_SUBTYPE_DISASSOC:
ieee80211_recv_mgmt_disassoc(ic, m0, ni, subtype, rssi, rstamp);
return;
default:
IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY,
wh, "mgt", "subtype 0x%x not handled", subtype);
ic->ic_stats.is_rx_badsubtype++;
break;
}
}
#ifndef IEEE80211_NO_HOSTAP
static void
ieee80211_node_pwrsave(struct ieee80211_node *ni, int enable)
{
struct ieee80211com *ic = ni->ni_ic;
struct mbuf *m;
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
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_snprintf(ebuf, sizeof(ebuf), 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_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr), ic->ic_ps_sta);
if (IEEE80211_NODE_SAVEQ_QLEN(ni) == 0) {
if (ic->ic_set_tim != NULL)
ic->ic_set_tim(ni, 0);
return;
}
IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER,
"[%s] flush ps queue, %u packets queued\n",
ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr),
IEEE80211_NODE_SAVEQ_QLEN(ni));
for (;;) {
int qlen;
IEEE80211_NODE_SAVEQ_DEQUEUE(ni, m, qlen);
if (m == NULL)
break;
if (qlen != 0)
m->m_flags |= M_MORE_DATA;
IF_ENQUEUE(&ic->ic_ifp->if_snd, m);
}
if (ic->ic_set_tim != NULL)
ic->ic_set_tim(ni, 0);
}
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;
IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
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;
}
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_snprintf(ebuf, sizeof(ebuf), wh->i_addr2));
ieee80211_send_nulldata(ieee80211_ref_node(ni));
ic->ic_stats.is_ps_qempty++;
if (ic->ic_set_tim != NULL)
ic->ic_set_tim(ni, 0);
return;
}
if (qlen != 0) {
IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER,
"[%s] recv ps-poll, send packet, %u still queued\n",
ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr), qlen);
m->m_flags |= M_MORE_DATA;
} else {
IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER,
"[%s] recv ps-poll, send packet, queue empty\n",
ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr));
if (ic->ic_set_tim != NULL)
ic->ic_set_tim(ni, 0);
}
m->m_flags |= M_PWR_SAV;
IF_ENQUEUE(&ic->ic_ifp->if_snd, m);
}
#endif
#ifdef IEEE80211_DEBUG
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;
}
void
ieee80211_note(struct ieee80211com *ic, const char *fmt, ...)
{
char buf[128];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if_printf(ic->ic_ifp, "%s", buf);
}
void
ieee80211_note_mac(struct ieee80211com *ic,
const u_int8_t mac[IEEE80211_ADDR_LEN],
const char *fmt, ...)
{
char buf[128];
va_list ap;
char ebuf[3 * ETHER_ADDR_LEN];
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if_printf(ic->ic_ifp, "[%s] %s\n", ether_snprintf(ebuf, sizeof(ebuf),
mac), buf);
}
static void
ieee80211_discard_frame(struct ieee80211com *ic,
const struct ieee80211_frame *wh,
const char *type, const char *fmt, ...)
{
va_list ap;
char ebuf[3 * ETHER_ADDR_LEN];
printf("[%s:%s] discard ", ic->ic_ifp->if_xname,
ether_snprintf(ebuf, sizeof(ebuf), 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;
char ebuf[3 * ETHER_ADDR_LEN];
printf("[%s:%s] discard ", ic->ic_ifp->if_xname,
ether_snprintf(ebuf, sizeof(ebuf), 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;
char ebuf[3 * ETHER_ADDR_LEN];
printf("[%s:%s] discard ", ic->ic_ifp->if_xname,
ether_snprintf(ebuf, sizeof(ebuf), mac));
if (type != NULL)
printf("%s frame, ", type);
else
printf("frame, ");
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
printf("\n");
}
#endif