6dc98ae16af127dc6192280d8f3dfcb1616e8240
[librecmc/librecmc.git] /
1 From: Felix Fietkau <nbd@nbd.name>
2 Date: Fri, 9 Dec 2022 21:15:04 +0100
3 Subject: [PATCH] wifi: mac80211: add a workaround for receiving
4  non-standard mesh A-MSDU
5
6 At least ath10k and ath11k supported hardware (maybe more) does not implement
7 mesh A-MSDU aggregation in a standard compliant way.
8 802.11-2020 9.3.2.2.2 declares that the Mesh Control field is part of the
9 A-MSDU header. As such, its length must not be included in the subframe
10 length field.
11 Hardware affected by this bug treats the mesh control field as part of the
12 MSDU data and sets the length accordingly.
13 In order to avoid packet loss, keep track of which stations are affected
14 by this and take it into account when converting A-MSDU to 802.3 + mesh control
15 packets.
16
17 Signed-off-by: Felix Fietkau <nbd@nbd.name>
18 ---
19
20 --- a/include/net/cfg80211.h
21 +++ b/include/net/cfg80211.h
22 @@ -6194,6 +6194,19 @@ static inline int ieee80211_data_to_8023
23  }
24  
25  /**
26 + * ieee80211_is_valid_amsdu - check if subframe lengths of an A-MSDU are valid
27 + *
28 + * This is used to detect non-standard A-MSDU frames, e.g. the ones generated
29 + * by ath10k and ath11k, where the subframe length includes the length of the
30 + * mesh control field.
31 + *
32 + * @skb: The input A-MSDU frame without any headers.
33 + * @mesh_hdr: use standard compliant mesh A-MSDU subframe header
34 + * Returns: true if subframe header lengths are valid for the @mesh_hdr mode
35 + */
36 +bool ieee80211_is_valid_amsdu(struct sk_buff *skb, bool mesh_hdr);
37 +
38 +/**
39   * ieee80211_amsdu_to_8023s - decode an IEEE 802.11n A-MSDU frame
40   *
41   * Decode an IEEE 802.11 A-MSDU and convert it to a list of 802.3 frames.
42 --- a/net/mac80211/rx.c
43 +++ b/net/mac80211/rx.c
44 @@ -2899,7 +2899,6 @@ __ieee80211_rx_h_amsdu(struct ieee80211_
45         static ieee80211_rx_result res;
46         struct ethhdr ethhdr;
47         const u8 *check_da = ethhdr.h_dest, *check_sa = ethhdr.h_source;
48 -       bool mesh = false;
49  
50         if (unlikely(ieee80211_has_a4(hdr->frame_control))) {
51                 check_da = NULL;
52 @@ -2917,7 +2916,6 @@ __ieee80211_rx_h_amsdu(struct ieee80211_
53                 case NL80211_IFTYPE_MESH_POINT:
54                         check_sa = NULL;
55                         check_da = NULL;
56 -                       mesh = true;
57                         break;
58                 default:
59                         break;
60 @@ -2932,10 +2930,21 @@ __ieee80211_rx_h_amsdu(struct ieee80211_
61                                           data_offset, true))
62                 return RX_DROP_UNUSABLE;
63  
64 +       if (rx->sta && rx->sta->amsdu_mesh_control < 0) {
65 +               bool valid_std = ieee80211_is_valid_amsdu(skb, true);
66 +               bool valid_nonstd = ieee80211_is_valid_amsdu(skb, false);
67 +
68 +               if (valid_std && !valid_nonstd)
69 +                       rx->sta->amsdu_mesh_control = 1;
70 +               else if (valid_nonstd && !valid_std)
71 +                       rx->sta->amsdu_mesh_control = 0;
72 +       }
73 +
74         ieee80211_amsdu_to_8023s(skb, &frame_list, dev->dev_addr,
75                                  rx->sdata->vif.type,
76                                  rx->local->hw.extra_tx_headroom,
77 -                                check_da, check_sa, mesh);
78 +                                check_da, check_sa,
79 +                                rx->sta->amsdu_mesh_control);
80  
81         while (!skb_queue_empty(&frame_list)) {
82                 rx->skb = __skb_dequeue(&frame_list);
83 --- a/net/mac80211/sta_info.c
84 +++ b/net/mac80211/sta_info.c
85 @@ -591,6 +591,9 @@ __sta_info_alloc(struct ieee80211_sub_if
86  
87         sta->sta_state = IEEE80211_STA_NONE;
88  
89 +       if (sdata->vif.type == NL80211_IFTYPE_MESH_POINT)
90 +               sta->amsdu_mesh_control = -1;
91 +
92         /* Mark TID as unreserved */
93         sta->reserved_tid = IEEE80211_TID_UNRESERVED;
94  
95 --- a/net/mac80211/sta_info.h
96 +++ b/net/mac80211/sta_info.h
97 @@ -702,6 +702,7 @@ struct sta_info {
98         struct codel_params cparams;
99  
100         u8 reserved_tid;
101 +       s8 amsdu_mesh_control;
102  
103         struct cfg80211_chan_def tdls_chandef;
104  
105 --- a/net/wireless/util.c
106 +++ b/net/wireless/util.c
107 @@ -776,6 +776,38 @@ __ieee80211_amsdu_copy(struct sk_buff *s
108         return frame;
109  }
110  
111 +bool ieee80211_is_valid_amsdu(struct sk_buff *skb, bool mesh_hdr)
112 +{
113 +       int offset = 0, remaining, subframe_len, padding;
114 +
115 +       for (offset = 0; offset < skb->len; offset += subframe_len + padding) {
116 +               struct {
117 +                   __be16 len;
118 +                   u8 mesh_flags;
119 +               } hdr;
120 +               u16 len;
121 +
122 +               if (skb_copy_bits(skb, offset + 2 * ETH_ALEN, &hdr, sizeof(hdr)) < 0)
123 +                       return false;
124 +
125 +               if (mesh_hdr)
126 +                       len = le16_to_cpu(*(__le16 *)&hdr.len) +
127 +                             __ieee80211_get_mesh_hdrlen(hdr.mesh_flags);
128 +               else
129 +                       len = ntohs(hdr.len);
130 +
131 +               subframe_len = sizeof(struct ethhdr) + len;
132 +               padding = (4 - subframe_len) & 0x3;
133 +               remaining = skb->len - offset;
134 +
135 +               if (subframe_len > remaining)
136 +                       return false;
137 +       }
138 +
139 +       return true;
140 +}
141 +EXPORT_SYMBOL(ieee80211_is_valid_amsdu);
142 +
143  void ieee80211_amsdu_to_8023s(struct sk_buff *skb, struct sk_buff_head *list,
144                               const u8 *addr, enum nl80211_iftype iftype,
145                               const unsigned int extra_headroom,