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