This patch completes net80211 support for receiving A-MPDUs.

This is a required feature of 11n. Technically, sending A-MPDUS
is required, too, but since we're not trying to get certified by
the Wifi Alliance we can postpone this until later. Devices we
interoperate with won't care if we don't send them.

A-MPDUs are aggregate frames containing several MPDUs (i.e. several
full 802.11 frames with MAC headers). Each subframe is separated by
a delimiter which contains the length and CRC of the subframe, and a
trailer for padding. If you'd like to see a visual illustration,
search the web yourself, or look at this one: 
http://m.eet.com/media/1110804/802fig6.gif

These frames are used to avoid overhead of separate ACKs for
individual 802.11 frames. From the point of view of 11a/b/g STAs
the medium is reserved by the AP for the entire period during
which an A-MPDU is sent by the AP or another 11n STA.

Multicast frames are never sent in an A-MPDU. Before A-MPDUs
can be sent a Block Ack agreement must be established between
2 peers. A Block Ack agreement is identified by an arbitrarily
chosen traffic identifier value (TID) and the peer's MAC address.
Once an agreement is set up, the hardware/firmware on each side
keeps track of successfully received and failed subframes of
an A-MPDU and signals success or failure of subframes to the
peer via a bitmap contained in a BlockAck response frame.
This BlockAck response is invisible to the driver and net80211 layers.

Subframes received successfully are passed up to the driver and
net80211 layer, where they appear like regular 802.11 frames.
Subframes are matched to an existing BlockAck agreement with the
peer (ieee80211_node) based on the TID in their MAC header.
Because subframes may be received out of sequence, the net80211 layer
has to buffer subframes which cannot yet be passed to the upper
layers of the network stack since their sequence number is too new.
The net80211 layer is aware of the current sequence number window
and slides it forward as needed after either passing subframes
up the stack or discarding the entire set of buffered frames in
case the window has moved forward too far.

Note that there is an injection attack against A-MPDUs which we can do
nothing about at the driver level: https://github.com/rpp0/aggr-inject
The problem is rooted in poorly written wireless device firmware code
responsible for splitting A-MPDU frames into subframes.
The attack is possible on unencrypted wifi only.
Once OpenBSD starts sending A-MPDUs we can prevent this attack being
performed through OpenBSD by not sending A-MPDUs on unencrypted networks.

Most of the implementation was written by damien@ years ago and
was already committed back then. This diff adds the missing pieces
and fixes a few bugs. The changes are:

 - In ieee80211_input(), do A-MPDU processing before duplicate
   detection based on sequence number. The previous order was
   wrong since subframes are passed into ieee80211_input() twice,
   once before going through the buffer for reordering and once
   after being sent back into ieee80211_input() from the buffer.

 - Don't forget to set ba->ba_ni in ieee80211_recv_addba_req()
   so we don't crash in ieee80211_rx_ba_timeout().

 - In ieee80211_recv_addba_req(), tweak the logic to deny BlockAck
   requests if the driver has no callback for doing so. This shouldn't
   happen in production but helps during development while adding 11n
   support to a driver.

 - Implement ieee80211_ba_del() which cleans up BlockAck state kept
   in the ieee80211_node structure. This is called when the node
   resets all of its state or when the node is freed.

 - Increase the minimum and maximum lifetime for BlockAck agrements.
   There seem to be no values given in the spec, and neither Linux
   nor FreeBSD seem to enforce any limits. The existing minimum is
   too short for BlockAck to be effective because the agreement may
   already be cancelled before the first A-MPDU arrives. I chose the
   new values arbitrarily. These values may be tuned in the future if
   necessary.

Tested against several APs.

OK?

Index: net80211/ieee80211_input.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_input.c,v
retrieving revision 1.142
diff -u -p -r1.142 ieee80211_input.c
--- net80211/ieee80211_input.c  15 Nov 2015 11:14:17 -0000      1.142
+++ net80211/ieee80211_input.c  12 Dec 2015 08:49:12 -0000
@@ -280,6 +280,43 @@ ieee80211_input(struct ifnet *ifp, struc
                tid = 0;
        }
 
+#ifndef IEEE80211_NO_HT
+       if (type == IEEE80211_FC0_TYPE_DATA && hasqos &&
+           !(rxi->rxi_flags & IEEE80211_RXI_AMPDU_DONE)) {
+               int ba_state = ni->ni_rx_ba[tid].ba_state;
+
+               /* 
+                * If Block Ack was explicitly requested, check
+                * if we have a BA agreement for this RA/TID.
+                */
+               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));
+                       /* send a DELBA with reason code UNKNOWN-BA */
+                       IEEE80211_SEND_ACTION(ic, ni,
+                           IEEE80211_CATEG_BA, IEEE80211_ACTION_DELBA,
+                           IEEE80211_REASON_SETUP_REQUIRED << 16 | tid);
+                       goto err;
+               }
+
+               /* 
+                * Check if we have an explicit or implicit
+                * Block Ack Request for a valid BA agreement.
+                */
+               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)) {
+                       /* go through A-MPDU reordering */
+                       ieee80211_input_ba(ifp, m, ni, tid, rxi);
+                       return; /* don't free m! */
+               }
+       }
+#endif
+
        /* duplicate detection (see 9.2.9) */
        if (ieee80211_has_seq(wh) &&
            ic->ic_state != IEEE80211_S_SCAN) {
@@ -430,27 +467,6 @@ ieee80211_input(struct ifnet *ifp, struc
                        goto out;
                }
 
-#ifndef IEEE80211_NO_HT
-               if (!(rxi->rxi_flags & IEEE80211_RXI_AMPDU_DONE) &&
-                   hasqos && (qos & IEEE80211_QOS_ACK_POLICY_MASK) ==
-                   IEEE80211_QOS_ACK_POLICY_BA) {
-                       /* check if we have a BA agreement for this RA/TID */
-                       if (ni->ni_rx_ba[tid].ba_state !=
-                           IEEE80211_BA_AGREED) {
-                               DPRINTF(("no BA agreement for %s, TID %d\n",
-                                   ether_sprintf(ni->ni_macaddr), tid));
-                               /* send a DELBA with reason code UNKNOWN-BA */
-                               IEEE80211_SEND_ACTION(ic, ni,
-                                   IEEE80211_CATEG_BA, IEEE80211_ACTION_DELBA,
-                                   IEEE80211_REASON_SETUP_REQUIRED << 16 |
-                                   tid);
-                               goto err;
-                       }
-                       /* go through A-MPDU reordering */
-                       ieee80211_input_ba(ifp, m, ni, tid, rxi);
-                       return; /* don't free m! */
-               }
-#endif
                if ((ic->ic_flags & IEEE80211_F_WEPON) ||
                    ((ic->ic_flags & IEEE80211_F_RSNON) &&
                     (ni->ni_flags & IEEE80211_NODE_RXPROT))) {
@@ -2449,6 +2465,7 @@ ieee80211_recv_addba_req(struct ieee8021
                ba->ba_timeout_val = IEEE80211_BA_MIN_TIMEOUT;
        else if (ba->ba_timeout_val > IEEE80211_BA_MAX_TIMEOUT)
                ba->ba_timeout_val = IEEE80211_BA_MAX_TIMEOUT;
+       ba->ba_ni = ni;
        timeout_set(&ba->ba_to, ieee80211_rx_ba_timeout, ba);
        ba->ba_winsize = bufsz;
        if (ba->ba_winsize == 0 || ba->ba_winsize > IEEE80211_BA_MAX_WINSZ)
@@ -2465,7 +2482,7 @@ ieee80211_recv_addba_req(struct ieee8021
        ba->ba_head = 0;
 
        /* notify drivers of this new Block Ack agreement */
-       if (ic->ic_ampdu_rx_start != NULL &&
+       if (ic->ic_ampdu_rx_start == NULL ||
            ic->ic_ampdu_rx_start(ic, ni, tid) != 0) {
                /* driver failed to setup, rollback */
                free(ba->ba_buf, M_DEVBUF, 0);
Index: net80211/ieee80211_node.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_node.c,v
retrieving revision 1.92
diff -u -p -r1.92 ieee80211_node.c
--- net80211/ieee80211_node.c   24 Nov 2015 13:45:06 -0000      1.92
+++ net80211/ieee80211_node.c   12 Dec 2015 08:49:12 -0000
@@ -68,6 +68,9 @@ u_int8_t ieee80211_node_getrssi(struct i
 void ieee80211_setup_node(struct ieee80211com *, struct ieee80211_node *,
     const u_int8_t *);
 void ieee80211_free_node(struct ieee80211com *, struct ieee80211_node *);
+#ifndef IEEE80211_NO_HT
+void ieee80211_ba_del(struct ieee80211_node *);
+#endif
 struct ieee80211_node *ieee80211_alloc_node_helper(struct ieee80211com *);
 void ieee80211_node_cleanup(struct ieee80211com *, struct ieee80211_node *);
 void ieee80211_needs_auth(struct ieee80211com *, struct ieee80211_node *);
@@ -757,6 +760,9 @@ ieee80211_node_cleanup(struct ieee80211c
                free(ni->ni_rsnie, M_DEVBUF, 0);
                ni->ni_rsnie = NULL;
        }
+#ifndef IEEE80211_NO_HT
+       ieee80211_ba_del(ni);
+#endif
 }
 
 void
@@ -1065,6 +1071,32 @@ ieee80211_find_node_for_beacon(struct ie
        return (keep);
 }
 
+#ifndef IEEE80211_NO_HT
+void
+ieee80211_ba_del(struct ieee80211_node *ni)
+{
+       int tid;
+
+       for (tid = 0; tid < nitems(ni->ni_rx_ba); tid++) {
+               struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid];
+               if (ba->ba_state == IEEE80211_BA_AGREED) {
+                       if (timeout_pending(&ba->ba_to))
+                               timeout_del(&ba->ba_to);
+                       ba->ba_state = IEEE80211_BA_INIT;
+               }
+       }
+
+       for (tid = 0; tid < nitems(ni->ni_tx_ba); tid++) {
+               struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
+               if (ba->ba_state == IEEE80211_BA_AGREED) {
+                       if (timeout_pending(&ba->ba_to))
+                               timeout_del(&ba->ba_to);
+                       ba->ba_state = IEEE80211_BA_INIT;
+               }
+       }
+}
+#endif
+
 void
 ieee80211_free_node(struct ieee80211com *ic, struct ieee80211_node *ni)
 {
@@ -1078,6 +1110,9 @@ ieee80211_free_node(struct ieee80211com 
        timeout_del(&ni->ni_eapol_to);
        timeout_del(&ni->ni_sa_query_to);
        IEEE80211_AID_CLR(ni->ni_associd, ic->ic_aid_bitmap);
+#endif
+#ifndef IEEE80211_NO_HT
+       ieee80211_ba_del(ni);
 #endif
        RB_REMOVE(ieee80211_tree, &ic->ic_tree, ni);
        ic->ic_nnodes--;
Index: net80211/ieee80211_node.h
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_node.h,v
retrieving revision 1.49
diff -u -p -r1.49 ieee80211_node.h
--- net80211/ieee80211_node.h   15 Nov 2015 12:34:07 -0000      1.49
+++ net80211/ieee80211_node.h   12 Dec 2015 08:49:12 -0000
@@ -112,8 +112,8 @@ struct ieee80211_tx_ba {
        struct ieee80211_node   *ba_ni; /* backpointer for callbacks */
        struct timeout          ba_to;
        int                     ba_timeout_val;
-#define IEEE80211_BA_MIN_TIMEOUT       (10 * 1000)             /* 10msec */
-#define IEEE80211_BA_MAX_TIMEOUT       (10 * 1000 * 1000)      /* 10sec */
+#define IEEE80211_BA_MIN_TIMEOUT       (10 * 1000 * 1000)      /* 10 sec */
+#define IEEE80211_BA_MAX_TIMEOUT       (60 * 1000 * 1000)      /* 60 sec */
 
        int                     ba_state;
 #define IEEE80211_BA_INIT      0

Reply via email to