#include "bpfilter.h"
#include <sys/param.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/sysctl.h>
#include <sys/task.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_llc.h>
#if NBPFILTER > 0
#include <net/bpf.h>
#endif
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_priv.h>
struct mbuf *ieee80211_input_hwdecrypt(struct ieee80211com *,
struct ieee80211_node *, struct mbuf *,
struct ieee80211_rxinfo *rxi);
struct mbuf *ieee80211_defrag(struct ieee80211com *, struct mbuf *, int);
void ieee80211_defrag_timeout(void *);
void ieee80211_input_ba(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *, int, struct ieee80211_rxinfo *,
struct mbuf_list *);
void ieee80211_input_ba_flush(struct ieee80211com *, struct ieee80211_node *,
struct ieee80211_rx_ba *, struct mbuf_list *);
int ieee80211_input_ba_gap_skip(struct ieee80211_rx_ba *);
void ieee80211_input_ba_gap_timeout(void *arg);
void ieee80211_ba_move_window(struct ieee80211com *,
struct ieee80211_node *, u_int8_t, u_int16_t, struct mbuf_list *);
void ieee80211_input_ba_seq(struct ieee80211com *,
struct ieee80211_node *, uint8_t, uint16_t, struct mbuf_list *);
void ieee80211_decap(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *, int, struct mbuf_list *);
int ieee80211_amsdu_decap_validate(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *);
void ieee80211_amsdu_decap(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *, int, struct mbuf_list *);
void ieee80211_enqueue_data(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *, int, struct mbuf_list *);
int ieee80211_parse_edca_params_body(struct ieee80211com *,
const u_int8_t *);
int ieee80211_parse_edca_params(struct ieee80211com *, const u_int8_t *);
int ieee80211_parse_wmm_params(struct ieee80211com *, const u_int8_t *);
enum ieee80211_cipher ieee80211_parse_rsn_cipher(const u_int8_t *);
enum ieee80211_akm ieee80211_parse_rsn_akm(const u_int8_t *);
int ieee80211_parse_rsn_body(struct ieee80211com *, const u_int8_t *,
u_int, struct ieee80211_rsnparams *);
int ieee80211_save_ie(const u_int8_t *, u_int8_t **);
void ieee80211_recv_probe_resp(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *, struct ieee80211_rxinfo *, int);
#ifndef IEEE80211_STA_ONLY
void ieee80211_recv_probe_req(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *, struct ieee80211_rxinfo *);
#endif
void ieee80211_recv_auth(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *, struct ieee80211_rxinfo *);
#ifndef IEEE80211_STA_ONLY
void ieee80211_recv_assoc_req(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *, struct ieee80211_rxinfo *, int);
#endif
void ieee80211_recv_assoc_resp(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *, int);
void ieee80211_recv_deauth(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *);
void ieee80211_recv_disassoc(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *);
void ieee80211_recv_addba_req(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *);
void ieee80211_recv_addba_resp(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *);
void ieee80211_recv_delba(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *);
void ieee80211_recv_sa_query_req(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *);
#ifndef IEEE80211_STA_ONLY
void ieee80211_recv_sa_query_resp(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *);
#endif
void ieee80211_recv_action(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *);
#ifndef IEEE80211_STA_ONLY
void ieee80211_recv_pspoll(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *);
#endif
void ieee80211_recv_bar(struct ieee80211com *, struct mbuf *,
struct ieee80211_node *);
void ieee80211_bar_tid(struct ieee80211com *, struct ieee80211_node *,
u_int8_t, u_int16_t);
u_int
ieee80211_get_hdrlen(const struct ieee80211_frame *wh)
{
u_int size = sizeof(*wh);
KASSERT(ieee80211_has_seq(wh));
if (ieee80211_has_addr4(wh))
size += IEEE80211_ADDR_LEN;
if (ieee80211_has_qos(wh))
size += sizeof(u_int16_t);
if (ieee80211_has_htc(wh))
size += sizeof(u_int32_t);
return size;
}
struct mbuf *
ieee80211_input_hwdecrypt(struct ieee80211com *ic, struct ieee80211_node *ni,
struct mbuf *m, struct ieee80211_rxinfo *rxi)
{
struct ieee80211_key *k;
struct ieee80211_frame *wh;
uint64_t pn, *prsc;
int hdrlen;
k = ieee80211_get_rxkey(ic, m, ni);
if (k == NULL)
return NULL;
wh = mtod(m, struct ieee80211_frame *);
hdrlen = ieee80211_get_hdrlen(wh);
switch (k->k_cipher) {
case IEEE80211_CIPHER_CCMP:
if (!(wh->i_fc[1] & IEEE80211_FC1_PROTECTED)) {
break;
}
if (ieee80211_ccmp_get_pn(&pn, &prsc, m, k) != 0) {
ic->ic_stats.is_ccmp_dec_errs++;
return NULL;
}
if (rxi->rxi_flags & IEEE80211_RXI_HWDEC_SAME_PN) {
if (pn < *prsc) {
ic->ic_stats.is_ccmp_replays++;
return NULL;
}
} else if (pn <= *prsc) {
ic->ic_stats.is_ccmp_replays++;
return NULL;
}
*prsc = pn;
wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED;
memmove(mtod(m, caddr_t) + IEEE80211_CCMP_HDRLEN, wh, hdrlen);
m_adj(m, IEEE80211_CCMP_HDRLEN);
break;
case IEEE80211_CIPHER_TKIP:
if (!(wh->i_fc[1] & IEEE80211_FC1_PROTECTED)) {
break;
}
if (ieee80211_tkip_get_tsc(&pn, &prsc, m, k) != 0)
return NULL;
if (rxi->rxi_flags & IEEE80211_RXI_HWDEC_SAME_PN) {
if (pn < *prsc) {
ic->ic_stats.is_tkip_replays++;
return NULL;
}
} else if (pn <= *prsc) {
ic->ic_stats.is_tkip_replays++;
return NULL;
}
*prsc = pn;
wh = mtod(m, struct ieee80211_frame *);
wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED;
memmove(mtod(m, caddr_t) + IEEE80211_TKIP_HDRLEN, wh, hdrlen);
m_adj(m, IEEE80211_TKIP_HDRLEN);
break;
default:
break;
}
return m;
}
void
ieee80211_inputm(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni,
struct ieee80211_rxinfo *rxi, struct mbuf_list *ml)
{
struct ieee80211com *ic = (void *)ifp;
struct ieee80211_frame *wh;
u_int16_t *orxseq, nrxseq, qos;
u_int8_t dir, type, subtype, tid;
int hdrlen, hasqos;
KASSERT(ni != NULL);
if (ic->ic_opmode == IEEE80211_M_MONITOR)
goto out;
if (m->m_len < sizeof(struct ieee80211_frame_min)) {
DPRINTF(("frame too short, len %u\n", 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) {
DPRINTF(("frame with wrong version: %x\n", 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;
subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
if (type != IEEE80211_FC0_TYPE_CTL) {
hdrlen = ieee80211_get_hdrlen(wh);
if (m->m_len < hdrlen) {
DPRINTF(("frame too short, len %u\n", m->m_len));
ic->ic_stats.is_rx_tooshort++;
goto err;
}
} else
hdrlen = 0;
if ((hasqos = ieee80211_has_qos(wh))) {
qos = ieee80211_get_qos(wh);
tid = qos & IEEE80211_QOS_TID;
} else {
qos = 0;
tid = 0;
}
if (ic->ic_state == IEEE80211_S_RUN &&
type == IEEE80211_FC0_TYPE_DATA && hasqos &&
(subtype & IEEE80211_FC0_SUBTYPE_NODATA) == 0 &&
!(rxi->rxi_flags & IEEE80211_RXI_AMPDU_DONE)
#ifndef IEEE80211_STA_ONLY
&& (ic->ic_opmode == IEEE80211_M_STA || ni != ic->ic_bss)
#endif
) {
int ba_state = ni->ni_rx_ba[tid].ba_state;
#ifndef IEEE80211_STA_ONLY
if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
if (!IEEE80211_ADDR_EQ(wh->i_addr1,
ic->ic_bss->ni_bssid)) {
ic->ic_stats.is_rx_wrongbss++;
goto err;
}
if (ni->ni_state != IEEE80211_S_ASSOC) {
ic->ic_stats.is_rx_notassoc++;
goto err;
}
}
#endif
if ((qos & IEEE80211_QOS_ACK_POLICY_MASK) ==
IEEE80211_QOS_ACK_POLICY_BA &&
ba_state != IEEE80211_BA_AGREED) {
DPRINTF(("no BA agreement for %s, TID %d\n",
ether_sprintf(ni->ni_macaddr), tid));
IEEE80211_SEND_ACTION(ic, ni,
IEEE80211_CATEG_BA, IEEE80211_ACTION_DELBA,
IEEE80211_REASON_SETUP_REQUIRED << 16 | tid);
goto err;
}
if (ba_state == IEEE80211_BA_AGREED &&
((qos & IEEE80211_QOS_ACK_POLICY_MASK) ==
IEEE80211_QOS_ACK_POLICY_BA ||
(qos & IEEE80211_QOS_ACK_POLICY_MASK) ==
IEEE80211_QOS_ACK_POLICY_NORMAL)) {
ieee80211_input_ba(ic, m, ni, tid, rxi, ml);
return;
} else if (ba_state == IEEE80211_BA_REQUESTED &&
(qos & IEEE80211_QOS_ACK_POLICY_MASK) ==
IEEE80211_QOS_ACK_POLICY_NORMAL) {
ieee80211_input_ba(ic, m, ni, tid, rxi, ml);
return;
}
}
if (ieee80211_has_seq(wh)) {
uint16_t rxseq = letoh16(*(const u_int16_t *)wh->i_seq);
if ((wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG) ||
(rxseq & IEEE80211_SEQ_FRAG_MASK))
goto err;
}
if (ieee80211_has_seq(wh) &&
ic->ic_state != IEEE80211_S_SCAN) {
nrxseq = letoh16(*(u_int16_t *)wh->i_seq) >>
IEEE80211_SEQ_SEQ_SHIFT;
if (hasqos)
orxseq = &ni->ni_qos_rxseqs[tid];
else
orxseq = &ni->ni_rxseq;
if (rxi->rxi_flags & IEEE80211_RXI_SAME_SEQ) {
if (nrxseq != *orxseq) {
ic->ic_stats.is_rx_dup++;
goto out;
}
} else if ((wh->i_fc[1] & IEEE80211_FC1_RETRY) &&
nrxseq == *orxseq) {
ic->ic_stats.is_rx_dup++;
goto out;
}
*orxseq = nrxseq;
}
if (ic->ic_state > IEEE80211_S_SCAN) {
if (rxi->rxi_rssi != 0)
ni->ni_rssi = rxi->rxi_rssi;
ni->ni_rstamp = rxi->rxi_tstamp;
ni->ni_inact = 0;
if (ic->ic_state == IEEE80211_S_RUN && ic->ic_bgscan_start) {
if ((*ic->ic_node_checkrssi)(ic, ni))
timeout_del(&ic->ic_bgscan_timeout);
else if (!timeout_pending(&ic->ic_bgscan_timeout) &&
(ic->ic_flags & IEEE80211_F_BGSCAN) == 0 &&
(ic->ic_flags & IEEE80211_F_DESBSSID) == 0)
timeout_add_msec(&ic->ic_bgscan_timeout,
500 * (ic->ic_bgscan_fail + 1));
}
}
#ifndef IEEE80211_STA_ONLY
if (ic->ic_opmode == IEEE80211_M_HOSTAP &&
(ic->ic_caps & IEEE80211_C_APPMGT) &&
ni->ni_state == IEEE80211_STA_ASSOC) {
if (wh->i_fc[1] & IEEE80211_FC1_PWR_MGT) {
if (ni->ni_pwrsave == IEEE80211_PS_AWAKE) {
ni->ni_pwrsave = IEEE80211_PS_DOZE;
DPRINTF(("PS mode on for %s\n",
ether_sprintf(wh->i_addr2)));
}
} else if (ni->ni_pwrsave == IEEE80211_PS_DOZE) {
struct mbuf *m;
ni->ni_pwrsave = IEEE80211_PS_AWAKE;
DPRINTF(("PS mode off for %s\n",
ether_sprintf(wh->i_addr2)));
(*ic->ic_set_tim)(ic, ni->ni_associd, 0);
while ((m = mq_dequeue(&ni->ni_savedq)) != NULL) {
mq_enqueue(&ic->ic_pwrsaveq, m);
if_start(ifp);
}
}
}
#endif
switch (type) {
case IEEE80211_FC0_TYPE_DATA:
switch (ic->ic_opmode) {
case IEEE80211_M_STA:
if (dir != IEEE80211_FC1_DIR_FROMDS) {
ic->ic_stats.is_rx_wrongdir++;
goto out;
}
if (ic->ic_state != IEEE80211_S_SCAN &&
!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid)) {
DPRINTF(("discard frame from SA %s\n",
ether_sprintf(wh->i_addr2)));
ic->ic_stats.is_rx_wrongbss++;
goto out;
}
if ((ifp->if_flags & IFF_SIMPLEX) &&
IEEE80211_IS_MULTICAST(wh->i_addr1) &&
IEEE80211_ADDR_EQ(wh->i_addr3, ic->ic_myaddr)) {
ic->ic_stats.is_rx_mcastecho++;
goto out;
}
break;
#ifndef IEEE80211_STA_ONLY
case IEEE80211_M_IBSS:
case IEEE80211_M_AHDEMO:
if (dir != IEEE80211_FC1_DIR_NODS) {
ic->ic_stats.is_rx_wrongdir++;
goto out;
}
if (ic->ic_state != IEEE80211_S_SCAN &&
!IEEE80211_ADDR_EQ(wh->i_addr3,
ic->ic_bss->ni_bssid) &&
!IEEE80211_ADDR_EQ(wh->i_addr3,
etherbroadcastaddr)) {
DPRINTF(("discard data frame to DA %s\n",
ether_sprintf(wh->i_addr3)));
ic->ic_stats.is_rx_wrongbss++;
goto out;
}
break;
case IEEE80211_M_HOSTAP:
if (dir != IEEE80211_FC1_DIR_TODS) {
ic->ic_stats.is_rx_wrongdir++;
goto out;
}
if (ic->ic_state != IEEE80211_S_SCAN &&
!IEEE80211_ADDR_EQ(wh->i_addr1,
ic->ic_bss->ni_bssid) &&
!IEEE80211_ADDR_EQ(wh->i_addr1,
etherbroadcastaddr)) {
DPRINTF(("discard data frame to BSS %s\n",
ether_sprintf(wh->i_addr1)));
ic->ic_stats.is_rx_wrongbss++;
goto out;
}
if (ni == ic->ic_bss) {
DPRINTF(("data from unknown src %s\n",
ether_sprintf(wh->i_addr2)));
ni = ieee80211_find_node(ic, wh->i_addr2);
if (ni == NULL)
ni = ieee80211_dup_bss(ic, wh->i_addr2);
if (ni != NULL) {
IEEE80211_SEND_MGMT(ic, ni,
IEEE80211_FC0_SUBTYPE_DEAUTH,
IEEE80211_REASON_NOT_AUTHED);
}
ic->ic_stats.is_rx_notassoc++;
goto err;
}
if (ni->ni_state != IEEE80211_STA_ASSOC) {
DPRINTF(("data from unassoc src %s\n",
ether_sprintf(wh->i_addr2)));
IEEE80211_SEND_MGMT(ic, ni,
IEEE80211_FC0_SUBTYPE_DISASSOC,
IEEE80211_REASON_NOT_ASSOCED);
ic->ic_stats.is_rx_notassoc++;
goto err;
}
break;
#endif
default:
goto out;
}
if (subtype & IEEE80211_FC0_SUBTYPE_NODATA) {
#if NBPFILTER > 0
if (ic->ic_rawbpf)
bpf_mtap(ic->ic_rawbpf, m, BPF_DIRECTION_IN);
#endif
goto out;
}
if ((ic->ic_flags & IEEE80211_F_WEPON) ||
((ic->ic_flags & IEEE80211_F_RSNON) &&
(ni->ni_flags & IEEE80211_NODE_RXPROT))) {
if (!(rxi->rxi_flags & IEEE80211_RXI_HWDEC)) {
if (!(wh->i_fc[1] & IEEE80211_FC1_PROTECTED)) {
ic->ic_stats.is_rx_unencrypted++;
goto err;
}
m = ieee80211_decrypt(ic, m, ni);
if (m == NULL) {
ic->ic_stats.is_rx_wepfail++;
goto err;
}
} else {
m = ieee80211_input_hwdecrypt(ic, ni, m, rxi);
if (m == NULL)
goto err;
}
wh = mtod(m, struct ieee80211_frame *);
} else if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) ||
(rxi->rxi_flags & IEEE80211_RXI_HWDEC)) {
ic->ic_stats.is_rx_nowep++;
goto out;
}
#if NBPFILTER > 0
if (ic->ic_rawbpf)
bpf_mtap(ic->ic_rawbpf, m, BPF_DIRECTION_IN);
#endif
if ((ni->ni_flags & IEEE80211_NODE_HT) &&
hasqos && (qos & IEEE80211_QOS_AMSDU))
ieee80211_amsdu_decap(ic, m, ni, hdrlen, ml);
else
ieee80211_decap(ic, m, ni, hdrlen, ml);
return;
case IEEE80211_FC0_TYPE_MGT:
if (dir != IEEE80211_FC1_DIR_NODS) {
ic->ic_stats.is_rx_wrongdir++;
goto err;
}
#ifndef IEEE80211_STA_ONLY
if (ic->ic_opmode == IEEE80211_M_AHDEMO) {
ic->ic_stats.is_rx_ahdemo_mgt++;
goto out;
}
#endif
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++;
goto out;
}
}
if (ni->ni_flags & IEEE80211_NODE_RXMGMTPROT) {
int is_multicast, is_protected;
is_multicast = IEEE80211_IS_MULTICAST(wh->i_addr1);
is_protected = (wh->i_fc[1] & IEEE80211_FC1_PROTECTED);
if (subtype == IEEE80211_FC0_SUBTYPE_DISASSOC ||
subtype == IEEE80211_FC0_SUBTYPE_DEAUTH ||
subtype == IEEE80211_FC0_SUBTYPE_ACTION) {
if (rxi->rxi_flags & IEEE80211_RXI_HWDEC) {
m = ieee80211_input_hwdecrypt(ic, ni,
m, rxi);
if (m == NULL)
goto out;
} else if (!is_multicast && !is_protected) {
ic->ic_stats.is_rx_unencrypted++;
goto out;
} else {
m = ieee80211_decrypt(ic, m, ni);
if (m == NULL) {
ic->ic_stats.is_rx_wepfail++;
goto out;
}
}
wh = mtod(m, struct ieee80211_frame *);
}
} else if ((ic->ic_flags & IEEE80211_F_RSNON) &&
((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) ||
(rxi->rxi_flags & IEEE80211_RXI_HWDEC))) {
ic->ic_stats.is_rx_nowep++;
goto out;
}
#if NBPFILTER > 0
if (bpf_mtap(ic->ic_rawbpf, m, BPF_DIRECTION_IN) != 0) {
m_freem(m);
return;
}
#endif
(*ic->ic_recv_mgmt)(ic, m, ni, rxi, subtype);
m_freem(m);
return;
case IEEE80211_FC0_TYPE_CTL:
switch (subtype) {
#ifndef IEEE80211_STA_ONLY
case IEEE80211_FC0_SUBTYPE_PS_POLL:
ieee80211_recv_pspoll(ic, m, ni);
break;
#endif
case IEEE80211_FC0_SUBTYPE_BAR:
ieee80211_recv_bar(ic, m, ni);
break;
default:
ic->ic_stats.is_rx_ctl++;
break;
}
goto out;
default:
DPRINTF(("bad frame type %x\n", type));
break;
}
err:
ifp->if_ierrors++;
out:
if (m != NULL) {
#if NBPFILTER > 0
if (ic->ic_rawbpf)
bpf_mtap(ic->ic_rawbpf, m, BPF_DIRECTION_IN);
#endif
m_freem(m);
}
}
void
ieee80211_input(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni,
struct ieee80211_rxinfo *rxi)
{
struct mbuf_list ml = MBUF_LIST_INITIALIZER();
ieee80211_inputm(ifp, m, ni, rxi, &ml);
if_input(ifp, &ml);
}
#ifdef notyet
struct mbuf *
ieee80211_defrag(struct ieee80211com *ic, struct mbuf *m, int hdrlen)
{
const struct ieee80211_frame *owh, *wh;
struct ieee80211_defrag *df;
u_int16_t rxseq, seq;
u_int8_t frag;
int i;
wh = mtod(m, struct ieee80211_frame *);
rxseq = letoh16(*(const u_int16_t *)wh->i_seq);
seq = rxseq >> IEEE80211_SEQ_SEQ_SHIFT;
frag = rxseq & IEEE80211_SEQ_FRAG_MASK;
if (frag == 0 && !(wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG))
return m;
if (frag == 0) {
if (++ic->ic_defrag_cur == IEEE80211_DEFRAG_SIZE)
ic->ic_defrag_cur = 0;
df = &ic->ic_defrag[ic->ic_defrag_cur];
m_freem(df->df_m);
df->df_seq = seq;
df->df_frag = 0;
df->df_m = m;
timeout_add_sec(&df->df_to, 1);
return NULL;
}
for (i = 0; i < IEEE80211_DEFRAG_SIZE; i++) {
df = &ic->ic_defrag[i];
if (df->df_m == NULL)
continue;
if (df->df_seq != seq || df->df_frag + 1 != frag)
continue;
owh = mtod(df->df_m, struct ieee80211_frame *);
if (((wh->i_fc[0] ^ owh->i_fc[0]) & IEEE80211_FC0_TYPE_MASK) ||
!IEEE80211_ADDR_EQ(wh->i_addr1, owh->i_addr1) ||
!IEEE80211_ADDR_EQ(wh->i_addr2, owh->i_addr2))
continue;
break;
}
if (i == IEEE80211_DEFRAG_SIZE) {
ic->ic_if.if_ierrors++;
m_freem(m);
return NULL;
}
df->df_frag = frag;
m_adj(m, hdrlen);
m_cat(df->df_m, m);
df->df_m->m_pkthdr.len += m->m_pkthdr.len;
if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG)
return NULL;
timeout_del(&df->df_to);
m = df->df_m;
df->df_m = NULL;
return m;
}
void
ieee80211_defrag_timeout(void *arg)
{
struct ieee80211_defrag *df = arg;
int s = splnet();
m_freem(df->df_m);
df->df_m = NULL;
splx(s);
}
#endif
void
ieee80211_input_ba(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni, int tid, struct ieee80211_rxinfo *rxi,
struct mbuf_list *ml)
{
struct ifnet *ifp = &ic->ic_if;
struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid];
struct ieee80211_frame *wh;
int idx, count;
u_int16_t sn;
wh = mtod(m, struct ieee80211_frame *);
sn = letoh16(*(u_int16_t *)wh->i_seq) >> IEEE80211_SEQ_SEQ_SHIFT;
if (ba->ba_timeout_val != 0)
timeout_add_usec(&ba->ba_to, ba->ba_timeout_val);
if (SEQ_LT(sn, ba->ba_winstart)) {
ic->ic_stats.is_ht_rx_frame_below_ba_winstart++;
m_freem(m);
return;
}
if (SEQ_LT(ba->ba_winend, sn)) {
ic->ic_stats.is_ht_rx_frame_above_ba_winend++;
count = (sn - ba->ba_winend) & 0xfff;
if (count > ba->ba_winsize) {
if (ba->ba_winmiss < IEEE80211_BA_MAX_WINMISS) {
if (ba->ba_missedsn == ((sn - 1) & 0xfff))
ba->ba_winmiss++;
else
ba->ba_winmiss = 0;
ba->ba_missedsn = sn;
ifp->if_ierrors++;
m_freem(m);
return;
}
ic->ic_stats.is_ht_rx_ba_window_jump++;
ba->ba_winmiss = 0;
ba->ba_missedsn = 0;
ieee80211_ba_move_window(ic, ni, tid, sn, ml);
} else {
ic->ic_stats.is_ht_rx_ba_window_slide++;
ieee80211_input_ba_seq(ic, ni, tid,
(ba->ba_winstart + count) & 0xfff, ml);
ieee80211_input_ba_flush(ic, ni, ba, ml);
}
}
ba->ba_winmiss = 0;
ba->ba_missedsn = 0;
idx = (sn - ba->ba_winstart) & 0xfff;
idx = (ba->ba_head + idx) % IEEE80211_BA_MAX_WINSZ;
if (ba->ba_buf[idx].m != NULL) {
ifp->if_ierrors++;
ic->ic_stats.is_ht_rx_ba_no_buf++;
m_freem(m);
return;
}
ba->ba_buf[idx].m = m;
rxi->rxi_flags |= IEEE80211_RXI_AMPDU_DONE;
ba->ba_buf[idx].rxi = *rxi;
ba->ba_gapwait++;
if (ba->ba_buf[ba->ba_head].m == NULL && ba->ba_gapwait == 1)
timeout_add_msec(&ba->ba_gap_to, IEEE80211_BA_GAP_TIMEOUT);
ieee80211_input_ba_flush(ic, ni, ba, ml);
}
void
ieee80211_input_ba_seq(struct ieee80211com *ic, struct ieee80211_node *ni,
uint8_t tid, uint16_t max_seq, struct mbuf_list *ml)
{
struct ifnet *ifp = &ic->ic_if;
struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid];
struct ieee80211_frame *wh;
uint16_t seq;
int i = 0;
while (i++ < ba->ba_winsize) {
if (ba->ba_buf[ba->ba_head].m != NULL) {
wh = mtod(ba->ba_buf[ba->ba_head].m,
struct ieee80211_frame *);
KASSERT(ieee80211_has_seq(wh));
seq = letoh16(*(u_int16_t *)wh->i_seq) >>
IEEE80211_SEQ_SEQ_SHIFT;
if (!SEQ_LT(seq, max_seq))
break;
ieee80211_inputm(ifp, ba->ba_buf[ba->ba_head].m,
ni, &ba->ba_buf[ba->ba_head].rxi, ml);
ba->ba_buf[ba->ba_head].m = NULL;
ba->ba_gapwait--;
} else
ic->ic_stats.is_ht_rx_ba_frame_lost++;
ba->ba_head = (ba->ba_head + 1) % IEEE80211_BA_MAX_WINSZ;
ba->ba_winstart = (ba->ba_winstart + 1) & 0xfff;
}
ba->ba_winend = (ba->ba_winstart + ba->ba_winsize - 1) & 0xfff;
}
void
ieee80211_input_ba_flush(struct ieee80211com *ic, struct ieee80211_node *ni,
struct ieee80211_rx_ba *ba, struct mbuf_list *ml)
{
struct ifnet *ifp = &ic->ic_if;
if (ba->ba_buf[ba->ba_head].m == NULL)
return;
while (ba->ba_buf[ba->ba_head].m != NULL) {
ieee80211_inputm(ifp, ba->ba_buf[ba->ba_head].m, ni,
&ba->ba_buf[ba->ba_head].rxi, ml);
ba->ba_buf[ba->ba_head].m = NULL;
ba->ba_gapwait--;
ba->ba_head = (ba->ba_head + 1) % IEEE80211_BA_MAX_WINSZ;
ba->ba_winstart = (ba->ba_winstart + 1) & 0xfff;
}
ba->ba_winend = (ba->ba_winstart + ba->ba_winsize - 1) & 0xfff;
if (timeout_pending(&ba->ba_gap_to))
timeout_del(&ba->ba_gap_to);
if (ba->ba_gapwait)
timeout_add_msec(&ba->ba_gap_to, IEEE80211_BA_GAP_TIMEOUT);
}
int
ieee80211_input_ba_gap_skip(struct ieee80211_rx_ba *ba)
{
int skipped = 0;
while (skipped < ba->ba_winsize && ba->ba_buf[ba->ba_head].m == NULL) {
ba->ba_head = (ba->ba_head + 1) % IEEE80211_BA_MAX_WINSZ;
ba->ba_winstart = (ba->ba_winstart + 1) & 0xfff;
skipped++;
}
if (skipped > 0)
ba->ba_winend = (ba->ba_winstart + ba->ba_winsize - 1) & 0xfff;
return skipped;
}
void
ieee80211_input_ba_gap_timeout(void *arg)
{
struct ieee80211_rx_ba *ba = arg;
struct ieee80211_node *ni = ba->ba_ni;
struct ieee80211com *ic = ni->ni_ic;
int s, skipped;
ic->ic_stats.is_ht_rx_ba_window_gap_timeout++;
s = splnet();
skipped = ieee80211_input_ba_gap_skip(ba);
ic->ic_stats.is_ht_rx_ba_frame_lost += skipped;
if (skipped) {
struct mbuf_list ml = MBUF_LIST_INITIALIZER();
ieee80211_input_ba_flush(ic, ni, ba, &ml);
if_input(&ic->ic_if, &ml);
}
splx(s);
}
void
ieee80211_ba_move_window(struct ieee80211com *ic, struct ieee80211_node *ni,
u_int8_t tid, u_int16_t ssn, struct mbuf_list *ml)
{
struct ifnet *ifp = &ic->ic_if;
struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid];
int count;
count = (ssn - ba->ba_winstart) & 0xfff;
if (count > ba->ba_winsize)
count = ba->ba_winsize;
while (count-- > 0) {
if (ba->ba_buf[ba->ba_head].m != NULL) {
ieee80211_inputm(ifp, ba->ba_buf[ba->ba_head].m, ni,
&ba->ba_buf[ba->ba_head].rxi, ml);
ba->ba_buf[ba->ba_head].m = NULL;
ba->ba_gapwait--;
} else
ic->ic_stats.is_ht_rx_ba_frame_lost++;
ba->ba_head = (ba->ba_head + 1) % IEEE80211_BA_MAX_WINSZ;
}
ba->ba_winstart = ssn;
ba->ba_winend = (ba->ba_winstart + ba->ba_winsize - 1) & 0xfff;
ieee80211_input_ba_flush(ic, ni, ba, ml);
}
void
ieee80211_enqueue_data(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni, int mcast, struct mbuf_list *ml)
{
struct ifnet *ifp = &ic->ic_if;
struct ether_header *eh;
struct mbuf *m1;
eh = mtod(m, struct ether_header *);
if ((ic->ic_flags & IEEE80211_F_RSNON) && !ni->ni_port_valid &&
eh->ether_type != htons(ETHERTYPE_EAPOL)) {
DPRINTF(("port not valid: %s\n",
ether_sprintf(eh->ether_dhost)));
ic->ic_stats.is_rx_unauth++;
m_freem(m);
return;
}
m1 = NULL;
#ifndef IEEE80211_STA_ONLY
if (ic->ic_opmode == IEEE80211_M_HOSTAP &&
!(ic->ic_userflags & IEEE80211_F_NOBRIDGE) &&
eh->ether_type != htons(ETHERTYPE_EAPOL)) {
struct ieee80211_node *ni1;
if (ETHER_IS_MULTICAST(eh->ether_dhost)) {
m1 = m_dup_pkt(m, ETHER_ALIGN, M_DONTWAIT);
if (m1 == NULL)
ifp->if_oerrors++;
else
m1->m_flags |= M_MCAST;
} else if (!mcast) {
ni1 = ieee80211_find_node(ic, eh->ether_dhost);
if (ni1 != NULL &&
ni1->ni_state == IEEE80211_STA_ASSOC) {
m1 = m;
m = NULL;
}
}
if (m1 != NULL) {
if (if_enqueue(ifp, m1))
ifp->if_oerrors++;
}
}
#endif
if (m != NULL) {
if ((ic->ic_flags & IEEE80211_F_RSNON) &&
eh->ether_type == htons(ETHERTYPE_EAPOL)) {
ifp->if_ipackets++;
#if NBPFILTER > 0
if (ifp->if_bpf && m1 == NULL)
bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_IN);
#endif
ieee80211_eapol_key_input(ic, m, ni);
} else {
ml_enqueue(ml, m);
}
}
}
void
ieee80211_decap(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni, int hdrlen, struct mbuf_list *ml)
{
struct ether_header eh;
struct ieee80211_frame *wh;
struct llc *llc;
int mcast;
if (m->m_len < hdrlen + LLC_SNAPFRAMELEN &&
(m = m_pullup(m, hdrlen + LLC_SNAPFRAMELEN)) == NULL) {
ic->ic_stats.is_rx_decap++;
return;
}
wh = mtod(m, struct ieee80211_frame *);
mcast = IEEE80211_IS_MULTICAST(wh->i_addr1);
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,
((struct ieee80211_frame_addr4 *)wh)->i_addr4);
break;
}
llc = (struct llc *)((caddr_t)wh + 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) {
eh.ether_type = llc->llc_snap.ether_type;
m_adj(m, hdrlen + LLC_SNAPFRAMELEN - ETHER_HDR_LEN);
} else {
eh.ether_type = htons(m->m_pkthdr.len - hdrlen);
m_adj(m, hdrlen - ETHER_HDR_LEN);
}
memcpy(mtod(m, caddr_t), &eh, ETHER_HDR_LEN);
if (!ALIGNED_POINTER(mtod(m, caddr_t) + ETHER_HDR_LEN, u_int32_t)) {
struct mbuf *m0 = m;
m = m_dup_pkt(m0, ETHER_ALIGN, M_NOWAIT);
m_freem(m0);
if (m == NULL) {
ic->ic_stats.is_rx_decap++;
return;
}
}
ieee80211_enqueue_data(ic, m, ni, mcast, ml);
}
int
ieee80211_amsdu_decap_validate(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni)
{
struct ether_header *eh = mtod(m, struct ether_header *);
const uint8_t llc_hdr_mac[ETHER_ADDR_LEN] = {
LLC_SNAP_LSAP, LLC_SNAP_LSAP, LLC_UI, 0, 0, 0
};
if (ETHER_IS_EQ(eh->ether_dhost, llc_hdr_mac))
return 1;
switch (ic->ic_opmode) {
#ifndef IEEE80211_STA_ONLY
case IEEE80211_M_HOSTAP:
if (!ETHER_IS_EQ(ni->ni_macaddr, eh->ether_shost))
return 1;
break;
#endif
case IEEE80211_M_STA:
if (!ETHER_IS_EQ(ic->ic_myaddr, eh->ether_dhost))
return 1;
break;
default:
break;
}
return 0;
}
void
ieee80211_amsdu_decap(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni, int hdrlen, struct mbuf_list *ml)
{
struct mbuf *n;
struct ether_header *eh;
struct llc *llc;
int len, pad, mcast;
struct ieee80211_frame *wh;
struct mbuf_list subframes = MBUF_LIST_INITIALIZER();
wh = mtod(m, struct ieee80211_frame *);
mcast = IEEE80211_IS_MULTICAST(wh->i_addr1);
m_adj(m, hdrlen);
while (m->m_pkthdr.len >= ETHER_HDR_LEN + LLC_SNAPFRAMELEN) {
m = m_pullup(m, ETHER_HDR_LEN + LLC_SNAPFRAMELEN);
if (m == NULL)
break;
eh = mtod(m, struct ether_header *);
len = ntohs(eh->ether_type);
if (len < LLC_SNAPFRAMELEN) {
DPRINTF(("A-MSDU subframe too short (%d)\n", len));
ic->ic_stats.is_rx_decap++;
ml_purge(&subframes);
m_freem(m);
return;
}
llc = (struct llc *)&eh[1];
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) {
eh->ether_type = llc->llc_snap.ether_type;
memmove((u_int8_t *)eh + LLC_SNAPFRAMELEN, eh,
ETHER_HDR_LEN);
m_adj(m, LLC_SNAPFRAMELEN);
len -= LLC_SNAPFRAMELEN;
}
len += ETHER_HDR_LEN;
if (len > m->m_pkthdr.len) {
DPRINTF(("A-MSDU subframe too long (%d)\n", len));
ic->ic_stats.is_rx_decap++;
ml_purge(&subframes);
m_freem(m);
return;
}
n = m_split(m, len, M_NOWAIT);
if (n == NULL) {
ic->ic_stats.is_rx_decap++;
ml_purge(&subframes);
m_freem(m);
return;
}
if (ieee80211_amsdu_decap_validate(ic, m, ni)) {
ic->ic_stats.is_rx_decap++;
ml_purge(&subframes);
m_freem(m);
return;
}
ml_enqueue(&subframes, m);
m = n;
pad = ((len + 3) & ~3) - len;
m_adj(m, pad);
}
while ((n = ml_dequeue(&subframes)) != NULL)
ieee80211_enqueue_data(ic, n, ni, mcast, ml);
m_freem(m);
}
int
ieee80211_parse_edca_params_body(struct ieee80211com *ic, const u_int8_t *frm)
{
u_int updtcount;
int aci;
updtcount = frm[0] & 0xf;
if (updtcount == ic->ic_edca_updtcount)
return 0;
ic->ic_edca_updtcount = updtcount;
frm += 2;
for (aci = 0; aci < EDCA_NUM_AC; aci++) {
struct ieee80211_edca_ac_params *ac = &ic->ic_edca_ac[aci];
ac->ac_acm = (frm[0] >> 4) & 0x1;
ac->ac_aifsn = frm[0] & 0xf;
ac->ac_ecwmin = frm[1] & 0xf;
ac->ac_ecwmax = frm[1] >> 4;
ac->ac_txoplimit = LE_READ_2(frm + 2);
frm += 4;
}
if ((ic->ic_flags & IEEE80211_F_QOS) && ic->ic_updateedca != NULL)
(*ic->ic_updateedca)(ic);
return 0;
}
int
ieee80211_parse_edca_params(struct ieee80211com *ic, const u_int8_t *frm)
{
if (frm[1] < 18) {
ic->ic_stats.is_rx_elem_toosmall++;
return IEEE80211_REASON_IE_INVALID;
}
return ieee80211_parse_edca_params_body(ic, frm + 2);
}
int
ieee80211_parse_wmm_params(struct ieee80211com *ic, const u_int8_t *frm)
{
if (frm[1] < 24) {
ic->ic_stats.is_rx_elem_toosmall++;
return IEEE80211_REASON_IE_INVALID;
}
return ieee80211_parse_edca_params_body(ic, frm + 8);
}
enum ieee80211_cipher
ieee80211_parse_rsn_cipher(const u_int8_t selector[4])
{
if (memcmp(selector, MICROSOFT_OUI, 3) == 0) {
switch (selector[3]) {
case 0:
return IEEE80211_CIPHER_USEGROUP;
case 1:
return IEEE80211_CIPHER_WEP40;
case 2:
return IEEE80211_CIPHER_TKIP;
case 4:
return IEEE80211_CIPHER_CCMP;
case 5:
return IEEE80211_CIPHER_WEP104;
}
} else if (memcmp(selector, IEEE80211_OUI, 3) == 0) {
switch (selector[3]) {
case 0:
return IEEE80211_CIPHER_USEGROUP;
case 1:
return IEEE80211_CIPHER_WEP40;
case 2:
return IEEE80211_CIPHER_TKIP;
case 4:
return IEEE80211_CIPHER_CCMP;
case 5:
return IEEE80211_CIPHER_WEP104;
case 6:
return IEEE80211_CIPHER_BIP;
}
}
return IEEE80211_CIPHER_NONE;
}
enum ieee80211_akm
ieee80211_parse_rsn_akm(const u_int8_t selector[4])
{
if (memcmp(selector, MICROSOFT_OUI, 3) == 0) {
switch (selector[3]) {
case 1:
return IEEE80211_AKM_8021X;
case 2:
return IEEE80211_AKM_PSK;
}
} else if (memcmp(selector, IEEE80211_OUI, 3) == 0) {
switch (selector[3]) {
case 1:
return IEEE80211_AKM_8021X;
case 2:
return IEEE80211_AKM_PSK;
case 5:
return IEEE80211_AKM_SHA256_8021X;
case 6:
return IEEE80211_AKM_SHA256_PSK;
case 8:
return IEEE80211_AKM_SAE;
}
}
return IEEE80211_AKM_NONE;
}
int
ieee80211_parse_rsn_body(struct ieee80211com *ic, const u_int8_t *frm,
u_int len, struct ieee80211_rsnparams *rsn)
{
const u_int8_t *efrm;
u_int16_t m, n, s;
efrm = frm + len;
if (LE_READ_2(frm) != 1)
return IEEE80211_STATUS_RSN_IE_VER_UNSUP;
frm += 2;
rsn->rsn_groupcipher = IEEE80211_CIPHER_CCMP;
rsn->rsn_nciphers = 1;
rsn->rsn_ciphers = IEEE80211_CIPHER_CCMP;
rsn->rsn_groupmgmtcipher = IEEE80211_CIPHER_BIP;
rsn->rsn_nakms = 1;
rsn->rsn_akms = IEEE80211_AKM_8021X;
rsn->rsn_caps = 0;
rsn->rsn_npmkids = 0;
if (frm + 4 > efrm)
return 0;
rsn->rsn_groupcipher = ieee80211_parse_rsn_cipher(frm);
if (rsn->rsn_groupcipher == IEEE80211_CIPHER_NONE ||
rsn->rsn_groupcipher == IEEE80211_CIPHER_USEGROUP ||
rsn->rsn_groupcipher == IEEE80211_CIPHER_BIP)
return IEEE80211_STATUS_BAD_GROUP_CIPHER;
frm += 4;
if (frm + 2 > efrm)
return 0;
m = rsn->rsn_nciphers = LE_READ_2(frm);
frm += 2;
if (frm + m * 4 > efrm)
return IEEE80211_STATUS_IE_INVALID;
rsn->rsn_ciphers = IEEE80211_CIPHER_NONE;
while (m-- > 0) {
rsn->rsn_ciphers |= ieee80211_parse_rsn_cipher(frm);
frm += 4;
}
if (rsn->rsn_ciphers & IEEE80211_CIPHER_USEGROUP) {
if (rsn->rsn_ciphers != IEEE80211_CIPHER_USEGROUP)
return IEEE80211_STATUS_BAD_PAIRWISE_CIPHER;
if (rsn->rsn_groupcipher == IEEE80211_CIPHER_CCMP)
return IEEE80211_STATUS_BAD_PAIRWISE_CIPHER;
}
if (frm + 2 > efrm)
return 0;
n = rsn->rsn_nakms = LE_READ_2(frm);
frm += 2;
if (frm + n * 4 > efrm)
return IEEE80211_STATUS_IE_INVALID;
rsn->rsn_akms = IEEE80211_AKM_NONE;
while (n-- > 0) {
rsn->rsn_akms |= ieee80211_parse_rsn_akm(frm);
frm += 4;
}
if (frm + 2 > efrm)
return 0;
rsn->rsn_caps = LE_READ_2(frm);
frm += 2;
if (frm + 2 > efrm)
return 0;
s = rsn->rsn_npmkids = LE_READ_2(frm);
frm += 2;
if (frm + s * IEEE80211_PMKID_LEN > efrm)
return IEEE80211_STATUS_IE_INVALID;
if (s != 0) {
rsn->rsn_pmkids = frm;
frm += s * IEEE80211_PMKID_LEN;
}
if (frm + 4 > efrm)
return 0;
rsn->rsn_groupmgmtcipher = ieee80211_parse_rsn_cipher(frm);
if (rsn->rsn_groupmgmtcipher != IEEE80211_CIPHER_BIP)
return IEEE80211_STATUS_BAD_GROUP_CIPHER;
return IEEE80211_STATUS_SUCCESS;
}
int
ieee80211_parse_rsn(struct ieee80211com *ic, const u_int8_t *frm,
struct ieee80211_rsnparams *rsn)
{
if (frm[1] < 2) {
ic->ic_stats.is_rx_elem_toosmall++;
return IEEE80211_STATUS_IE_INVALID;
}
return ieee80211_parse_rsn_body(ic, frm + 2, frm[1], rsn);
}
int
ieee80211_parse_wpa(struct ieee80211com *ic, const u_int8_t *frm,
struct ieee80211_rsnparams *rsn)
{
if (frm[1] < 6) {
ic->ic_stats.is_rx_elem_toosmall++;
return IEEE80211_STATUS_IE_INVALID;
}
return ieee80211_parse_rsn_body(ic, frm + 6, frm[1] - 4, rsn);
}
int
ieee80211_save_ie(const u_int8_t *frm, u_int8_t **ie)
{
int olen = *ie ? 2 + (*ie)[1] : 0;
int len = 2 + frm[1];
if (*ie == NULL || olen != len) {
if (*ie != NULL)
free(*ie, M_DEVBUF, olen);
*ie = malloc(len, M_DEVBUF, M_NOWAIT);
if (*ie == NULL)
return ENOMEM;
}
memcpy(*ie, frm, len);
return 0;
}
void
ieee80211_recv_probe_resp(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *rni, struct ieee80211_rxinfo *rxi, int isprobe)
{
struct ieee80211_node *ni;
const struct ieee80211_frame *wh;
const u_int8_t *frm, *efrm;
const u_int8_t *tstamp, *ssid, *rates, *xrates, *edcaie, *wmmie, *tim;
const u_int8_t *rsnie, *wpaie, *htcaps, *htop, *vhtcaps, *vhtop;
u_int16_t capinfo, bintval;
u_int8_t chan, bchan, erp;
int is_new;
#ifdef DIAGNOSTIC
if (ic->ic_opmode != IEEE80211_M_STA &&
#ifndef IEEE80211_STA_ONLY
ic->ic_opmode != IEEE80211_M_IBSS &&
ic->ic_opmode != IEEE80211_M_HOSTAP &&
#endif
ic->ic_state != IEEE80211_S_SCAN) {
panic("%s: impossible operating mode", __func__);
}
#endif
if (m->m_len < sizeof(*wh) + 12) {
DPRINTF(("frame too short\n"));
return;
}
wh = mtod(m, struct ieee80211_frame *);
frm = (const u_int8_t *)&wh[1];
efrm = mtod(m, u_int8_t *) + m->m_len;
tstamp = frm; frm += 8;
bintval = LE_READ_2(frm); frm += 2;
capinfo = LE_READ_2(frm); frm += 2;
ssid = rates = xrates = edcaie = wmmie = rsnie = wpaie = tim = NULL;
htcaps = htop = vhtcaps = vhtop = NULL;
if (rxi->rxi_chan)
bchan = rxi->rxi_chan;
else
bchan = ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan);
chan = bchan;
erp = 0;
while (frm + 2 <= efrm) {
if (frm + 2 + frm[1] > efrm) {
ic->ic_stats.is_rx_elem_toosmall++;
break;
}
switch (frm[0]) {
case IEEE80211_ELEMID_SSID:
ssid = frm;
break;
case IEEE80211_ELEMID_RATES:
rates = frm;
break;
case IEEE80211_ELEMID_DSPARMS:
if (frm[1] < 1) {
ic->ic_stats.is_rx_elem_toosmall++;
break;
}
chan = frm[2];
break;
case IEEE80211_ELEMID_XRATES:
xrates = frm;
break;
case IEEE80211_ELEMID_ERP:
if (frm[1] < 1) {
ic->ic_stats.is_rx_elem_toosmall++;
break;
}
erp = frm[2];
break;
case IEEE80211_ELEMID_RSN:
rsnie = frm;
break;
case IEEE80211_ELEMID_EDCAPARMS:
edcaie = frm;
break;
case IEEE80211_ELEMID_HTCAPS:
htcaps = frm;
break;
case IEEE80211_ELEMID_HTOP:
if (frm[1] < 22) {
ic->ic_stats.is_rx_elem_toosmall++;
break;
}
htop = frm;
chan = frm[2];
break;
case IEEE80211_ELEMID_VHTCAPS:
vhtcaps = frm;
break;
case IEEE80211_ELEMID_VHTOP:
vhtop = frm;
break;
case IEEE80211_ELEMID_TIM:
if (frm[1] < 4) {
ic->ic_stats.is_rx_elem_toosmall++;
break;
}
tim = frm;
break;
case IEEE80211_ELEMID_VENDOR:
if (frm[1] < 4) {
ic->ic_stats.is_rx_elem_toosmall++;
break;
}
if (memcmp(frm + 2, MICROSOFT_OUI, 3) == 0) {
if (frm[5] == 1)
wpaie = frm;
else if (frm[1] >= 5 &&
frm[5] == 2 && frm[6] == 1)
wmmie = frm;
}
break;
}
frm += 2 + frm[1];
}
if (rates == NULL || rates[1] > IEEE80211_RATE_MAXSIZE) {
DPRINTF(("invalid supported rates element\n"));
return;
}
if (ssid == NULL || ssid[1] > IEEE80211_NWID_LEN) {
DPRINTF(("invalid SSID element\n"));
return;
}
if (
#if IEEE80211_CHAN_MAX < 255
chan > IEEE80211_CHAN_MAX ||
#endif
(isclr(ic->ic_chan_active, chan) &&
((ic->ic_caps & IEEE80211_C_SCANALL) == 0 ||
(ic->ic_flags & IEEE80211_F_BGSCAN) == 0))) {
DPRINTF(("ignore %s with invalid channel %u\n",
isprobe ? "probe response" : "beacon", chan));
ic->ic_stats.is_rx_badchan++;
return;
}
if ((rxi->rxi_chan != 0 && chan != rxi->rxi_chan) ||
((ic->ic_state != IEEE80211_S_SCAN ||
!(ic->ic_caps & IEEE80211_C_SCANALL)) &&
chan != bchan)) {
DPRINTF(("ignore %s on channel %u marked for channel %u\n",
isprobe ? "probe response" : "beacon", bchan, chan));
ic->ic_stats.is_rx_chanmismatch++;
return;
}
#ifdef IEEE80211_DEBUG
if (ieee80211_debug > 1 &&
(ni == NULL || ic->ic_state == IEEE80211_S_SCAN ||
(ic->ic_flags & IEEE80211_F_BGSCAN))) {
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((u_int8_t *)wh->i_addr2));
printf("%s: caps 0x%x bintval %u erp 0x%x\n",
__func__, capinfo, bintval, erp);
}
#endif
if ((ni = ieee80211_find_node(ic, wh->i_addr2)) == NULL) {
ni = ieee80211_alloc_node(ic, wh->i_addr2);
if (ni == NULL)
return;
is_new = 1;
} else
is_new = 0;
ni->ni_chan = &ic->ic_channels[chan];
if (htcaps)
ieee80211_setup_htcaps(ni, htcaps + 2, htcaps[1]);
if (htop && !ieee80211_setup_htop(ni, htop + 2, htop[1], 1))
htop = NULL;
if (htcaps && vhtcaps && IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) {
ieee80211_setup_vhtcaps(ni, vhtcaps + 2, vhtcaps[1]);
if (vhtop && !ieee80211_setup_vhtop(ni, vhtop + 2, vhtop[1], 1))
vhtop = NULL;
}
if (tim) {
ni->ni_dtimcount = tim[2];
ni->ni_dtimperiod = tim[3];
}
if (ic->ic_opmode == IEEE80211_M_STA &&
ic->ic_state == IEEE80211_S_RUN &&
ni->ni_state == IEEE80211_STA_BSS) {
int updateprot = 0;
if (ni->ni_erp != erp) {
DPRINTF(("[%s] erp change: was 0x%x, now 0x%x\n",
ether_sprintf((u_int8_t *)wh->i_addr2),
ni->ni_erp, erp));
if ((ic->ic_curmode == IEEE80211_MODE_11G ||
(ic->ic_curmode == IEEE80211_MODE_11N &&
IEEE80211_IS_CHAN_2GHZ(ni->ni_chan))) &&
(erp & IEEE80211_ERP_USE_PROTECTION))
ic->ic_flags |= IEEE80211_F_USEPROT;
else
ic->ic_flags &= ~IEEE80211_F_USEPROT;
ic->ic_bss->ni_erp = erp;
updateprot = 1;
}
if (htop && (ic->ic_bss->ni_flags & IEEE80211_NODE_HT)) {
enum ieee80211_htprot htprot_last, htprot;
htprot_last =
((ic->ic_bss->ni_htop1 & IEEE80211_HTOP1_PROT_MASK)
>> IEEE80211_HTOP1_PROT_SHIFT);
htprot = ((ni->ni_htop1 & IEEE80211_HTOP1_PROT_MASK) >>
IEEE80211_HTOP1_PROT_SHIFT);
if (htprot_last != htprot) {
DPRINTF(("[%s] htprot change: was %d, now %d\n",
ether_sprintf((u_int8_t *)wh->i_addr2),
htprot_last, htprot));
ic->ic_stats.is_ht_prot_change++;
ic->ic_bss->ni_htop1 = ni->ni_htop1;
updateprot = 1;
}
}
if (updateprot && ic->ic_updateprot != NULL)
ic->ic_updateprot(ic);
if (htop && (ic->ic_bss->ni_flags & IEEE80211_NODE_HT) &&
(ic->ic_htcaps & IEEE80211_HTCAP_CBW20_40)) {
uint8_t chw_last, chw, sco_last, sco;
chw_last = (ic->ic_bss->ni_htop0 & IEEE80211_HTOP0_CHW);
chw = (ni->ni_htop0 & IEEE80211_HTOP0_CHW);
sco_last =
((ic->ic_bss->ni_htop0 & IEEE80211_HTOP0_SCO_MASK)
>> IEEE80211_HTOP0_SCO_SHIFT);
sco = ((ni->ni_htop0 & IEEE80211_HTOP0_SCO_MASK) >>
IEEE80211_HTOP0_SCO_SHIFT);
ic->ic_bss->ni_htop0 = ni->ni_htop0;
if (chw_last != chw || sco_last != sco) {
if (ic->ic_updatechan != NULL)
ic->ic_updatechan(ic);
}
} else if (htop)
ic->ic_bss->ni_htop0 = ni->ni_htop0;
if ((ni->ni_capinfo ^ capinfo) &
IEEE80211_CAPINFO_SHORT_SLOTTIME) {
ieee80211_set_shortslottime(ic,
ic->ic_curmode == IEEE80211_MODE_11A ||
(capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME));
}
if (tim && ic->ic_bss->ni_dtimperiod != ni->ni_dtimperiod) {
ic->ic_bss->ni_dtimperiod = ni->ni_dtimperiod;
ic->ic_bss->ni_dtimcount = ni->ni_dtimcount;
if (ic->ic_updatedtim != NULL)
ic->ic_updatedtim(ic);
}
if ((ic->ic_flags & IEEE80211_F_BGSCAN) == 0)
ic->ic_mgt_timer = 0;
}
if (ni->ni_flags & IEEE80211_NODE_QOS) {
if ((edcaie != NULL &&
ieee80211_parse_edca_params(ic, edcaie) == 0) ||
(wmmie != NULL &&
ieee80211_parse_wmm_params(ic, wmmie) == 0))
ni->ni_flags |= IEEE80211_NODE_QOS;
else
ni->ni_flags &= ~IEEE80211_NODE_QOS;
}
if (ic->ic_state == IEEE80211_S_SCAN ||
(ic->ic_flags & IEEE80211_F_BGSCAN)) {
struct ieee80211_rsnparams rsn, wpa;
ni->ni_rsnprotos = IEEE80211_PROTO_NONE;
ni->ni_supported_rsnprotos = IEEE80211_PROTO_NONE;
ni->ni_rsnakms = 0;
ni->ni_supported_rsnakms = 0;
ni->ni_rsnciphers = 0;
ni->ni_rsngroupcipher = 0;
ni->ni_rsngroupmgmtcipher = 0;
ni->ni_rsncaps = 0;
if (rsnie != NULL &&
ieee80211_parse_rsn(ic, rsnie, &rsn) == 0) {
ni->ni_supported_rsnprotos |= IEEE80211_PROTO_RSN;
ni->ni_supported_rsnakms |= rsn.rsn_akms;
}
if (wpaie != NULL &&
ieee80211_parse_wpa(ic, wpaie, &wpa) == 0) {
ni->ni_supported_rsnprotos |= IEEE80211_PROTO_WPA;
ni->ni_supported_rsnakms |= wpa.rsn_akms;
}
if (rsnie != NULL &&
(ni->ni_supported_rsnprotos & IEEE80211_PROTO_RSN) &&
(ic->ic_caps & IEEE80211_C_RSN)) {
if (ieee80211_save_ie(rsnie, &ni->ni_rsnie) == 0
#ifndef IEEE80211_STA_ONLY
&& ic->ic_opmode != IEEE80211_M_HOSTAP
#endif
) {
ni->ni_rsnprotos = IEEE80211_PROTO_RSN;
ni->ni_rsnakms = rsn.rsn_akms;
ni->ni_rsnciphers = rsn.rsn_ciphers;
ni->ni_rsngroupcipher = rsn.rsn_groupcipher;
ni->ni_rsngroupmgmtcipher =
rsn.rsn_groupmgmtcipher;
ni->ni_rsncaps = rsn.rsn_caps;
}
} else if (wpaie != NULL &&
(ni->ni_supported_rsnprotos & IEEE80211_PROTO_WPA) &&
(ic->ic_caps & IEEE80211_C_RSN)) {
if (ieee80211_save_ie(wpaie, &ni->ni_rsnie) == 0
#ifndef IEEE80211_STA_ONLY
&& ic->ic_opmode != IEEE80211_M_HOSTAP
#endif
) {
ni->ni_rsnprotos = IEEE80211_PROTO_WPA;
ni->ni_rsnakms = wpa.rsn_akms;
ni->ni_rsnciphers = wpa.rsn_ciphers;
ni->ni_rsngroupcipher = wpa.rsn_groupcipher;
ni->ni_rsngroupmgmtcipher =
wpa.rsn_groupmgmtcipher;
ni->ni_rsncaps = wpa.rsn_caps;
}
}
}
if (ssid[1] != 0 && ni->ni_essid[0] == '\0') {
ni->ni_esslen = ssid[1];
memset(ni->ni_essid, 0, sizeof(ni->ni_essid));
memcpy(ni->ni_essid, &ssid[2], ssid[1]);
}
IEEE80211_ADDR_COPY(ni->ni_bssid, wh->i_addr3);
if (ic->ic_state == IEEE80211_S_SCAN &&
IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) {
if (isprobe || ni->ni_rssi == 0)
ni->ni_rssi = rxi->rxi_rssi;
else if (ni->ni_rssi < rxi->rxi_rssi)
ni->ni_rssi = rxi->rxi_rssi;
} else
ni->ni_rssi = rxi->rxi_rssi;
ni->ni_rstamp = rxi->rxi_tstamp;
memcpy(ni->ni_tstamp, tstamp, sizeof(ni->ni_tstamp));
ni->ni_intval = bintval;
ni->ni_capinfo = capinfo;
ni->ni_erp = erp;
ieee80211_setup_rates(ic, ni, rates, xrates, IEEE80211_F_DOSORT);
#ifndef IEEE80211_STA_ONLY
if (ic->ic_opmode == IEEE80211_M_IBSS && is_new && isprobe) {
if (ic->ic_newassoc)
(*ic->ic_newassoc)(ic, ni, 1);
}
#endif
}
#ifndef IEEE80211_STA_ONLY
void
ieee80211_recv_probe_req(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni, struct ieee80211_rxinfo *rxi)
{
const struct ieee80211_frame *wh;
const u_int8_t *frm, *efrm;
const u_int8_t *ssid, *rates, *xrates, *htcaps, *vhtcaps;
u_int8_t rate;
if (ic->ic_opmode == IEEE80211_M_STA ||
ic->ic_state != IEEE80211_S_RUN)
return;
wh = mtod(m, struct ieee80211_frame *);
frm = (const u_int8_t *)&wh[1];
efrm = mtod(m, u_int8_t *) + m->m_len;
ssid = rates = xrates = htcaps = vhtcaps = NULL;
while (frm + 2 <= efrm) {
if (frm + 2 + frm[1] > efrm) {
ic->ic_stats.is_rx_elem_toosmall++;
break;
}
switch (frm[0]) {
case IEEE80211_ELEMID_SSID:
ssid = frm;
break;
case IEEE80211_ELEMID_RATES:
rates = frm;
break;
case IEEE80211_ELEMID_XRATES:
xrates = frm;
break;
case IEEE80211_ELEMID_HTCAPS:
htcaps = frm;
break;
case IEEE80211_ELEMID_VHTCAPS:
vhtcaps = frm;
break;
}
frm += 2 + frm[1];
}
if (rates == NULL || rates[1] > IEEE80211_RATE_MAXSIZE) {
DPRINTF(("invalid supported rates element\n"));
return;
}
if (ssid == NULL || ssid[1] > IEEE80211_NWID_LEN) {
DPRINTF(("invalid SSID element\n"));
return;
}
if (ssid[1] != 0 && (ssid[1] != ic->ic_bss->ni_esslen ||
memcmp(&ssid[2], ic->ic_bss->ni_essid, ic->ic_bss->ni_esslen))) {
DPRINTF(("SSID mismatch\n"));
ic->ic_stats.is_rx_ssidmismatch++;
return;
}
if (ssid[1] == 0 && (ic->ic_userflags & IEEE80211_F_HIDENWID)) {
DPRINTF(("wildcard SSID rejected"));
ic->ic_stats.is_rx_ssidmismatch++;
return;
}
if (ni == ic->ic_bss) {
ni = ieee80211_find_node(ic, wh->i_addr2);
if (ni == NULL)
ni = ieee80211_dup_bss(ic, wh->i_addr2);
if (ni == NULL)
return;
DPRINTF(("new probe req from %s\n",
ether_sprintf((u_int8_t *)wh->i_addr2)));
}
ni->ni_rssi = rxi->rxi_rssi;
ni->ni_rstamp = rxi->rxi_tstamp;
rate = ieee80211_setup_rates(ic, ni, rates, xrates,
IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO |
IEEE80211_F_DODEL);
if (rate & IEEE80211_RATE_BASIC) {
DPRINTF(("rate mismatch for %s\n",
ether_sprintf((u_int8_t *)wh->i_addr2)));
return;
}
if (htcaps)
ieee80211_setup_htcaps(ni, htcaps + 2, htcaps[1]);
else
ieee80211_clear_htcaps(ni);
if (htcaps && vhtcaps && IEEE80211_IS_CHAN_5GHZ(ic->ic_bss->ni_chan))
ieee80211_setup_vhtcaps(ni, vhtcaps + 2, vhtcaps[1]);
else
ieee80211_clear_vhtcaps(ni);
IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_PROBE_RESP, 0);
}
#endif
void
ieee80211_recv_auth(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni, struct ieee80211_rxinfo *rxi)
{
const struct ieee80211_frame *wh;
const u_int8_t *frm;
u_int16_t algo, seq, status;
if (m->m_len < sizeof(*wh) + 6) {
DPRINTF(("frame too short\n"));
return;
}
wh = mtod(m, struct ieee80211_frame *);
frm = (const u_int8_t *)&wh[1];
algo = LE_READ_2(frm); frm += 2;
seq = LE_READ_2(frm); frm += 2;
status = LE_READ_2(frm); frm += 2;
DPRINTF(("auth %d seq %d from %s\n", algo, seq,
ether_sprintf((u_int8_t *)wh->i_addr2)));
if (algo != IEEE80211_AUTH_ALG_OPEN) {
DPRINTF(("unsupported auth algorithm %d from %s\n",
algo, ether_sprintf((u_int8_t *)wh->i_addr2)));
ic->ic_stats.is_rx_auth_unsupported++;
#ifndef IEEE80211_STA_ONLY
if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
IEEE80211_SEND_MGMT(ic, ni,
IEEE80211_FC0_SUBTYPE_AUTH,
IEEE80211_STATUS_ALG << 16 | ((seq + 1) & 0xfff));
}
#endif
return;
}
ieee80211_auth_open(ic, wh, ni, rxi, seq, status);
}
#ifndef IEEE80211_STA_ONLY
void
ieee80211_recv_assoc_req(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni, struct ieee80211_rxinfo *rxi, int reassoc)
{
const struct ieee80211_frame *wh;
const u_int8_t *frm, *efrm;
const u_int8_t *ssid, *rates, *xrates, *rsnie, *wpaie, *wmeie;
const u_int8_t *htcaps, *vhtcaps;
u_int16_t capinfo, bintval;
int resp, status = 0;
struct ieee80211_rsnparams rsn;
u_int8_t rate;
const u_int8_t *saveie = NULL;
if (ic->ic_opmode != IEEE80211_M_HOSTAP ||
ic->ic_state != IEEE80211_S_RUN)
return;
if (m->m_len < sizeof(*wh) + (reassoc ? 10 : 4)) {
DPRINTF(("frame too short\n"));
return;
}
wh = mtod(m, struct ieee80211_frame *);
frm = (const u_int8_t *)&wh[1];
efrm = mtod(m, u_int8_t *) + m->m_len;
if (!IEEE80211_ADDR_EQ(wh->i_addr3, ic->ic_bss->ni_bssid)) {
DPRINTF(("ignore other bss from %s\n",
ether_sprintf((u_int8_t *)wh->i_addr2)));
ic->ic_stats.is_rx_assoc_bss++;
return;
}
capinfo = LE_READ_2(frm); frm += 2;
bintval = LE_READ_2(frm); frm += 2;
if (reassoc) {
frm += IEEE80211_ADDR_LEN;
resp = IEEE80211_FC0_SUBTYPE_REASSOC_RESP;
} else
resp = IEEE80211_FC0_SUBTYPE_ASSOC_RESP;
ssid = rates = xrates = rsnie = wpaie = wmeie = htcaps = vhtcaps = NULL;
while (frm + 2 <= efrm) {
if (frm + 2 + frm[1] > efrm) {
ic->ic_stats.is_rx_elem_toosmall++;
break;
}
switch (frm[0]) {
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:
rsnie = frm;
break;
case IEEE80211_ELEMID_QOS_CAP:
break;
case IEEE80211_ELEMID_HTCAPS:
htcaps = frm;
break;
case IEEE80211_ELEMID_VHTCAPS:
vhtcaps = frm;
break;
case IEEE80211_ELEMID_VENDOR:
if (frm[1] < 4) {
ic->ic_stats.is_rx_elem_toosmall++;
break;
}
if (memcmp(frm + 2, MICROSOFT_OUI, 3) == 0) {
if (frm[5] == 1)
wpaie = frm;
if (frm[1] == 7 && frm[5] == 2 && frm[6] == 0)
wmeie = frm;
}
break;
}
frm += 2 + frm[1];
}
if (rates == NULL || rates[1] > IEEE80211_RATE_MAXSIZE) {
DPRINTF(("invalid supported rates element\n"));
return;
}
if (ssid == NULL || ssid[1] > IEEE80211_NWID_LEN) {
DPRINTF(("invalid SSID element\n"));
return;
}
if (ssid[1] != ic->ic_bss->ni_esslen ||
memcmp(&ssid[2], ic->ic_bss->ni_essid, ic->ic_bss->ni_esslen)) {
DPRINTF(("SSID mismatch\n"));
ic->ic_stats.is_rx_ssidmismatch++;
return;
}
if (ni->ni_state != IEEE80211_STA_AUTH &&
ni->ni_state != IEEE80211_STA_ASSOC) {
DPRINTF(("deny %sassoc from %s, not authenticated\n",
reassoc ? "re" : "",
ether_sprintf((u_int8_t *)wh->i_addr2)));
ni = ieee80211_find_node(ic, wh->i_addr2);
if (ni == NULL)
ni = ieee80211_dup_bss(ic, wh->i_addr2);
if (ni != NULL) {
IEEE80211_SEND_MGMT(ic, ni,
IEEE80211_FC0_SUBTYPE_DEAUTH,
IEEE80211_REASON_ASSOC_NOT_AUTHED);
}
ic->ic_stats.is_rx_assoc_notauth++;
return;
}
if (ni->ni_state == IEEE80211_STA_ASSOC &&
(ni->ni_flags & IEEE80211_NODE_MFP)) {
if (ni->ni_flags & IEEE80211_NODE_SA_QUERY_FAILED) {
IEEE80211_SEND_MGMT(ic, ni,
IEEE80211_FC0_SUBTYPE_DISASSOC,
IEEE80211_REASON_AUTH_EXPIRE);
ieee80211_node_leave(ic, ni);
} else {
IEEE80211_SEND_MGMT(ic, ni, resp,
IEEE80211_STATUS_TRY_AGAIN_LATER);
if (!(ni->ni_flags & IEEE80211_NODE_SA_QUERY))
ieee80211_sa_query_request(ic, ni);
}
return;
}
if (!(capinfo & IEEE80211_CAPINFO_ESS)) {
ic->ic_stats.is_rx_assoc_capmismatch++;
status = IEEE80211_STATUS_CAPINFO;
goto end;
}
rate = ieee80211_setup_rates(ic, ni, rates, xrates,
IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO |
IEEE80211_F_DODEL);
if (rate & IEEE80211_RATE_BASIC) {
ic->ic_stats.is_rx_assoc_norate++;
status = IEEE80211_STATUS_BASIC_RATE;
goto end;
}
ni->ni_rsnprotos = IEEE80211_PROTO_NONE;
ni->ni_supported_rsnprotos = IEEE80211_PROTO_NONE;
ni->ni_rsnakms = 0;
ni->ni_supported_rsnakms = 0;
ni->ni_rsnciphers = 0;
ni->ni_rsngroupcipher = 0;
ni->ni_rsngroupmgmtcipher = 0;
ni->ni_rsncaps = 0;
if (rsnie != NULL) {
status = ieee80211_parse_rsn(ic, rsnie, &rsn);
if (status != 0)
goto end;
ni->ni_supported_rsnprotos = IEEE80211_PROTO_RSN;
ni->ni_supported_rsnakms = rsn.rsn_akms;
if ((ic->ic_flags & IEEE80211_F_RSNON) &&
(ic->ic_rsnprotos & IEEE80211_PROTO_RSN)) {
ni->ni_rsnprotos = IEEE80211_PROTO_RSN;
saveie = rsnie;
}
} else if (wpaie != NULL) {
status = ieee80211_parse_wpa(ic, wpaie, &rsn);
if (status != 0)
goto end;
ni->ni_supported_rsnprotos = IEEE80211_PROTO_WPA;
ni->ni_supported_rsnakms = rsn.rsn_akms;
if ((ic->ic_flags & IEEE80211_F_RSNON) &&
(ic->ic_rsnprotos & IEEE80211_PROTO_WPA)) {
ni->ni_rsnprotos = IEEE80211_PROTO_WPA;
saveie = wpaie;
}
}
if (ic->ic_flags & IEEE80211_F_QOS) {
if (wmeie != NULL)
ni->ni_flags |= IEEE80211_NODE_QOS;
else
ni->ni_flags &= ~IEEE80211_NODE_QOS;
}
if (ic->ic_flags & IEEE80211_F_RSNON) {
if (ni->ni_rsnprotos == IEEE80211_PROTO_NONE) {
status = IEEE80211_STATUS_IE_INVALID;
goto end;
}
if (rsn.rsn_nakms != 1 ||
!(rsn.rsn_akms & ic->ic_bss->ni_rsnakms)) {
status = IEEE80211_STATUS_BAD_AKMP;
ni->ni_rsnprotos = IEEE80211_PROTO_NONE;
goto end;
}
if (rsn.rsn_nciphers != 1 ||
!(rsn.rsn_ciphers & ic->ic_bss->ni_rsnciphers)) {
status = IEEE80211_STATUS_BAD_PAIRWISE_CIPHER;
ni->ni_rsnprotos = IEEE80211_PROTO_NONE;
goto end;
}
if (rsn.rsn_groupcipher != ic->ic_bss->ni_rsngroupcipher) {
status = IEEE80211_STATUS_BAD_GROUP_CIPHER;
ni->ni_rsnprotos = IEEE80211_PROTO_NONE;
goto end;
}
if ((ic->ic_bss->ni_rsncaps & IEEE80211_RSNCAP_MFPR) &&
!(rsn.rsn_caps & IEEE80211_RSNCAP_MFPC)) {
status = IEEE80211_STATUS_MFP_POLICY;
ni->ni_rsnprotos = IEEE80211_PROTO_NONE;
goto end;
}
if ((ic->ic_bss->ni_rsncaps & IEEE80211_RSNCAP_MFPC) &&
(rsn.rsn_caps & (IEEE80211_RSNCAP_MFPC |
IEEE80211_RSNCAP_MFPR)) == IEEE80211_RSNCAP_MFPR) {
status = IEEE80211_STATUS_MFP_POLICY;
ni->ni_rsnprotos = IEEE80211_PROTO_NONE;
goto end;
}
if ((rsn.rsn_caps & IEEE80211_RSNCAP_MFPC) &&
(rsn.rsn_ciphers != IEEE80211_CIPHER_CCMP ||
rsn.rsn_groupmgmtcipher !=
ic->ic_bss->ni_rsngroupmgmtcipher)) {
status = IEEE80211_STATUS_MFP_POLICY;
ni->ni_rsnprotos = IEEE80211_PROTO_NONE;
goto end;
}
if ((ic->ic_flags & IEEE80211_F_COUNTERM) &&
(rsn.rsn_ciphers == IEEE80211_CIPHER_TKIP ||
rsn.rsn_groupcipher == IEEE80211_CIPHER_TKIP)) {
status = IEEE80211_STATUS_CIPHER_REJ_POLICY;
ni->ni_rsnprotos = IEEE80211_PROTO_NONE;
goto end;
}
if (saveie == NULL ||
ieee80211_save_ie(saveie, &ni->ni_rsnie) != 0) {
status = IEEE80211_STATUS_TOOMANY;
ni->ni_rsnprotos = IEEE80211_PROTO_NONE;
goto end;
}
ni->ni_rsnakms = rsn.rsn_akms;
ni->ni_rsnciphers = rsn.rsn_ciphers;
ni->ni_rsngroupcipher = ic->ic_bss->ni_rsngroupcipher;
ni->ni_rsngroupmgmtcipher = ic->ic_bss->ni_rsngroupmgmtcipher;
ni->ni_rsncaps = rsn.rsn_caps;
if (ieee80211_is_8021x_akm(ni->ni_rsnakms)) {
struct ieee80211_pmk *pmk = NULL;
const u_int8_t *pmkid = rsn.rsn_pmkids;
while (rsn.rsn_npmkids-- > 0) {
pmk = ieee80211_pmksa_find(ic, ni, pmkid);
if (pmk != NULL)
break;
pmkid += IEEE80211_PMKID_LEN;
}
if (pmk != NULL) {
memcpy(ni->ni_pmk, pmk->pmk_key,
IEEE80211_PMK_LEN);
memcpy(ni->ni_pmkid, pmk->pmk_pmkid,
IEEE80211_PMKID_LEN);
ni->ni_flags |= IEEE80211_NODE_PMK;
}
}
}
ni->ni_rssi = rxi->rxi_rssi;
ni->ni_rstamp = rxi->rxi_tstamp;
ni->ni_intval = bintval;
ni->ni_capinfo = capinfo;
ni->ni_chan = ic->ic_bss->ni_chan;
if (htcaps)
ieee80211_setup_htcaps(ni, htcaps + 2, htcaps[1]);
else
ieee80211_clear_htcaps(ni);
if (htcaps && vhtcaps && IEEE80211_IS_CHAN_5GHZ(ic->ic_bss->ni_chan))
ieee80211_setup_vhtcaps(ni, vhtcaps + 2, vhtcaps[1]);
else
ieee80211_clear_vhtcaps(ni);
end:
if (status != 0) {
IEEE80211_SEND_MGMT(ic, ni, resp, status);
ieee80211_node_leave(ic, ni);
} else
ieee80211_node_join(ic, ni, resp);
}
#endif
void
ieee80211_recv_assoc_resp(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni, int reassoc)
{
struct ifnet *ifp = &ic->ic_if;
const struct ieee80211_frame *wh;
const u_int8_t *frm, *efrm;
const u_int8_t *rates, *xrates, *edcaie, *wmmie, *htcaps, *htop;
const u_int8_t *vhtcaps, *vhtop;
u_int16_t capinfo, status, associd;
u_int8_t rate;
if (ic->ic_opmode != IEEE80211_M_STA ||
ic->ic_state != IEEE80211_S_ASSOC) {
ic->ic_stats.is_rx_mgtdiscard++;
return;
}
if (m->m_len < sizeof(*wh) + 6) {
DPRINTF(("frame too short\n"));
return;
}
wh = mtod(m, struct ieee80211_frame *);
frm = (const u_int8_t *)&wh[1];
efrm = mtod(m, u_int8_t *) + m->m_len;
capinfo = LE_READ_2(frm); frm += 2;
status = LE_READ_2(frm); frm += 2;
if (status != IEEE80211_STATUS_SUCCESS) {
if (ifp->if_flags & IFF_DEBUG)
printf("%s: %sassociation failed (status %d)"
" for %s\n", ifp->if_xname,
reassoc ? "re" : "",
status, ether_sprintf((u_int8_t *)wh->i_addr3));
if (ni != ic->ic_bss)
ni->ni_fails++;
ic->ic_stats.is_rx_auth_fail++;
return;
}
associd = LE_READ_2(frm); frm += 2;
rates = xrates = edcaie = wmmie = htcaps = htop = NULL;
vhtcaps = vhtop = NULL;
while (frm + 2 <= efrm) {
if (frm + 2 + frm[1] > efrm) {
ic->ic_stats.is_rx_elem_toosmall++;
break;
}
switch (frm[0]) {
case IEEE80211_ELEMID_RATES:
rates = frm;
break;
case IEEE80211_ELEMID_XRATES:
xrates = frm;
break;
case IEEE80211_ELEMID_EDCAPARMS:
edcaie = frm;
break;
case IEEE80211_ELEMID_HTCAPS:
htcaps = frm;
break;
case IEEE80211_ELEMID_HTOP:
htop = frm;
break;
case IEEE80211_ELEMID_VHTCAPS:
vhtcaps = frm;
break;
case IEEE80211_ELEMID_VHTOP:
vhtop = frm;
break;
case IEEE80211_ELEMID_VENDOR:
if (frm[1] < 4) {
ic->ic_stats.is_rx_elem_toosmall++;
break;
}
if (memcmp(frm + 2, MICROSOFT_OUI, 3) == 0) {
if (frm[1] >= 5 && frm[5] == 2 && frm[6] == 1)
wmmie = frm;
}
break;
}
frm += 2 + frm[1];
}
if (rates == NULL || rates[1] > IEEE80211_RATE_MAXSIZE) {
DPRINTF(("invalid supported rates element\n"));
return;
}
rate = ieee80211_setup_rates(ic, ni, rates, xrates,
IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO |
IEEE80211_F_DODEL);
if (rate & IEEE80211_RATE_BASIC) {
DPRINTF(("rate mismatch for %s\n",
ether_sprintf((u_int8_t *)wh->i_addr2)));
ic->ic_stats.is_rx_assoc_norate++;
return;
}
ni->ni_capinfo = capinfo;
ni->ni_associd = associd;
if (edcaie != NULL || wmmie != NULL) {
ic->ic_edca_updtcount = -1;
if ((edcaie != NULL &&
ieee80211_parse_edca_params(ic, edcaie) == 0) ||
(wmmie != NULL &&
ieee80211_parse_wmm_params(ic, wmmie) == 0))
ni->ni_flags |= IEEE80211_NODE_QOS;
else
ni->ni_flags &= ~IEEE80211_NODE_QOS;
}
if (htcaps)
ieee80211_setup_htcaps(ni, htcaps + 2, htcaps[1]);
if (htop)
ieee80211_setup_htop(ni, htop + 2, htop[1], 0);
ieee80211_ht_negotiate(ic, ni);
if (htcaps && vhtcaps && IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) {
ieee80211_setup_vhtcaps(ni, vhtcaps + 2, vhtcaps[1]);
if (vhtop && !ieee80211_setup_vhtop(ni, vhtop + 2, vhtop[1], 1))
vhtop = NULL;
}
ieee80211_vht_negotiate(ic, ni);
if (ni->ni_flags & IEEE80211_NODE_VHT)
ieee80211_setmode(ic, IEEE80211_MODE_11AC);
else if (ni->ni_flags & IEEE80211_NODE_HT)
ieee80211_setmode(ic, IEEE80211_MODE_11N);
else
ieee80211_setmode(ic, ieee80211_node_abg_mode(ic, ni));
ieee80211_reset_erp(ic);
if (ic->ic_curmode == IEEE80211_MODE_11A ||
(ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE))
ic->ic_flags |= IEEE80211_F_SHPREAMBLE;
else
ic->ic_flags &= ~IEEE80211_F_SHPREAMBLE;
ieee80211_set_shortslottime(ic,
ic->ic_curmode == IEEE80211_MODE_11A ||
(ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME));
if ((ic->ic_curmode == IEEE80211_MODE_11G ||
(ic->ic_curmode == IEEE80211_MODE_11N &&
IEEE80211_IS_CHAN_2GHZ(ni->ni_chan))) &&
(ni->ni_erp & IEEE80211_ERP_USE_PROTECTION))
ic->ic_flags |= IEEE80211_F_USEPROT;
else
ic->ic_flags &= ~IEEE80211_F_USEPROT;
if (ic->ic_flags & IEEE80211_F_RSNON) {
ni->ni_rsn_supp_state = RSNA_SUPP_PTKSTART;
} else if (ic->ic_flags & IEEE80211_F_WEPON)
ni->ni_flags |= IEEE80211_NODE_TXRXPROT;
ieee80211_new_state(ic, IEEE80211_S_RUN,
IEEE80211_FC0_SUBTYPE_ASSOC_RESP);
}
void
ieee80211_recv_deauth(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni)
{
const struct ieee80211_frame *wh;
const u_int8_t *frm;
u_int16_t reason;
if (m->m_len < sizeof(*wh) + 2) {
DPRINTF(("frame too short\n"));
return;
}
wh = mtod(m, struct ieee80211_frame *);
frm = (const u_int8_t *)&wh[1];
reason = LE_READ_2(frm);
ic->ic_stats.is_rx_deauth++;
switch (ic->ic_opmode) {
case IEEE80211_M_STA: {
int bgscan = ((ic->ic_flags & IEEE80211_F_BGSCAN) &&
ic->ic_state == IEEE80211_S_RUN);
int stay_auth = ((ic->ic_userflags & IEEE80211_F_STAYAUTH) &&
ic->ic_state >= IEEE80211_S_AUTH);
if (!(bgscan || stay_auth))
ieee80211_new_state(ic, IEEE80211_S_AUTH,
IEEE80211_FC0_SUBTYPE_DEAUTH);
}
break;
#ifndef IEEE80211_STA_ONLY
case IEEE80211_M_HOSTAP:
if (ni != ic->ic_bss) {
int stay_auth =
((ic->ic_userflags & IEEE80211_F_STAYAUTH) &&
(ni->ni_state == IEEE80211_STA_AUTH ||
ni->ni_state == IEEE80211_STA_ASSOC));
if (ic->ic_if.if_flags & IFF_DEBUG)
printf("%s: station %s deauthenticated "
"by peer (reason %d)\n",
ic->ic_if.if_xname,
ether_sprintf(ni->ni_macaddr),
reason);
if (!stay_auth)
ieee80211_node_leave(ic, ni);
}
break;
#endif
default:
break;
}
}
void
ieee80211_recv_disassoc(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni)
{
const struct ieee80211_frame *wh;
const u_int8_t *frm;
u_int16_t reason;
if (m->m_len < sizeof(*wh) + 2) {
DPRINTF(("frame too short\n"));
return;
}
wh = mtod(m, struct ieee80211_frame *);
frm = (const u_int8_t *)&wh[1];
reason = LE_READ_2(frm);
ic->ic_stats.is_rx_disassoc++;
switch (ic->ic_opmode) {
case IEEE80211_M_STA: {
int bgscan = ((ic->ic_flags & IEEE80211_F_BGSCAN) &&
ic->ic_state == IEEE80211_S_RUN);
if (!bgscan)
ieee80211_new_state(ic, IEEE80211_S_ASSOC,
IEEE80211_FC0_SUBTYPE_DISASSOC);
}
break;
#ifndef IEEE80211_STA_ONLY
case IEEE80211_M_HOSTAP:
if (ni != ic->ic_bss) {
if (ic->ic_if.if_flags & IFF_DEBUG)
printf("%s: station %s disassociated "
"by peer (reason %d)\n",
ic->ic_if.if_xname,
ether_sprintf(ni->ni_macaddr),
reason);
ieee80211_node_leave(ic, ni);
}
break;
#endif
default:
break;
}
}
void
ieee80211_recv_addba_req(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni)
{
const struct ieee80211_frame *wh;
const u_int8_t *frm;
struct ieee80211_rx_ba *ba;
u_int16_t params, ssn, bufsz, timeout;
u_int8_t token, tid;
int err = 0;
if (ic->ic_state != IEEE80211_S_RUN ||
((ic->ic_flags & IEEE80211_F_RSNON) && !ni->ni_port_valid))
return;
if (!(ni->ni_flags & IEEE80211_NODE_HT)) {
DPRINTF(("received ADDBA req from non-HT STA %s\n",
ether_sprintf(ni->ni_macaddr)));
return;
}
if (m->m_len < sizeof(*wh) + 9) {
DPRINTF(("frame too short\n"));
return;
}
wh = mtod(m, struct ieee80211_frame *);
frm = (const u_int8_t *)&wh[1];
token = frm[2];
params = LE_READ_2(&frm[3]);
tid = ((params & IEEE80211_ADDBA_TID_MASK) >>
IEEE80211_ADDBA_TID_SHIFT);
bufsz = (params & IEEE80211_ADDBA_BUFSZ_MASK) >>
IEEE80211_ADDBA_BUFSZ_SHIFT;
timeout = LE_READ_2(&frm[5]);
ssn = LE_READ_2(&frm[7]) >> 4;
ba = &ni->ni_rx_ba[tid];
if (ba->ba_state == IEEE80211_BA_REQUESTED)
return;
if ((ic->ic_flags & IEEE80211_F_BGSCAN) &&
(ic->ic_xflags & IEEE80211_F_TX_MGMT_ONLY))
return;
if (ba->ba_state == IEEE80211_BA_AGREED) {
if (ba->ba_timeout_val != 0)
timeout_add_usec(&ba->ba_to, ba->ba_timeout_val);
if (!(ni->ni_flags & IEEE80211_NODE_MFP) ||
!(ni->ni_rsncaps & IEEE80211_RSNCAP_PBAC))
return;
if (SEQ_LT(ba->ba_winstart, ssn)) {
struct mbuf_list ml = MBUF_LIST_INITIALIZER();
ieee80211_ba_move_window(ic, ni, tid, ssn, &ml);
if_input(&ic->ic_if, &ml);
}
return;
}
if ((ic->ic_flags & IEEE80211_F_PBAR) &&
(!(ni->ni_flags & IEEE80211_NODE_MFP) ||
!(ni->ni_rsncaps & IEEE80211_RSNCAP_PBAC)))
goto refuse;
if (ic->ic_tid_noack & (1 << tid))
goto refuse;
if (!(ic->ic_htcaps & IEEE80211_HTCAP_DELAYEDBA) &&
!(params & IEEE80211_ADDBA_BA_POLICY))
goto refuse;
ba->ba_state = IEEE80211_BA_REQUESTED;
ba->ba_timeout_val = timeout * IEEE80211_DUR_TU;
ba->ba_ni = ni;
ba->ba_token = token;
timeout_set(&ba->ba_to, ieee80211_rx_ba_timeout, ba);
timeout_set(&ba->ba_gap_to, ieee80211_input_ba_gap_timeout, ba);
ba->ba_gapwait = 0;
ba->ba_winsize = bufsz;
if (ba->ba_winsize == 0 || ba->ba_winsize > IEEE80211_BA_MAX_WINSZ)
ba->ba_winsize = IEEE80211_BA_MAX_WINSZ;
ba->ba_params = (params & IEEE80211_ADDBA_BA_POLICY);
ba->ba_params |= ((ba->ba_winsize << IEEE80211_ADDBA_BUFSZ_SHIFT) |
(tid << IEEE80211_ADDBA_TID_SHIFT));
ba->ba_params |= IEEE80211_ADDBA_AMSDU;
ba->ba_winstart = ssn;
ba->ba_winend = (ba->ba_winstart + ba->ba_winsize - 1) & 0xfff;
ba->ba_buf = malloc(IEEE80211_BA_MAX_WINSZ * sizeof(*ba->ba_buf),
M_DEVBUF, M_NOWAIT | M_ZERO);
if (ba->ba_buf == NULL)
goto refuse;
ba->ba_head = 0;
if (ic->ic_ampdu_rx_start != NULL)
err = ic->ic_ampdu_rx_start(ic, ni, tid);
if (err == EBUSY) {
return;
} else if (err) {
ieee80211_addba_req_refuse(ic, ni, tid);
} else
ieee80211_addba_req_accept(ic, ni, tid);
return;
refuse:
IEEE80211_SEND_ACTION(ic, ni, IEEE80211_CATEG_BA,
IEEE80211_ACTION_ADDBA_RESP,
IEEE80211_STATUS_REFUSED << 16 | token << 8 | tid);
}
void
ieee80211_addba_req_accept(struct ieee80211com *ic, struct ieee80211_node *ni,
uint8_t tid)
{
struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid];
ba->ba_state = IEEE80211_BA_AGREED;
ic->ic_stats.is_ht_rx_ba_agreements++;
if (ba->ba_timeout_val != 0)
timeout_add_usec(&ba->ba_to, ba->ba_timeout_val);
IEEE80211_SEND_ACTION(ic, ni, IEEE80211_CATEG_BA,
IEEE80211_ACTION_ADDBA_RESP,
IEEE80211_STATUS_SUCCESS << 16 | ba->ba_token << 8 | tid);
}
void
ieee80211_addba_req_refuse(struct ieee80211com *ic, struct ieee80211_node *ni,
uint8_t tid)
{
struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid];
free(ba->ba_buf, M_DEVBUF,
IEEE80211_BA_MAX_WINSZ * sizeof(*ba->ba_buf));
ba->ba_buf = NULL;
ba->ba_state = IEEE80211_BA_INIT;
IEEE80211_SEND_ACTION(ic, ni, IEEE80211_CATEG_BA,
IEEE80211_ACTION_ADDBA_RESP,
IEEE80211_STATUS_REFUSED << 16 | ba->ba_token << 8 | tid);
}
void
ieee80211_recv_addba_resp(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni)
{
const struct ieee80211_frame *wh;
const u_int8_t *frm;
struct ieee80211_tx_ba *ba;
u_int16_t status, params, bufsz, timeout;
u_int8_t token, tid;
int err = 0;
if (m->m_len < sizeof(*wh) + 9) {
DPRINTF(("frame too short\n"));
return;
}
wh = mtod(m, struct ieee80211_frame *);
frm = (const u_int8_t *)&wh[1];
token = frm[2];
status = LE_READ_2(&frm[3]);
params = LE_READ_2(&frm[5]);
tid = (params >> 2) & 0xf;
bufsz = (params >> 6) & 0x3ff;
timeout = LE_READ_2(&frm[7]);
DPRINTF(("received ADDBA resp from %s, TID %d, status %d\n",
ether_sprintf(ni->ni_macaddr), tid, status));
ba = &ni->ni_tx_ba[tid];
if (ba->ba_state != IEEE80211_BA_REQUESTED) {
DPRINTF(("no matching ADDBA req found\n"));
return;
}
if (token != ba->ba_token) {
DPRINTF(("ignoring ADDBA resp from %s: token %x!=%x\n",
ether_sprintf(ni->ni_macaddr), token, ba->ba_token));
return;
}
timeout_del(&ba->ba_to);
if (status != IEEE80211_STATUS_SUCCESS) {
if (ni->ni_addba_req_intval[tid] <
IEEE80211_ADDBA_REQ_INTVAL_MAX)
ni->ni_addba_req_intval[tid]++;
ieee80211_addba_resp_refuse(ic, ni, tid, status);
IEEE80211_SEND_ACTION(ic, ni, IEEE80211_CATEG_BA,
IEEE80211_ACTION_DELBA,
IEEE80211_REASON_SETUP_REQUIRED << 16 | 1 << 8 | tid);
return;
}
if (ic->ic_ampdu_tx_start != NULL)
err = ic->ic_ampdu_tx_start(ic, ni, tid);
if (err == EBUSY) {
return;
} else if (err) {
ieee80211_addba_resp_refuse(ic, ni, tid,
IEEE80211_STATUS_UNSPECIFIED);
} else
ieee80211_addba_resp_accept(ic, ni, tid);
}
void
ieee80211_addba_resp_accept(struct ieee80211com *ic,
struct ieee80211_node *ni, uint8_t tid)
{
struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
ba->ba_state = IEEE80211_BA_AGREED;
ic->ic_stats.is_ht_tx_ba_agreements++;
ni->ni_addba_req_intval[tid] = 1;
ni->ni_qos_txseqs[tid] = ba->ba_winstart;
if (ba->ba_timeout_val != 0)
timeout_add_usec(&ba->ba_to, ba->ba_timeout_val);
}
void
ieee80211_addba_resp_refuse(struct ieee80211com *ic,
struct ieee80211_node *ni, uint8_t tid, uint16_t status)
{
struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
ba->ba_state = IEEE80211_BA_INIT;
}
void
ieee80211_recv_delba(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni)
{
const struct ieee80211_frame *wh;
const u_int8_t *frm;
u_int16_t params, reason;
u_int8_t tid;
int i;
if (m->m_len < sizeof(*wh) + 6) {
DPRINTF(("frame too short\n"));
return;
}
wh = mtod(m, struct ieee80211_frame *);
frm = (const u_int8_t *)&wh[1];
params = LE_READ_2(&frm[2]);
reason = LE_READ_2(&frm[4]);
tid = params >> 12;
DPRINTF(("received DELBA from %s, TID %d, reason %d\n",
ether_sprintf(ni->ni_macaddr), tid, reason));
if (params & IEEE80211_DELBA_INITIATOR) {
struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid];
if (ba->ba_state != IEEE80211_BA_AGREED) {
DPRINTF(("no matching Block Ack agreement\n"));
return;
}
if (ic->ic_ampdu_rx_stop != NULL)
ic->ic_ampdu_rx_stop(ic, ni, tid);
ba->ba_state = IEEE80211_BA_INIT;
timeout_del(&ba->ba_to);
timeout_del(&ba->ba_gap_to);
ba->ba_gapwait = 0;
if (ba->ba_buf != NULL) {
for (i = 0; i < IEEE80211_BA_MAX_WINSZ; i++)
m_freem(ba->ba_buf[i].m);
free(ba->ba_buf, M_DEVBUF,
IEEE80211_BA_MAX_WINSZ * sizeof(*ba->ba_buf));
ba->ba_buf = NULL;
}
} else {
struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
if (ba->ba_state != IEEE80211_BA_AGREED) {
DPRINTF(("no matching Block Ack agreement\n"));
return;
}
if (ic->ic_ampdu_tx_stop != NULL)
ic->ic_ampdu_tx_stop(ic, ni, tid);
ba->ba_state = IEEE80211_BA_INIT;
timeout_del(&ba->ba_to);
}
}
void
ieee80211_recv_sa_query_req(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni)
{
const struct ieee80211_frame *wh;
const u_int8_t *frm;
if (ic->ic_opmode != IEEE80211_M_STA ||
!(ni->ni_flags & IEEE80211_NODE_MFP)) {
DPRINTF(("unexpected SA Query req from %s\n",
ether_sprintf(ni->ni_macaddr)));
return;
}
if (m->m_len < sizeof(*wh) + 4) {
DPRINTF(("frame too short\n"));
return;
}
wh = mtod(m, struct ieee80211_frame *);
frm = (const u_int8_t *)&wh[1];
ni->ni_sa_query_trid = LE_READ_2(&frm[2]);
IEEE80211_SEND_ACTION(ic, ni, IEEE80211_CATEG_SA_QUERY,
IEEE80211_ACTION_SA_QUERY_RESP, 0);
}
#ifndef IEEE80211_STA_ONLY
void
ieee80211_recv_sa_query_resp(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni)
{
const struct ieee80211_frame *wh;
const u_int8_t *frm;
if (!(ni->ni_flags & IEEE80211_NODE_SA_QUERY)) {
DPRINTF(("unexpected SA Query resp from %s\n",
ether_sprintf(ni->ni_macaddr)));
return;
}
if (m->m_len < sizeof(*wh) + 4) {
DPRINTF(("frame too short\n"));
return;
}
wh = mtod(m, struct ieee80211_frame *);
frm = (const u_int8_t *)&wh[1];
if (ni->ni_sa_query_trid != LE_READ_2(&frm[2])) {
DPRINTF(("transaction identifier does not match\n"));
return;
}
timeout_del(&ni->ni_sa_query_to);
ni->ni_flags &= ~IEEE80211_NODE_SA_QUERY;
}
#endif
void
ieee80211_recv_action(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni)
{
const struct ieee80211_frame *wh;
const u_int8_t *frm;
if (m->m_len < sizeof(*wh) + 2) {
DPRINTF(("frame too short\n"));
return;
}
wh = mtod(m, struct ieee80211_frame *);
frm = (const u_int8_t *)&wh[1];
switch (frm[0]) {
case IEEE80211_CATEG_BA:
switch (frm[1]) {
case IEEE80211_ACTION_ADDBA_REQ:
ieee80211_recv_addba_req(ic, m, ni);
break;
case IEEE80211_ACTION_ADDBA_RESP:
ieee80211_recv_addba_resp(ic, m, ni);
break;
case IEEE80211_ACTION_DELBA:
ieee80211_recv_delba(ic, m, ni);
break;
}
break;
case IEEE80211_CATEG_SA_QUERY:
switch (frm[1]) {
case IEEE80211_ACTION_SA_QUERY_REQ:
ieee80211_recv_sa_query_req(ic, m, ni);
break;
#ifndef IEEE80211_STA_ONLY
case IEEE80211_ACTION_SA_QUERY_RESP:
ieee80211_recv_sa_query_resp(ic, m, ni);
break;
#endif
}
break;
default:
DPRINTF(("action frame category %d not handled\n", frm[0]));
break;
}
}
void
ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni, struct ieee80211_rxinfo *rxi, int subtype)
{
switch (subtype) {
case IEEE80211_FC0_SUBTYPE_BEACON:
ieee80211_recv_probe_resp(ic, m, ni, rxi, 0);
break;
case IEEE80211_FC0_SUBTYPE_PROBE_RESP:
ieee80211_recv_probe_resp(ic, m, ni, rxi, 1);
break;
#ifndef IEEE80211_STA_ONLY
case IEEE80211_FC0_SUBTYPE_PROBE_REQ:
ieee80211_recv_probe_req(ic, m, ni, rxi);
break;
#endif
case IEEE80211_FC0_SUBTYPE_AUTH:
ieee80211_recv_auth(ic, m, ni, rxi);
break;
#ifndef IEEE80211_STA_ONLY
case IEEE80211_FC0_SUBTYPE_ASSOC_REQ:
ieee80211_recv_assoc_req(ic, m, ni, rxi, 0);
break;
case IEEE80211_FC0_SUBTYPE_REASSOC_REQ:
ieee80211_recv_assoc_req(ic, m, ni, rxi, 1);
break;
#endif
case IEEE80211_FC0_SUBTYPE_ASSOC_RESP:
ieee80211_recv_assoc_resp(ic, m, ni, 0);
break;
case IEEE80211_FC0_SUBTYPE_REASSOC_RESP:
ieee80211_recv_assoc_resp(ic, m, ni, 1);
break;
case IEEE80211_FC0_SUBTYPE_DEAUTH:
ieee80211_recv_deauth(ic, m, ni);
break;
case IEEE80211_FC0_SUBTYPE_DISASSOC:
ieee80211_recv_disassoc(ic, m, ni);
break;
case IEEE80211_FC0_SUBTYPE_ACTION:
ieee80211_recv_action(ic, m, ni);
break;
default:
DPRINTF(("mgmt frame with subtype 0x%x not handled\n",
subtype));
ic->ic_stats.is_rx_badsubtype++;
break;
}
}
#ifndef IEEE80211_STA_ONLY
void
ieee80211_recv_pspoll(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni)
{
struct ifnet *ifp = &ic->ic_if;
struct ieee80211_frame_pspoll *psp;
struct ieee80211_frame *wh;
u_int16_t aid;
if (ic->ic_opmode != IEEE80211_M_HOSTAP ||
!(ic->ic_caps & IEEE80211_C_APPMGT) ||
ni->ni_state != IEEE80211_STA_ASSOC)
return;
if (m->m_len < sizeof(*psp)) {
DPRINTF(("frame too short, len %u\n", m->m_len));
ic->ic_stats.is_rx_tooshort++;
return;
}
psp = mtod(m, struct ieee80211_frame_pspoll *);
if (!IEEE80211_ADDR_EQ(psp->i_bssid, ic->ic_bss->ni_bssid)) {
DPRINTF(("discard pspoll frame to BSS %s\n",
ether_sprintf(psp->i_bssid)));
ic->ic_stats.is_rx_wrongbss++;
return;
}
aid = letoh16(*(u_int16_t *)psp->i_aid);
if (aid != ni->ni_associd) {
DPRINTF(("invalid pspoll aid %x from %s\n", aid,
ether_sprintf(psp->i_ta)));
return;
}
m = mq_dequeue(&ni->ni_savedq);
if (m == NULL)
return;
if (mq_empty(&ni->ni_savedq)) {
(*ic->ic_set_tim)(ic, ni->ni_associd, 0);
} else {
wh = mtod(m, struct ieee80211_frame *);
wh->i_fc[1] |= IEEE80211_FC1_MORE_DATA;
}
mq_enqueue(&ic->ic_pwrsaveq, m);
if_start(ifp);
}
#endif
void
ieee80211_recv_bar(struct ieee80211com *ic, struct mbuf *m,
struct ieee80211_node *ni)
{
const struct ieee80211_frame_min *wh;
const u_int8_t *frm;
u_int16_t ctl, ssn;
u_int8_t tid, ntids;
if (!(ni->ni_flags & IEEE80211_NODE_HT)) {
DPRINTF(("received BlockAckReq from non-HT STA %s\n",
ether_sprintf(ni->ni_macaddr)));
return;
}
if (m->m_len < sizeof(*wh) + 4) {
DPRINTF(("frame too short\n"));
return;
}
wh = mtod(m, struct ieee80211_frame_min *);
frm = (const u_int8_t *)&wh[1];
ctl = LE_READ_2(&frm[0]);
tid = ctl >> 12;
if (ctl & IEEE80211_BA_MULTI_TID) {
ntids = tid + 1;
if (m->m_len < sizeof(*wh) + 2 + 4 * ntids) {
DPRINTF(("MTBAR frame too short\n"));
return;
}
frm += 2;
while (ntids-- > 0) {
tid = LE_READ_2(&frm[0]) >> 12;
ssn = LE_READ_2(&frm[2]) >> 4;
ieee80211_bar_tid(ic, ni, tid, ssn);
frm += 4;
}
} else {
ssn = LE_READ_2(&frm[2]) >> 4;
ieee80211_bar_tid(ic, ni, tid, ssn);
}
}
void
ieee80211_bar_tid(struct ieee80211com *ic, struct ieee80211_node *ni,
u_int8_t tid, u_int16_t ssn)
{
struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid];
if (ba->ba_state != IEEE80211_BA_AGREED) {
IEEE80211_SEND_ACTION(ic, ni, IEEE80211_CATEG_BA,
IEEE80211_ACTION_DELBA,
IEEE80211_REASON_SETUP_REQUIRED << 16 | tid);
return;
}
if ((ni->ni_flags & IEEE80211_NODE_MFP) &&
(ni->ni_rsncaps & IEEE80211_RSNCAP_PBAC)) {
if (SEQ_LT(ssn, ba->ba_winstart) ||
SEQ_LT(ba->ba_winend, ssn))
ic->ic_stats.is_pbac_errs++;
return;
}
if (ba->ba_timeout_val != 0)
timeout_add_usec(&ba->ba_to, ba->ba_timeout_val);
if (SEQ_LT(ba->ba_winstart, ssn)) {
struct mbuf_list ml = MBUF_LIST_INITIALIZER();
ieee80211_ba_move_window(ic, ni, tid, ssn, &ml);
if_input(&ic->ic_if, &ml);
}
}