summaryrefslogtreecommitdiffstats
path: root/sys/net80211/ieee80211_output.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/net80211/ieee80211_output.c')
-rw-r--r--sys/net80211/ieee80211_output.c141
1 files changed, 104 insertions, 37 deletions
diff --git a/sys/net80211/ieee80211_output.c b/sys/net80211/ieee80211_output.c
index 9b79d65..1fa87f2 100644
--- a/sys/net80211/ieee80211_output.c
+++ b/sys/net80211/ieee80211_output.c
@@ -66,20 +66,41 @@ __FBSDID("$FreeBSD$");
#include <netinet/if_ether.h>
#endif
-int
+/*
+ * Send a management frame to the specified node. The node pointer
+ * must have a reference as the pointer will be passed to the driver
+ * and potentially held for a long time. If the frame is successfully
+ * dispatched to the driver, then it is responsible for freeing the
+ * reference (and potentially free'ing up any associated storage).
+ */
+static int
ieee80211_mgmt_output(struct ifnet *ifp, struct ieee80211_node *ni,
struct mbuf *m, int type)
{
struct ieee80211com *ic = (void *)ifp;
struct ieee80211_frame *wh;
- /* XXX this probably shouldn't be permitted */
- KASSERT(ni != NULL, ("%s: null node", __func__));
+ KASSERT(ni != NULL, ("null node"));
ni->ni_inact = 0;
+ /*
+ * Yech, hack alert! We want to pass the node down to the
+ * driver's start routine. If we don't do so then the start
+ * routine must immediately look it up again and that can
+ * cause a lock order reversal if, for example, this frame
+ * is being sent because the station is being timedout and
+ * the frame being sent is a DEAUTH message. We could stick
+ * this in an m_tag and tack that on to the mbuf. However
+ * that's rather expensive to do for every frame so instead
+ * we stuff it in the rcvif field since outbound frames do
+ * not (presently) use this.
+ */
M_PREPEND(m, sizeof(struct ieee80211_frame), M_DONTWAIT);
if (m == NULL)
return ENOMEM;
+ KASSERT(m->m_pkthdr.rcvif == NULL, ("rcvif not null"));
+ m->m_pkthdr.rcvif = (void *)ni;
+
wh = mtod(m, struct ieee80211_frame *);
wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT | type;
wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
@@ -106,31 +127,57 @@ ieee80211_mgmt_output(struct ifnet *ifp, struct ieee80211_node *ni,
ether_sprintf(ni->ni_macaddr),
ieee80211_chan2ieee(ic, ni->ni_chan));
}
+
IF_ENQUEUE(&ic->ic_mgtq, m);
ifp->if_timer = 1;
(*ifp->if_start)(ifp);
return 0;
}
+/*
+ * Encapsulate an outbound data frame. The mbuf chain is updated and
+ * a reference to the destination node is returned. If an error is
+ * encountered NULL is returned and the node reference will also be NULL.
+ *
+ * NB: The caller is responsible for free'ing a returned node reference.
+ * The convention is ic_bss is not reference counted; the caller must
+ * maintain that.
+ */
struct mbuf *
-ieee80211_encap(struct ifnet *ifp, struct mbuf *m)
+ieee80211_encap(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node **pni)
{
struct ieee80211com *ic = (void *)ifp;
struct ether_header eh;
struct ieee80211_frame *wh;
+ struct ieee80211_node *ni = NULL;
struct llc *llc;
- struct ieee80211_node *ni;
if (m->m_len < sizeof(struct ether_header)) {
m = m_pullup(m, sizeof(struct ether_header));
- if (m == NULL)
- return NULL;
+ if (m == NULL) {
+ /* XXX statistic */
+ goto bad;
+ }
}
memcpy(&eh, mtod(m, caddr_t), sizeof(struct ether_header));
- ni = ieee80211_find_node(ic, eh.ether_dhost);
- if (ni == NULL) /*ic_opmode?? XXX*/
- ni = ieee80211_ref_node(ic->ic_bss);
+ if (ic->ic_opmode != IEEE80211_M_STA) {
+ ni = ieee80211_find_node(ic, eh.ether_dhost);
+ if (ni == NULL) {
+ /*
+ * When not in station mode the
+ * destination address should always be
+ * in the node table unless this is a
+ * multicast/broadcast frame.
+ */
+ if (!IEEE80211_IS_MULTICAST(eh.ether_dhost)) {
+ /* ic->ic_stats.st_tx_nonode++; XXX statistic */
+ goto bad;
+ }
+ ni = ic->ic_bss;
+ }
+ } else
+ ni = ic->ic_bss;
ni->ni_inact = 0;
m_adj(m, sizeof(struct ether_header) - sizeof(struct llc));
@@ -142,10 +189,8 @@ ieee80211_encap(struct ifnet *ifp, struct mbuf *m)
llc->llc_snap.org_code[2] = 0;
llc->llc_snap.ether_type = eh.ether_type;
M_PREPEND(m, sizeof(struct ieee80211_frame), M_DONTWAIT);
- if (m == NULL) {
- ieee80211_unref_node(&ni);
- return NULL;
- }
+ if (m == NULL)
+ goto bad;
wh = mtod(m, struct ieee80211_frame *);
wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_DATA;
*(u_int16_t *)wh->i_dur = 0;
@@ -173,11 +218,17 @@ ieee80211_encap(struct ifnet *ifp, struct mbuf *m)
IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_shost);
break;
case IEEE80211_M_MONITOR:
- m_freem(m), m = NULL;
- break;
+ goto bad;
}
- ieee80211_unref_node(&ni);
+ *pni = ni;
return m;
+bad:
+ if (m != NULL)
+ m_freem(m);
+ if (ni && ni != ic->ic_bss)
+ ieee80211_free_node(ic, ni);
+ *pni = NULL;
+ return NULL;
}
/*
@@ -240,10 +291,16 @@ ieee80211_getmbuf(int flags, int type, u_int pktlen)
return m;
}
+/*
+ * Send a management frame. The node is for the destination (or ic_bss
+ * when in station mode). Nodes other than ic_bss have their reference
+ * count bumped to reflect our use for an indeterminant time.
+ */
int
ieee80211_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
int type, int arg)
{
+#define senderr(_x) do { ret = _x; goto bad; } while (0)
struct ifnet *ifp = &ic->ic_if;
struct mbuf *m;
u_int8_t *frm;
@@ -251,6 +308,15 @@ ieee80211_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
u_int16_t capinfo;
int ret, timer;
+ KASSERT(ni != NULL, ("null node"));
+
+ /*
+ * Hold a reference on the node so it doesn't go away until after
+ * the xmit is complete all the way in the driver. On error we
+ * will remove our reference.
+ */
+ if (ni != ic->ic_bss)
+ ieee80211_ref_node(ni);
timer = 0;
switch (type) {
case IEEE80211_FC0_SUBTYPE_PROBE_REQ:
@@ -265,7 +331,7 @@ ieee80211_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
+ 2 + IEEE80211_RATE_SIZE
+ 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE));
if (m == NULL)
- return ENOMEM;
+ senderr(ENOMEM);
m->m_data += sizeof(struct ieee80211_frame);
frm = mtod(m, u_int8_t *);
frm = ieee80211_add_ssid(frm, ic->ic_des_essid, ic->ic_des_esslen);
@@ -295,7 +361,7 @@ ieee80211_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
+ 6
+ 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE));
if (m == NULL)
- return ENOMEM;
+ senderr(ENOMEM);
m->m_data += sizeof(struct ieee80211_frame);
frm = mtod(m, u_int8_t *);
@@ -336,7 +402,7 @@ ieee80211_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
case IEEE80211_FC0_SUBTYPE_AUTH:
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL)
- return ENOMEM;
+ senderr(ENOMEM);
MH_ALIGN(m, 2 * 3);
m->m_pkthdr.len = m->m_len = 6;
frm = mtod(m, u_int8_t *);
@@ -354,7 +420,7 @@ ieee80211_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
ether_sprintf(ni->ni_macaddr), arg);
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL)
- return ENOMEM;
+ senderr(ENOMEM);
MH_ALIGN(m, 2);
m->m_pkthdr.len = m->m_len = 2;
*mtod(m, u_int16_t *) = htole16(arg); /* reason */
@@ -379,7 +445,7 @@ ieee80211_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
+ 2 + IEEE80211_RATE_SIZE
+ 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE));
if (m == NULL)
- return ENOMEM;
+ senderr(ENOMEM);
m->m_data += sizeof(struct ieee80211_frame);
frm = mtod(m, u_int8_t *);
@@ -430,7 +496,7 @@ ieee80211_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
+ 2 + IEEE80211_RATE_SIZE
+ 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE));
if (m == NULL)
- return ENOMEM;
+ senderr(ENOMEM);
m->m_data += sizeof(struct ieee80211_frame);
frm = mtod(m, u_int8_t *);
@@ -443,19 +509,12 @@ ieee80211_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
*(u_int16_t *)frm = htole16(arg); /* status */
frm += 2;
- if (arg == IEEE80211_STATUS_SUCCESS && ni != NULL)
+ if (arg == IEEE80211_STATUS_SUCCESS)
*(u_int16_t *)frm = htole16(ni->ni_associd);
- else
- *(u_int16_t *)frm = htole16(0);
frm += 2;
- if (ni != NULL) {
- frm = ieee80211_add_rates(frm, &ni->ni_rates);
- frm = ieee80211_add_xrates(frm, &ni->ni_rates);
- } else {
- frm = ieee80211_add_rates(frm, &ic->ic_bss->ni_rates);
- frm = ieee80211_add_xrates(frm, &ic->ic_bss->ni_rates);
- }
+ frm = ieee80211_add_rates(frm, &ni->ni_rates);
+ frm = ieee80211_add_xrates(frm, &ni->ni_rates);
m->m_pkthdr.len = m->m_len = frm - mtod(m, u_int8_t *);
break;
@@ -465,7 +524,7 @@ ieee80211_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
ether_sprintf(ni->ni_macaddr), arg);
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL)
- return ENOMEM;
+ senderr(ENOMEM);
MH_ALIGN(m, 2);
m->m_pkthdr.len = m->m_len = 2;
*mtod(m, u_int16_t *) = htole16(arg); /* reason */
@@ -474,11 +533,19 @@ ieee80211_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
default:
IEEE80211_DPRINTF(("%s: invalid mgmt frame type %u\n",
__func__, type));
- return EINVAL;
+ senderr(EINVAL);
+ /* NOTREACHED */
}
ret = ieee80211_mgmt_output(ifp, ni, m, type);
- if (ret == 0 && timer)
- ic->ic_mgt_timer = timer;
+ if (ret == 0) {
+ if (timer)
+ ic->ic_mgt_timer = timer;
+ } else {
+bad:
+ if (ni != ic->ic_bss) /* remove ref we added */
+ ieee80211_free_node(ic, ni);
+ }
return ret;
+#undef senderr
}
OpenPOWER on IntegriCloud