Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

mac80211: Tear down BA session on BAR tx failure

As described at [1] some STAs (i.e. Intel 5100 Windows) can end up
correctly BlockAcking incoming frames without delivering them to user
space if a AMPDU subframe got lost and we don't flush the receipients
reorder buffer with a BlockAckReq. This in turn results in stuck
connections.

According to 802.11n-2009 it is not necessary to send a BAR to flush
the recepients RX reorder buffer but we still do that to be polite.

However, assume the following frame exchange:

AP -> STA, AMPDU (failed)
AP -> STA, BAR (failed)

The client in question then ends up in the same situation and won't
deliver frames to userspace anymore since we weren't able to flush
its reorder buffer.

This is not a hypothetical situation but I was able to observe this
exact behavior during a stress test between a rt2800pci AP and a Intel
5100 Windows client.

In order to work around this issue just tear down the BA session as
soon as a BAR failed to be TX'ed.

[1] http://comments.gmane.org/gmane.linux.kernel.wireless.general/66867

Signed-off-by: Helmut Schaa <helmut.schaa@googlemail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>

authored by

Helmut Schaa and committed by
John W. Linville
e69deded c1407b6c

+18
+18
net/mac80211/status.c
··· 187 187 int rates_idx = -1; 188 188 bool send_to_cooked; 189 189 bool acked; 190 + struct ieee80211_bar *bar; 191 + u16 tid; 190 192 191 193 for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { 192 194 if (info->status.rates[i].idx < 0) { ··· 243 241 & IEEE80211_SCTL_SEQ); 244 242 ieee80211_send_bar(sta->sdata, hdr->addr1, 245 243 tid, ssn); 244 + } 245 + 246 + if (!acked && ieee80211_is_back_req(fc)) { 247 + /* 248 + * BAR failed, let's tear down the BA session as a 249 + * last resort as some STAs (Intel 5100 on Windows) 250 + * can get stuck when the BA window isn't flushed 251 + * correctly. 252 + */ 253 + bar = (struct ieee80211_bar *) skb->data; 254 + if (!(bar->control & IEEE80211_BAR_CTRL_MULTI_TID)) { 255 + tid = (bar->control & 256 + IEEE80211_BAR_CTRL_TID_INFO_MASK) >> 257 + IEEE80211_BAR_CTRL_TID_INFO_SHIFT; 258 + ieee80211_stop_tx_ba_session(&sta->sta, tid); 259 + } 246 260 } 247 261 248 262 if (info->flags & IEEE80211_TX_STAT_TX_FILTERED) {