mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-21 22:47:56 +00:00
ec33a6ca2c
The standard defines the A-MSDU header length field differently for mesh compared to other modes. Deal with this accordingly and work around broken implementations (e.g. ath10k, ath11k). Signed-off-by: Felix Fietkau <nbd@nbd.name>
146 lines
4.4 KiB
Diff
146 lines
4.4 KiB
Diff
From: Felix Fietkau <nbd@nbd.name>
|
|
Date: Fri, 9 Dec 2022 21:15:04 +0100
|
|
Subject: [PATCH] wifi: mac80211: add a workaround for receiving
|
|
non-standard mesh A-MSDU
|
|
|
|
At least ath10k and ath11k supported hardware (maybe more) does not implement
|
|
mesh A-MSDU aggregation in a standard compliant way.
|
|
802.11-2020 9.3.2.2.2 declares that the Mesh Control field is part of the
|
|
A-MSDU header. As such, its length must not be included in the subframe
|
|
length field.
|
|
Hardware affected by this bug treats the mesh control field as part of the
|
|
MSDU data and sets the length accordingly.
|
|
In order to avoid packet loss, keep track of which stations are affected
|
|
by this and take it into account when converting A-MSDU to 802.3 + mesh control
|
|
packets.
|
|
|
|
Signed-off-by: Felix Fietkau <nbd@nbd.name>
|
|
---
|
|
|
|
--- a/include/net/cfg80211.h
|
|
+++ b/include/net/cfg80211.h
|
|
@@ -6194,6 +6194,19 @@ static inline int ieee80211_data_to_8023
|
|
}
|
|
|
|
/**
|
|
+ * ieee80211_is_valid_amsdu - check if subframe lengths of an A-MSDU are valid
|
|
+ *
|
|
+ * This is used to detect non-standard A-MSDU frames, e.g. the ones generated
|
|
+ * by ath10k and ath11k, where the subframe length includes the length of the
|
|
+ * mesh control field.
|
|
+ *
|
|
+ * @skb: The input A-MSDU frame without any headers.
|
|
+ * @mesh_hdr: use standard compliant mesh A-MSDU subframe header
|
|
+ * Returns: true if subframe header lengths are valid for the @mesh_hdr mode
|
|
+ */
|
|
+bool ieee80211_is_valid_amsdu(struct sk_buff *skb, bool mesh_hdr);
|
|
+
|
|
+/**
|
|
* ieee80211_amsdu_to_8023s - decode an IEEE 802.11n A-MSDU frame
|
|
*
|
|
* Decode an IEEE 802.11 A-MSDU and convert it to a list of 802.3 frames.
|
|
--- a/net/mac80211/rx.c
|
|
+++ b/net/mac80211/rx.c
|
|
@@ -2899,7 +2899,6 @@ __ieee80211_rx_h_amsdu(struct ieee80211_
|
|
static ieee80211_rx_result res;
|
|
struct ethhdr ethhdr;
|
|
const u8 *check_da = ethhdr.h_dest, *check_sa = ethhdr.h_source;
|
|
- bool mesh = false;
|
|
|
|
if (unlikely(ieee80211_has_a4(hdr->frame_control))) {
|
|
check_da = NULL;
|
|
@@ -2917,7 +2916,6 @@ __ieee80211_rx_h_amsdu(struct ieee80211_
|
|
case NL80211_IFTYPE_MESH_POINT:
|
|
check_sa = NULL;
|
|
check_da = NULL;
|
|
- mesh = true;
|
|
break;
|
|
default:
|
|
break;
|
|
@@ -2932,10 +2930,21 @@ __ieee80211_rx_h_amsdu(struct ieee80211_
|
|
data_offset, true))
|
|
return RX_DROP_UNUSABLE;
|
|
|
|
+ if (rx->sta && rx->sta->amsdu_mesh_control < 0) {
|
|
+ bool valid_std = ieee80211_is_valid_amsdu(skb, true);
|
|
+ bool valid_nonstd = ieee80211_is_valid_amsdu(skb, false);
|
|
+
|
|
+ if (valid_std && !valid_nonstd)
|
|
+ rx->sta->amsdu_mesh_control = 1;
|
|
+ else if (valid_nonstd && !valid_std)
|
|
+ rx->sta->amsdu_mesh_control = 0;
|
|
+ }
|
|
+
|
|
ieee80211_amsdu_to_8023s(skb, &frame_list, dev->dev_addr,
|
|
rx->sdata->vif.type,
|
|
rx->local->hw.extra_tx_headroom,
|
|
- check_da, check_sa, mesh);
|
|
+ check_da, check_sa,
|
|
+ rx->sta->amsdu_mesh_control);
|
|
|
|
while (!skb_queue_empty(&frame_list)) {
|
|
rx->skb = __skb_dequeue(&frame_list);
|
|
--- a/net/mac80211/sta_info.c
|
|
+++ b/net/mac80211/sta_info.c
|
|
@@ -591,6 +591,9 @@ __sta_info_alloc(struct ieee80211_sub_if
|
|
|
|
sta->sta_state = IEEE80211_STA_NONE;
|
|
|
|
+ if (sdata->vif.type == NL80211_IFTYPE_MESH_POINT)
|
|
+ sta->amsdu_mesh_control = -1;
|
|
+
|
|
/* Mark TID as unreserved */
|
|
sta->reserved_tid = IEEE80211_TID_UNRESERVED;
|
|
|
|
--- a/net/mac80211/sta_info.h
|
|
+++ b/net/mac80211/sta_info.h
|
|
@@ -702,6 +702,7 @@ struct sta_info {
|
|
struct codel_params cparams;
|
|
|
|
u8 reserved_tid;
|
|
+ s8 amsdu_mesh_control;
|
|
|
|
struct cfg80211_chan_def tdls_chandef;
|
|
|
|
--- a/net/wireless/util.c
|
|
+++ b/net/wireless/util.c
|
|
@@ -776,6 +776,38 @@ __ieee80211_amsdu_copy(struct sk_buff *s
|
|
return frame;
|
|
}
|
|
|
|
+bool ieee80211_is_valid_amsdu(struct sk_buff *skb, bool mesh_hdr)
|
|
+{
|
|
+ int offset = 0, remaining, subframe_len, padding;
|
|
+
|
|
+ for (offset = 0; offset < skb->len; offset += subframe_len + padding) {
|
|
+ struct {
|
|
+ __be16 len;
|
|
+ u8 mesh_flags;
|
|
+ } hdr;
|
|
+ u16 len;
|
|
+
|
|
+ if (skb_copy_bits(skb, offset + 2 * ETH_ALEN, &hdr, sizeof(hdr)) < 0)
|
|
+ return false;
|
|
+
|
|
+ if (mesh_hdr)
|
|
+ len = le16_to_cpu(*(__le16 *)&hdr.len) +
|
|
+ __ieee80211_get_mesh_hdrlen(hdr.mesh_flags);
|
|
+ else
|
|
+ len = ntohs(hdr.len);
|
|
+
|
|
+ subframe_len = sizeof(struct ethhdr) + len;
|
|
+ padding = (4 - subframe_len) & 0x3;
|
|
+ remaining = skb->len - offset;
|
|
+
|
|
+ if (subframe_len > remaining)
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+}
|
|
+EXPORT_SYMBOL(ieee80211_is_valid_amsdu);
|
|
+
|
|
void ieee80211_amsdu_to_8023s(struct sk_buff *skb, struct sk_buff_head *list,
|
|
const u8 *addr, enum nl80211_iftype iftype,
|
|
const unsigned int extra_headroom,
|