[openwrt/openwrt] mac80211: add fixes for receiving A-MSDU packets on mesh interfaces

LEDE Commits lede-commits at lists.infradead.org
Mon Feb 13 03:05:35 PST 2023


nbd pushed a commit to openwrt/openwrt.git, branch master:
https://git.openwrt.org/ec33a6ca2cd16e92d2a544f38704405c043c822d

commit ec33a6ca2cd16e92d2a544f38704405c043c822d
Author: Felix Fietkau <nbd at nbd.name>
AuthorDate: Sat Dec 10 14:55:33 2022 +0100

    mac80211: add fixes for receiving A-MSDU packets on mesh interfaces
    
    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 at nbd.name>
---
 ...11-fix-and-simplify-unencrypted-drop-chec.patch |  87 +++
 ...11-move-A-MSDU-check-in-ieee80211_data_to.patch |  25 +
 ...11-factor-out-bridge-tunnel-RFC1042-heade.patch |  76 +++
 ...11-remove-mesh-forwarding-congestion-chec.patch |  54 ++
 ...11-fix-receiving-A-MSDU-frames-on-mesh-in.patch | 753 +++++++++++++++++++++
 ...11-add-a-workaround-for-receiving-non-sta.patch | 145 ++++
 .../500-mac80211_configure_antenna_gain.patch      |   2 +-
 ...80211-mask-nested-A-MSDU-support-for-mesh.patch |  33 -
 8 files changed, 1141 insertions(+), 34 deletions(-)

diff --git a/package/kernel/mac80211/patches/subsys/311-wifi-mac80211-fix-and-simplify-unencrypted-drop-chec.patch b/package/kernel/mac80211/patches/subsys/311-wifi-mac80211-fix-and-simplify-unencrypted-drop-chec.patch
new file mode 100644
index 0000000000..804b02eb30
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/311-wifi-mac80211-fix-and-simplify-unencrypted-drop-chec.patch
@@ -0,0 +1,87 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Thu, 1 Dec 2022 14:57:30 +0100
+Subject: [PATCH] wifi: mac80211: fix and simplify unencrypted drop check for
+ mesh
+
+ieee80211_drop_unencrypted is called from ieee80211_rx_h_mesh_fwding and
+ieee80211_frame_allowed.
+
+Since ieee80211_rx_h_mesh_fwding can forward packets for other mesh nodes
+and is called earlier, it needs to check the decryptions status and if the
+packet is using the control protocol on its own, instead of deferring to
+the later call from ieee80211_frame_allowed.
+
+Because of that, ieee80211_drop_unencrypted has a mesh specific check
+that skips over the mesh header in order to check the payload protocol.
+This code is invalid when called from ieee80211_frame_allowed, since that
+happens after the 802.11->802.3 conversion.
+
+Fix this by moving the mesh specific check directly into
+ieee80211_rx_h_mesh_fwding.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+Link: https://lore.kernel.org/r/20221201135730.19723-1-nbd@nbd.name
+Signed-off-by: Johannes Berg <johannes.berg at intel.com>
+---
+
+--- a/net/mac80211/rx.c
++++ b/net/mac80211/rx.c
+@@ -2403,7 +2403,6 @@ static int ieee80211_802_1x_port_control
+ 
+ static int ieee80211_drop_unencrypted(struct ieee80211_rx_data *rx, __le16 fc)
+ {
+-	struct ieee80211_hdr *hdr = (void *)rx->skb->data;
+ 	struct sk_buff *skb = rx->skb;
+ 	struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
+ 
+@@ -2414,31 +2413,6 @@ static int ieee80211_drop_unencrypted(st
+ 	if (status->flag & RX_FLAG_DECRYPTED)
+ 		return 0;
+ 
+-	/* check mesh EAPOL frames first */
+-	if (unlikely(rx->sta && ieee80211_vif_is_mesh(&rx->sdata->vif) &&
+-		     ieee80211_is_data(fc))) {
+-		struct ieee80211s_hdr *mesh_hdr;
+-		u16 hdr_len = ieee80211_hdrlen(fc);
+-		u16 ethertype_offset;
+-		__be16 ethertype;
+-
+-		if (!ether_addr_equal(hdr->addr1, rx->sdata->vif.addr))
+-			goto drop_check;
+-
+-		/* make sure fixed part of mesh header is there, also checks skb len */
+-		if (!pskb_may_pull(rx->skb, hdr_len + 6))
+-			goto drop_check;
+-
+-		mesh_hdr = (struct ieee80211s_hdr *)(skb->data + hdr_len);
+-		ethertype_offset = hdr_len + ieee80211_get_mesh_hdrlen(mesh_hdr) +
+-				   sizeof(rfc1042_header);
+-
+-		if (skb_copy_bits(rx->skb, ethertype_offset, &ethertype, 2) == 0 &&
+-		    ethertype == rx->sdata->control_port_protocol)
+-			return 0;
+-	}
+-
+-drop_check:
+ 	/* Drop unencrypted frames if key is set. */
+ 	if (unlikely(!ieee80211_has_protected(fc) &&
+ 		     !ieee80211_is_any_nullfunc(fc) &&
+@@ -2892,8 +2866,16 @@ ieee80211_rx_h_mesh_fwding(struct ieee80
+ 	hdr = (struct ieee80211_hdr *) skb->data;
+ 	mesh_hdr = (struct ieee80211s_hdr *) (skb->data + hdrlen);
+ 
+-	if (ieee80211_drop_unencrypted(rx, hdr->frame_control))
+-		return RX_DROP_MONITOR;
++	if (ieee80211_drop_unencrypted(rx, hdr->frame_control)) {
++		int offset = hdrlen + ieee80211_get_mesh_hdrlen(mesh_hdr) +
++			     sizeof(rfc1042_header);
++		__be16 ethertype;
++
++		if (!ether_addr_equal(hdr->addr1, rx->sdata->vif.addr) ||
++		    skb_copy_bits(rx->skb, offset, &ethertype, 2) != 0 ||
++		    ethertype != rx->sdata->control_port_protocol)
++			return RX_DROP_MONITOR;
++	}
+ 
+ 	/* frame is in RMC, don't forward */
+ 	if (ieee80211_is_data(hdr->frame_control) &&
diff --git a/package/kernel/mac80211/patches/subsys/312-wifi-cfg80211-move-A-MSDU-check-in-ieee80211_data_to.patch b/package/kernel/mac80211/patches/subsys/312-wifi-cfg80211-move-A-MSDU-check-in-ieee80211_data_to.patch
new file mode 100644
index 0000000000..f668905cca
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/312-wifi-cfg80211-move-A-MSDU-check-in-ieee80211_data_to.patch
@@ -0,0 +1,25 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Fri, 2 Dec 2022 13:53:11 +0100
+Subject: [PATCH] wifi: cfg80211: move A-MSDU check in
+ ieee80211_data_to_8023_exthdr
+
+When parsing the outer A-MSDU header, don't check for inner bridge tunnel
+or RFC1042 headers. This is handled by ieee80211_amsdu_to_8023s already.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/wireless/util.c
++++ b/net/wireless/util.c
+@@ -631,8 +631,9 @@ int ieee80211_data_to_8023_exthdr(struct
+ 		break;
+ 	}
+ 
+-	if (likely(skb_copy_bits(skb, hdrlen, &payload, sizeof(payload)) == 0 &&
+-	           ((!is_amsdu && ether_addr_equal(payload.hdr, rfc1042_header) &&
++	if (likely(!is_amsdu &&
++		   skb_copy_bits(skb, hdrlen, &payload, sizeof(payload)) == 0 &&
++	           ((ether_addr_equal(payload.hdr, rfc1042_header) &&
+ 		     payload.proto != htons(ETH_P_AARP) &&
+ 		     payload.proto != htons(ETH_P_IPX)) ||
+ 		    ether_addr_equal(payload.hdr, bridge_tunnel_header)))) {
diff --git a/package/kernel/mac80211/patches/subsys/313-wifi-cfg80211-factor-out-bridge-tunnel-RFC1042-heade.patch b/package/kernel/mac80211/patches/subsys/313-wifi-cfg80211-factor-out-bridge-tunnel-RFC1042-heade.patch
new file mode 100644
index 0000000000..8641057869
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/313-wifi-cfg80211-factor-out-bridge-tunnel-RFC1042-heade.patch
@@ -0,0 +1,76 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Fri, 2 Dec 2022 13:54:15 +0100
+Subject: [PATCH] wifi: cfg80211: factor out bridge tunnel / RFC1042 header
+ check
+
+The same check is done in multiple places, unify it.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/wireless/util.c
++++ b/net/wireless/util.c
+@@ -542,6 +542,21 @@ unsigned int ieee80211_get_mesh_hdrlen(s
+ }
+ EXPORT_SYMBOL(ieee80211_get_mesh_hdrlen);
+ 
++static bool ieee80211_get_8023_tunnel_proto(const void *hdr, __be16 *proto)
++{
++	const __be16 *hdr_proto = hdr + ETH_ALEN;
++
++	if (!(ether_addr_equal(hdr, rfc1042_header) &&
++	      *hdr_proto != htons(ETH_P_AARP) &&
++	      *hdr_proto != htons(ETH_P_IPX)) &&
++	    !ether_addr_equal(hdr, bridge_tunnel_header))
++		return false;
++
++	*proto = *hdr_proto;
++
++	return true;
++}
++
+ int ieee80211_data_to_8023_exthdr(struct sk_buff *skb, struct ethhdr *ehdr,
+ 				  const u8 *addr, enum nl80211_iftype iftype,
+ 				  u8 data_offset, bool is_amsdu)
+@@ -633,14 +648,9 @@ int ieee80211_data_to_8023_exthdr(struct
+ 
+ 	if (likely(!is_amsdu &&
+ 		   skb_copy_bits(skb, hdrlen, &payload, sizeof(payload)) == 0 &&
+-	           ((ether_addr_equal(payload.hdr, rfc1042_header) &&
+-		     payload.proto != htons(ETH_P_AARP) &&
+-		     payload.proto != htons(ETH_P_IPX)) ||
+-		    ether_addr_equal(payload.hdr, bridge_tunnel_header)))) {
+-		/* remove RFC1042 or Bridge-Tunnel encapsulation and
+-		 * replace EtherType */
++		   ieee80211_get_8023_tunnel_proto(&payload, &tmp.h_proto))) {
++		/* remove RFC1042 or Bridge-Tunnel encapsulation */
+ 		hdrlen += ETH_ALEN + 2;
+-		tmp.h_proto = payload.proto;
+ 		skb_postpull_rcsum(skb, &payload, ETH_ALEN + 2);
+ 	} else {
+ 		tmp.h_proto = htons(skb->len - hdrlen);
+@@ -756,8 +766,6 @@ void ieee80211_amsdu_to_8023s(struct sk_
+ {
+ 	unsigned int hlen = ALIGN(extra_headroom, 4);
+ 	struct sk_buff *frame = NULL;
+-	u16 ethertype;
+-	u8 *payload;
+ 	int offset = 0, remaining;
+ 	struct ethhdr eth;
+ 	bool reuse_frag = skb->head_frag && !skb_has_frag_list(skb);
+@@ -811,14 +819,8 @@ void ieee80211_amsdu_to_8023s(struct sk_
+ 		frame->dev = skb->dev;
+ 		frame->priority = skb->priority;
+ 
+-		payload = frame->data;
+-		ethertype = (payload[6] << 8) | payload[7];
+-		if (likely((ether_addr_equal(payload, rfc1042_header) &&
+-			    ethertype != ETH_P_AARP && ethertype != ETH_P_IPX) ||
+-			   ether_addr_equal(payload, bridge_tunnel_header))) {
+-			eth.h_proto = htons(ethertype);
++		if (likely(ieee80211_get_8023_tunnel_proto(frame->data, &eth.h_proto)))
+ 			skb_pull(frame, ETH_ALEN + 2);
+-		}
+ 
+ 		memcpy(skb_push(frame, sizeof(eth)), &eth, sizeof(eth));
+ 		__skb_queue_tail(list, frame);
diff --git a/package/kernel/mac80211/patches/subsys/314-wifi-mac80211-remove-mesh-forwarding-congestion-chec.patch b/package/kernel/mac80211/patches/subsys/314-wifi-mac80211-remove-mesh-forwarding-congestion-chec.patch
new file mode 100644
index 0000000000..515176f0de
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/314-wifi-mac80211-remove-mesh-forwarding-congestion-chec.patch
@@ -0,0 +1,54 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Fri, 2 Dec 2022 17:01:46 +0100
+Subject: [PATCH] wifi: mac80211: remove mesh forwarding congestion check
+
+Now that all drivers use iTXQ, it does not make sense to check to drop
+tx forwarding packets when the driver has stopped the queues.
+fq_codel will take care of dropping packets when the queues fill up
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/mac80211/debugfs_netdev.c
++++ b/net/mac80211/debugfs_netdev.c
+@@ -603,8 +603,6 @@ IEEE80211_IF_FILE(fwded_mcast, u.mesh.ms
+ IEEE80211_IF_FILE(fwded_unicast, u.mesh.mshstats.fwded_unicast, DEC);
+ IEEE80211_IF_FILE(fwded_frames, u.mesh.mshstats.fwded_frames, DEC);
+ IEEE80211_IF_FILE(dropped_frames_ttl, u.mesh.mshstats.dropped_frames_ttl, DEC);
+-IEEE80211_IF_FILE(dropped_frames_congestion,
+-		  u.mesh.mshstats.dropped_frames_congestion, DEC);
+ IEEE80211_IF_FILE(dropped_frames_no_route,
+ 		  u.mesh.mshstats.dropped_frames_no_route, DEC);
+ 
+@@ -740,7 +738,6 @@ static void add_mesh_stats(struct ieee80
+ 	MESHSTATS_ADD(fwded_frames);
+ 	MESHSTATS_ADD(dropped_frames_ttl);
+ 	MESHSTATS_ADD(dropped_frames_no_route);
+-	MESHSTATS_ADD(dropped_frames_congestion);
+ #undef MESHSTATS_ADD
+ }
+ 
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -329,7 +329,6 @@ struct mesh_stats {
+ 	__u32 fwded_frames;		/* Mesh total forwarded frames */
+ 	__u32 dropped_frames_ttl;	/* Not transmitted since mesh_ttl == 0*/
+ 	__u32 dropped_frames_no_route;	/* Not transmitted, no route found */
+-	__u32 dropped_frames_congestion;/* Not forwarded due to congestion */
+ };
+ 
+ #define PREQ_Q_F_START		0x1
+--- a/net/mac80211/rx.c
++++ b/net/mac80211/rx.c
+@@ -2926,11 +2926,6 @@ ieee80211_rx_h_mesh_fwding(struct ieee80
+ 		return RX_CONTINUE;
+ 
+ 	ac = ieee802_1d_to_ac[skb->priority];
+-	q = sdata->vif.hw_queue[ac];
+-	if (ieee80211_queue_stopped(&local->hw, q)) {
+-		IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, dropped_frames_congestion);
+-		return RX_DROP_MONITOR;
+-	}
+ 	skb_set_queue_mapping(skb, ac);
+ 
+ 	if (!--mesh_hdr->ttl) {
diff --git a/package/kernel/mac80211/patches/subsys/315-wifi-mac80211-fix-receiving-A-MSDU-frames-on-mesh-in.patch b/package/kernel/mac80211/patches/subsys/315-wifi-mac80211-fix-receiving-A-MSDU-frames-on-mesh-in.patch
new file mode 100644
index 0000000000..6aec9bc85f
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/315-wifi-mac80211-fix-receiving-A-MSDU-frames-on-mesh-in.patch
@@ -0,0 +1,753 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Tue, 6 Dec 2022 11:15:02 +0100
+Subject: [PATCH] wifi: mac80211: fix receiving A-MSDU frames on mesh
+ interfaces
+
+The current mac80211 mesh A-MSDU receive path fails to parse A-MSDU packets
+on mesh interfaces, because it assumes that the Mesh Control field is always
+directly after the 802.11 header.
+802.11-2020 9.3.2.2.2 Figure 9-70 shows that the Mesh Control field is
+actually part of the A-MSDU subframe header.
+This makes more sense, since it allows packets for multiple different
+destinations to be included in the same A-MSDU, as long as RA and TID are
+still the same.
+Another issue is the fact that the A-MSDU subframe length field was apparently
+accidentally defined as little-endian in the standard.
+
+In order to fix this, the mesh forwarding path needs happen at a different
+point in the receive path.
+
+ieee80211_data_to_8023_exthdr is changed to ignore the mesh control field
+and leave it in after the ethernet header. This also affects the source/dest
+MAC address fields, which now in the case of mesh point to the mesh SA/DA.
+
+ieee80211_amsdu_to_8023s is changed to deal with the endian difference and
+to add the Mesh Control length to the subframe length, since it's not covered
+by the MSDU length field.
+
+With these changes, the mac80211 will get the same packet structure for
+converted regular data packets and unpacked A-MSDU subframes.
+
+The mesh forwarding checks are now only performed after the A-MSDU decap.
+For locally received packets, the Mesh Control header is stripped away.
+For forwarded packets, a new 802.11 header gets added.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c
++++ b/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c
+@@ -33,7 +33,7 @@ static int mwifiex_11n_dispatch_amsdu_pk
+ 		skb_trim(skb, le16_to_cpu(local_rx_pd->rx_pkt_length));
+ 
+ 		ieee80211_amsdu_to_8023s(skb, &list, priv->curr_addr,
+-					 priv->wdev.iftype, 0, NULL, NULL);
++					 priv->wdev.iftype, 0, NULL, NULL, false);
+ 
+ 		while (!skb_queue_empty(&list)) {
+ 			struct rx_packet_hdr *rx_hdr;
+--- a/include/net/cfg80211.h
++++ b/include/net/cfg80211.h
+@@ -6208,11 +6208,36 @@ static inline int ieee80211_data_to_8023
+  * @extra_headroom: The hardware extra headroom for SKBs in the @list.
+  * @check_da: DA to check in the inner ethernet header, or NULL
+  * @check_sa: SA to check in the inner ethernet header, or NULL
++ * @mesh_control: A-MSDU subframe header includes the mesh control field
+  */
+ 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,
+-			      const u8 *check_da, const u8 *check_sa);
++			      const u8 *check_da, const u8 *check_sa,
++			      bool mesh_control);
++
++/**
++ * ieee80211_get_8023_tunnel_proto - get RFC1042 or bridge tunnel encap protocol
++ *
++ * Check for RFC1042 or bridge tunnel header and fetch the encapsulated
++ * protocol.
++ *
++ * @hdr: pointer to the MSDU payload
++ * @proto: destination pointer to store the protocol
++ * Return: true if encapsulation was found
++ */
++bool ieee80211_get_8023_tunnel_proto(const void *hdr, __be16 *proto);
++
++/**
++ * ieee80211_strip_8023_mesh_hdr - strip mesh header from converted 802.3 frames
++ *
++ * Strip the mesh header, which was left in by ieee80211_data_to_8023 as part
++ * of the MSDU data. Also move any source/destination addresses from the mesh
++ * header to the ethernet header (if present).
++ *
++ * @skb: The 802.3 frame with embedded mesh header
++ */
++int ieee80211_strip_8023_mesh_hdr(struct sk_buff *skb);
+ 
+ /**
+  * cfg80211_classify8021d - determine the 802.1p/1d tag for a data frame
+--- a/net/mac80211/rx.c
++++ b/net/mac80211/rx.c
+@@ -2720,6 +2720,174 @@ ieee80211_deliver_skb(struct ieee80211_r
+ 	}
+ }
+ 
++static ieee80211_rx_result
++ieee80211_rx_mesh_data(struct ieee80211_sub_if_data *sdata, struct sta_info *sta,
++		       struct sk_buff *skb)
++{
++#ifdef CPTCFG_MAC80211_MESH
++	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
++	struct ieee80211_local *local = sdata->local;
++	uint16_t fc = IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_DATA;
++	struct ieee80211_hdr hdr = {
++		.frame_control = cpu_to_le16(fc)
++	};
++	struct ieee80211_hdr *fwd_hdr;
++	struct ieee80211s_hdr *mesh_hdr;
++	struct ieee80211_tx_info *info;
++	struct sk_buff *fwd_skb;
++	struct ethhdr *eth;
++	bool multicast;
++	int tailroom = 0;
++	int hdrlen, mesh_hdrlen;
++	u8 *qos;
++
++	if (!ieee80211_vif_is_mesh(&sdata->vif))
++		return RX_CONTINUE;
++
++	if (!pskb_may_pull(skb, sizeof(*eth) + 6))
++		return RX_DROP_MONITOR;
++
++	mesh_hdr = (struct ieee80211s_hdr *)(skb->data + sizeof(*eth));
++	mesh_hdrlen = ieee80211_get_mesh_hdrlen(mesh_hdr);
++
++	if (!pskb_may_pull(skb, sizeof(*eth) + mesh_hdrlen))
++		return RX_DROP_MONITOR;
++
++	eth = (struct ethhdr *)skb->data;
++	multicast = is_multicast_ether_addr(eth->h_dest);
++
++	mesh_hdr = (struct ieee80211s_hdr *)(eth + 1);
++	if (!mesh_hdr->ttl)
++		return RX_DROP_MONITOR;
++
++	/* frame is in RMC, don't forward */
++	if (is_multicast_ether_addr(eth->h_dest) &&
++	    mesh_rmc_check(sdata, eth->h_source, mesh_hdr))
++		return RX_DROP_MONITOR;
++
++	/* Frame has reached destination.  Don't forward */
++	if (ether_addr_equal(sdata->vif.addr, eth->h_dest))
++		goto rx_accept;
++
++	if (!ifmsh->mshcfg.dot11MeshForwarding) {
++		if (is_multicast_ether_addr(eth->h_dest))
++			goto rx_accept;
++
++		return RX_DROP_MONITOR;
++	}
++
++	/* forward packet */
++	if (sdata->crypto_tx_tailroom_needed_cnt)
++		tailroom = IEEE80211_ENCRYPT_TAILROOM;
++
++	if (!--mesh_hdr->ttl) {
++		if (multicast)
++			goto rx_accept;
++
++		IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, dropped_frames_ttl);
++		return RX_DROP_MONITOR;
++	}
++
++	if (mesh_hdr->flags & MESH_FLAGS_AE) {
++		struct mesh_path *mppath;
++		char *proxied_addr;
++
++		if (multicast)
++			proxied_addr = mesh_hdr->eaddr1;
++		else if ((mesh_hdr->flags & MESH_FLAGS_AE) == MESH_FLAGS_AE_A5_A6)
++			/* has_a4 already checked in ieee80211_rx_mesh_check */
++			proxied_addr = mesh_hdr->eaddr2;
++		else
++			return RX_DROP_MONITOR;
++
++		rcu_read_lock();
++		mppath = mpp_path_lookup(sdata, proxied_addr);
++		if (!mppath) {
++			mpp_path_add(sdata, proxied_addr, eth->h_source);
++		} else {
++			spin_lock_bh(&mppath->state_lock);
++			if (!ether_addr_equal(mppath->mpp, eth->h_source))
++				memcpy(mppath->mpp, eth->h_source, ETH_ALEN);
++			mppath->exp_time = jiffies;
++			spin_unlock_bh(&mppath->state_lock);
++		}
++		rcu_read_unlock();
++	}
++
++	skb_set_queue_mapping(skb, ieee802_1d_to_ac[skb->priority]);
++
++	ieee80211_fill_mesh_addresses(&hdr, &hdr.frame_control,
++				      eth->h_dest, eth->h_source);
++	hdrlen = ieee80211_hdrlen(hdr.frame_control);
++	if (multicast) {
++		int extra_head = sizeof(struct ieee80211_hdr) - sizeof(*eth);
++
++		fwd_skb = skb_copy_expand(skb, local->tx_headroom + extra_head +
++					       IEEE80211_ENCRYPT_HEADROOM,
++					  tailroom, GFP_ATOMIC);
++		if (!fwd_skb)
++			goto rx_accept;
++	} else {
++		fwd_skb = skb;
++		skb = NULL;
++
++		if (skb_cow_head(fwd_skb, hdrlen - sizeof(struct ethhdr)))
++			return RX_DROP_UNUSABLE;
++	}
++
++	fwd_hdr = skb_push(fwd_skb, hdrlen - sizeof(struct ethhdr));
++	memcpy(fwd_hdr, &hdr, hdrlen - 2);
++	qos = ieee80211_get_qos_ctl(fwd_hdr);
++	qos[0] = qos[1] = 0;
++
++	skb_reset_mac_header(fwd_skb);
++	hdrlen += mesh_hdrlen;
++	if (ieee80211_get_8023_tunnel_proto(fwd_skb->data + hdrlen,
++					    &fwd_skb->protocol))
++		hdrlen += ETH_ALEN;
++	else
++		fwd_skb->protocol = htons(fwd_skb->len - hdrlen);
++	skb_set_network_header(fwd_skb, hdrlen);
++
++	info = IEEE80211_SKB_CB(fwd_skb);
++	memset(info, 0, sizeof(*info));
++	info->control.flags |= IEEE80211_TX_INTCFL_NEED_TXPROCESSING;
++	info->control.vif = &sdata->vif;
++	info->control.jiffies = jiffies;
++	if (multicast) {
++		IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_mcast);
++		memcpy(fwd_hdr->addr2, sdata->vif.addr, ETH_ALEN);
++		/* update power mode indication when forwarding */
++		ieee80211_mps_set_frame_flags(sdata, NULL, fwd_hdr);
++	} else if (!mesh_nexthop_lookup(sdata, fwd_skb)) {
++		/* mesh power mode flags updated in mesh_nexthop_lookup */
++		IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_unicast);
++	} else {
++		/* unable to resolve next hop */
++		if (sta)
++			mesh_path_error_tx(sdata, ifmsh->mshcfg.element_ttl,
++					   hdr.addr3, 0,
++					   WLAN_REASON_MESH_PATH_NOFORWARD,
++					   sta->sta.addr);
++		IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, dropped_frames_no_route);
++		kfree_skb(fwd_skb);
++		goto rx_accept;
++	}
++
++	IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_frames);
++	fwd_skb->dev = sdata->dev;
++	ieee80211_add_pending_skb(local, fwd_skb);
++
++rx_accept:
++	if (!skb)
++		return RX_QUEUED;
++
++	ieee80211_strip_8023_mesh_hdr(skb);
++#endif
++
++	return RX_CONTINUE;
++}
++
+ static ieee80211_rx_result debug_noinline
+ __ieee80211_rx_h_amsdu(struct ieee80211_rx_data *rx, u8 data_offset)
+ {
+@@ -2728,8 +2896,10 @@ __ieee80211_rx_h_amsdu(struct ieee80211_
+ 	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ 	__le16 fc = hdr->frame_control;
+ 	struct sk_buff_head frame_list;
++	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;
+@@ -2746,6 +2916,8 @@ __ieee80211_rx_h_amsdu(struct ieee80211_
+ 			break;
+ 		case NL80211_IFTYPE_MESH_POINT:
+ 			check_sa = NULL;
++			check_da = NULL;
++			mesh = true;
+ 			break;
+ 		default:
+ 			break;
+@@ -2763,17 +2935,29 @@ __ieee80211_rx_h_amsdu(struct ieee80211_
+ 	ieee80211_amsdu_to_8023s(skb, &frame_list, dev->dev_addr,
+ 				 rx->sdata->vif.type,
+ 				 rx->local->hw.extra_tx_headroom,
+-				 check_da, check_sa);
++				 check_da, check_sa, mesh);
+ 
+ 	while (!skb_queue_empty(&frame_list)) {
+ 		rx->skb = __skb_dequeue(&frame_list);
+ 
+-		if (!ieee80211_frame_allowed(rx, fc)) {
+-			dev_kfree_skb(rx->skb);
++		res = ieee80211_rx_mesh_data(rx->sdata, rx->sta, rx->skb);
++		switch (res) {
++		case RX_QUEUED:
+ 			continue;
++		case RX_CONTINUE:
++			break;
++		default:
++			goto free;
+ 		}
+ 
++		if (!ieee80211_frame_allowed(rx, fc))
++			goto free;
++
+ 		ieee80211_deliver_skb(rx);
++		continue;
++
++free:
++		dev_kfree_skb(rx->skb);
+ 	}
+ 
+ 	return RX_QUEUED;
+@@ -2806,6 +2990,8 @@ ieee80211_rx_h_amsdu(struct ieee80211_rx
+ 			if (!rx->sdata->u.mgd.use_4addr)
+ 				return RX_DROP_UNUSABLE;
+ 			break;
++		case NL80211_IFTYPE_MESH_POINT:
++			break;
+ 		default:
+ 			return RX_DROP_UNUSABLE;
+ 		}
+@@ -2834,155 +3020,6 @@ ieee80211_rx_h_amsdu(struct ieee80211_rx
+ 	return __ieee80211_rx_h_amsdu(rx, 0);
+ }
+ 
+-#ifdef CPTCFG_MAC80211_MESH
+-static ieee80211_rx_result
+-ieee80211_rx_h_mesh_fwding(struct ieee80211_rx_data *rx)
+-{
+-	struct ieee80211_hdr *fwd_hdr, *hdr;
+-	struct ieee80211_tx_info *info;
+-	struct ieee80211s_hdr *mesh_hdr;
+-	struct sk_buff *skb = rx->skb, *fwd_skb;
+-	struct ieee80211_local *local = rx->local;
+-	struct ieee80211_sub_if_data *sdata = rx->sdata;
+-	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
+-	u16 ac, q, hdrlen;
+-	int tailroom = 0;
+-
+-	hdr = (struct ieee80211_hdr *) skb->data;
+-	hdrlen = ieee80211_hdrlen(hdr->frame_control);
+-
+-	/* make sure fixed part of mesh header is there, also checks skb len */
+-	if (!pskb_may_pull(rx->skb, hdrlen + 6))
+-		return RX_DROP_MONITOR;
+-
+-	mesh_hdr = (struct ieee80211s_hdr *) (skb->data + hdrlen);
+-
+-	/* make sure full mesh header is there, also checks skb len */
+-	if (!pskb_may_pull(rx->skb,
+-			   hdrlen + ieee80211_get_mesh_hdrlen(mesh_hdr)))
+-		return RX_DROP_MONITOR;
+-
+-	/* reload pointers */
+-	hdr = (struct ieee80211_hdr *) skb->data;
+-	mesh_hdr = (struct ieee80211s_hdr *) (skb->data + hdrlen);
+-
+-	if (ieee80211_drop_unencrypted(rx, hdr->frame_control)) {
+-		int offset = hdrlen + ieee80211_get_mesh_hdrlen(mesh_hdr) +
+-			     sizeof(rfc1042_header);
+-		__be16 ethertype;
+-
+-		if (!ether_addr_equal(hdr->addr1, rx->sdata->vif.addr) ||
+-		    skb_copy_bits(rx->skb, offset, &ethertype, 2) != 0 ||
+-		    ethertype != rx->sdata->control_port_protocol)
+-			return RX_DROP_MONITOR;
+-	}
+-
+-	/* frame is in RMC, don't forward */
+-	if (ieee80211_is_data(hdr->frame_control) &&
+-	    is_multicast_ether_addr(hdr->addr1) &&
+-	    mesh_rmc_check(rx->sdata, hdr->addr3, mesh_hdr))
+-		return RX_DROP_MONITOR;
+-
+-	if (!ieee80211_is_data(hdr->frame_control))
+-		return RX_CONTINUE;
+-
+-	if (!mesh_hdr->ttl)
+-		return RX_DROP_MONITOR;
+-
+-	if (mesh_hdr->flags & MESH_FLAGS_AE) {
+-		struct mesh_path *mppath;
+-		char *proxied_addr;
+-		char *mpp_addr;
+-
+-		if (is_multicast_ether_addr(hdr->addr1)) {
+-			mpp_addr = hdr->addr3;
+-			proxied_addr = mesh_hdr->eaddr1;
+-		} else if ((mesh_hdr->flags & MESH_FLAGS_AE) ==
+-			    MESH_FLAGS_AE_A5_A6) {
+-			/* has_a4 already checked in ieee80211_rx_mesh_check */
+-			mpp_addr = hdr->addr4;
+-			proxied_addr = mesh_hdr->eaddr2;
+-		} else {
+-			return RX_DROP_MONITOR;
+-		}
+-
+-		rcu_read_lock();
+-		mppath = mpp_path_lookup(sdata, proxied_addr);
+-		if (!mppath) {
+-			mpp_path_add(sdata, proxied_addr, mpp_addr);
+-		} else {
+-			spin_lock_bh(&mppath->state_lock);
+-			if (!ether_addr_equal(mppath->mpp, mpp_addr))
+-				memcpy(mppath->mpp, mpp_addr, ETH_ALEN);
+-			mppath->exp_time = jiffies;
+-			spin_unlock_bh(&mppath->state_lock);
+-		}
+-		rcu_read_unlock();
+-	}
+-
+-	/* Frame has reached destination.  Don't forward */
+-	if (!is_multicast_ether_addr(hdr->addr1) &&
+-	    ether_addr_equal(sdata->vif.addr, hdr->addr3))
+-		return RX_CONTINUE;
+-
+-	ac = ieee802_1d_to_ac[skb->priority];
+-	skb_set_queue_mapping(skb, ac);
+-
+-	if (!--mesh_hdr->ttl) {
+-		if (!is_multicast_ether_addr(hdr->addr1))
+-			IEEE80211_IFSTA_MESH_CTR_INC(ifmsh,
+-						     dropped_frames_ttl);
+-		goto out;
+-	}
+-
+-	if (!ifmsh->mshcfg.dot11MeshForwarding)
+-		goto out;
+-
+-	if (sdata->crypto_tx_tailroom_needed_cnt)
+-		tailroom = IEEE80211_ENCRYPT_TAILROOM;
+-
+-	fwd_skb = skb_copy_expand(skb, local->tx_headroom +
+-				       IEEE80211_ENCRYPT_HEADROOM,
+-				  tailroom, GFP_ATOMIC);
+-	if (!fwd_skb)
+-		goto out;
+-
+-	fwd_skb->dev = sdata->dev;
+-	fwd_hdr =  (struct ieee80211_hdr *) fwd_skb->data;
+-	fwd_hdr->frame_control &= ~cpu_to_le16(IEEE80211_FCTL_RETRY);
+-	info = IEEE80211_SKB_CB(fwd_skb);
+-	memset(info, 0, sizeof(*info));
+-	info->control.flags |= IEEE80211_TX_INTCFL_NEED_TXPROCESSING;
+-	info->control.vif = &rx->sdata->vif;
+-	info->control.jiffies = jiffies;
+-	if (is_multicast_ether_addr(fwd_hdr->addr1)) {
+-		IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_mcast);
+-		memcpy(fwd_hdr->addr2, sdata->vif.addr, ETH_ALEN);
+-		/* update power mode indication when forwarding */
+-		ieee80211_mps_set_frame_flags(sdata, NULL, fwd_hdr);
+-	} else if (!mesh_nexthop_lookup(sdata, fwd_skb)) {
+-		/* mesh power mode flags updated in mesh_nexthop_lookup */
+-		IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_unicast);
+-	} else {
+-		/* unable to resolve next hop */
+-		mesh_path_error_tx(sdata, ifmsh->mshcfg.element_ttl,
+-				   fwd_hdr->addr3, 0,
+-				   WLAN_REASON_MESH_PATH_NOFORWARD,
+-				   fwd_hdr->addr2);
+-		IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, dropped_frames_no_route);
+-		kfree_skb(fwd_skb);
+-		return RX_DROP_MONITOR;
+-	}
+-
+-	IEEE80211_IFSTA_MESH_CTR_INC(ifmsh, fwded_frames);
+-	ieee80211_add_pending_skb(local, fwd_skb);
+- out:
+-	if (is_multicast_ether_addr(hdr->addr1))
+-		return RX_CONTINUE;
+-	return RX_DROP_MONITOR;
+-}
+-#endif
+-
+ static ieee80211_rx_result debug_noinline
+ ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
+ {
+@@ -2991,6 +3028,7 @@ ieee80211_rx_h_data(struct ieee80211_rx_
+ 	struct net_device *dev = sdata->dev;
+ 	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data;
+ 	__le16 fc = hdr->frame_control;
++	static ieee80211_rx_result res;
+ 	bool port_control;
+ 	int err;
+ 
+@@ -3017,6 +3055,10 @@ ieee80211_rx_h_data(struct ieee80211_rx_
+ 	if (unlikely(err))
+ 		return RX_DROP_UNUSABLE;
+ 
++	res = ieee80211_rx_mesh_data(rx->sdata, rx->sta, rx->skb);
++	if (res != RX_CONTINUE)
++		return res;
++
+ 	if (!ieee80211_frame_allowed(rx, fc))
+ 		return RX_DROP_MONITOR;
+ 
+@@ -3987,10 +4029,6 @@ static void ieee80211_rx_handlers(struct
+ 		CALL_RXH(ieee80211_rx_h_defragment);
+ 		CALL_RXH(ieee80211_rx_h_michael_mic_verify);
+ 		/* must be after MMIC verify so header is counted in MPDU mic */
+-#ifdef CPTCFG_MAC80211_MESH
+-		if (ieee80211_vif_is_mesh(&rx->sdata->vif))
+-			CALL_RXH(ieee80211_rx_h_mesh_fwding);
+-#endif
+ 		CALL_RXH(ieee80211_rx_h_amsdu);
+ 		CALL_RXH(ieee80211_rx_h_data);
+ 
+--- a/net/wireless/util.c
++++ b/net/wireless/util.c
+@@ -542,7 +542,7 @@ unsigned int ieee80211_get_mesh_hdrlen(s
+ }
+ EXPORT_SYMBOL(ieee80211_get_mesh_hdrlen);
+ 
+-static bool ieee80211_get_8023_tunnel_proto(const void *hdr, __be16 *proto)
++bool ieee80211_get_8023_tunnel_proto(const void *hdr, __be16 *proto)
+ {
+ 	const __be16 *hdr_proto = hdr + ETH_ALEN;
+ 
+@@ -556,6 +556,49 @@ static bool ieee80211_get_8023_tunnel_pr
+ 
+ 	return true;
+ }
++EXPORT_SYMBOL(ieee80211_get_8023_tunnel_proto);
++
++int ieee80211_strip_8023_mesh_hdr(struct sk_buff *skb)
++{
++	const void *mesh_addr;
++	struct {
++		struct ethhdr eth;
++		u8 flags;
++	} payload;
++	int hdrlen;
++	int ret;
++
++	ret = skb_copy_bits(skb, 0, &payload, sizeof(payload));
++	if (ret)
++		return ret;
++
++	hdrlen = sizeof(payload.eth) + __ieee80211_get_mesh_hdrlen(payload.flags);
++
++	if (likely(pskb_may_pull(skb, hdrlen + 8) &&
++		   ieee80211_get_8023_tunnel_proto(skb->data + hdrlen,
++						   &payload.eth.h_proto)))
++		hdrlen += ETH_ALEN + 2;
++	else if (!pskb_may_pull(skb, hdrlen))
++		return -EINVAL;
++
++	mesh_addr = skb->data + sizeof(payload.eth) + ETH_ALEN;
++	switch (payload.flags & MESH_FLAGS_AE) {
++	case MESH_FLAGS_AE_A4:
++		memcpy(&payload.eth.h_source, mesh_addr, ETH_ALEN);
++		break;
++	case MESH_FLAGS_AE_A5_A6:
++		memcpy(&payload.eth.h_dest, mesh_addr, 2 * ETH_ALEN);
++		break;
++	default:
++		break;
++	}
++
++	pskb_pull(skb, hdrlen - sizeof(payload.eth));
++	memcpy(skb->data, &payload.eth, sizeof(payload.eth));
++
++	return 0;
++}
++EXPORT_SYMBOL(ieee80211_strip_8023_mesh_hdr);
+ 
+ int ieee80211_data_to_8023_exthdr(struct sk_buff *skb, struct ethhdr *ehdr,
+ 				  const u8 *addr, enum nl80211_iftype iftype,
+@@ -568,7 +611,6 @@ int ieee80211_data_to_8023_exthdr(struct
+ 	} payload;
+ 	struct ethhdr tmp;
+ 	u16 hdrlen;
+-	u8 mesh_flags = 0;
+ 
+ 	if (unlikely(!ieee80211_is_data_present(hdr->frame_control)))
+ 		return -1;
+@@ -589,12 +631,6 @@ int ieee80211_data_to_8023_exthdr(struct
+ 	memcpy(tmp.h_dest, ieee80211_get_DA(hdr), ETH_ALEN);
+ 	memcpy(tmp.h_source, ieee80211_get_SA(hdr), ETH_ALEN);
+ 
+-	if (iftype == NL80211_IFTYPE_MESH_POINT &&
+-	    skb_copy_bits(skb, hdrlen, &mesh_flags, 1) < 0)
+-		return -1;
+-
+-	mesh_flags &= MESH_FLAGS_AE;
+-
+ 	switch (hdr->frame_control &
+ 		cpu_to_le16(IEEE80211_FCTL_TODS | IEEE80211_FCTL_FROMDS)) {
+ 	case cpu_to_le16(IEEE80211_FCTL_TODS):
+@@ -608,17 +644,6 @@ int ieee80211_data_to_8023_exthdr(struct
+ 			     iftype != NL80211_IFTYPE_AP_VLAN &&
+ 			     iftype != NL80211_IFTYPE_STATION))
+ 			return -1;
+-		if (iftype == NL80211_IFTYPE_MESH_POINT) {
+-			if (mesh_flags == MESH_FLAGS_AE_A4)
+-				return -1;
+-			if (mesh_flags == MESH_FLAGS_AE_A5_A6 &&
+-			    skb_copy_bits(skb, hdrlen +
+-					  offsetof(struct ieee80211s_hdr, eaddr1),
+-					  tmp.h_dest, 2 * ETH_ALEN) < 0)
+-				return -1;
+-
+-			hdrlen += __ieee80211_get_mesh_hdrlen(mesh_flags);
+-		}
+ 		break;
+ 	case cpu_to_le16(IEEE80211_FCTL_FROMDS):
+ 		if ((iftype != NL80211_IFTYPE_STATION &&
+@@ -627,16 +652,6 @@ int ieee80211_data_to_8023_exthdr(struct
+ 		    (is_multicast_ether_addr(tmp.h_dest) &&
+ 		     ether_addr_equal(tmp.h_source, addr)))
+ 			return -1;
+-		if (iftype == NL80211_IFTYPE_MESH_POINT) {
+-			if (mesh_flags == MESH_FLAGS_AE_A5_A6)
+-				return -1;
+-			if (mesh_flags == MESH_FLAGS_AE_A4 &&
+-			    skb_copy_bits(skb, hdrlen +
+-					  offsetof(struct ieee80211s_hdr, eaddr1),
+-					  tmp.h_source, ETH_ALEN) < 0)
+-				return -1;
+-			hdrlen += __ieee80211_get_mesh_hdrlen(mesh_flags);
+-		}
+ 		break;
+ 	case cpu_to_le16(0):
+ 		if (iftype != NL80211_IFTYPE_ADHOC &&
+@@ -646,7 +661,7 @@ int ieee80211_data_to_8023_exthdr(struct
+ 		break;
+ 	}
+ 
+-	if (likely(!is_amsdu &&
++	if (likely(!is_amsdu && iftype != NL80211_IFTYPE_MESH_POINT &&
+ 		   skb_copy_bits(skb, hdrlen, &payload, sizeof(payload)) == 0 &&
+ 		   ieee80211_get_8023_tunnel_proto(&payload, &tmp.h_proto))) {
+ 		/* remove RFC1042 or Bridge-Tunnel encapsulation */
+@@ -722,7 +737,8 @@ __ieee80211_amsdu_copy_frag(struct sk_bu
+ 
+ static struct sk_buff *
+ __ieee80211_amsdu_copy(struct sk_buff *skb, unsigned int hlen,
+-		       int offset, int len, bool reuse_frag)
++		       int offset, int len, bool reuse_frag,
++		       int min_len)
+ {
+ 	struct sk_buff *frame;
+ 	int cur_len = len;
+@@ -736,7 +752,7 @@ __ieee80211_amsdu_copy(struct sk_buff *s
+ 	 * in the stack later.
+ 	 */
+ 	if (reuse_frag)
+-		cur_len = min_t(int, len, 32);
++		cur_len = min_t(int, len, min_len);
+ 
+ 	/*
+ 	 * Allocate and reserve two bytes more for payload
+@@ -746,6 +762,7 @@ __ieee80211_amsdu_copy(struct sk_buff *s
+ 	if (!frame)
+ 		return NULL;
+ 
++	frame->priority = skb->priority;
+ 	skb_reserve(frame, hlen + sizeof(struct ethhdr) + 2);
+ 	skb_copy_bits(skb, offset, skb_put(frame, cur_len), cur_len);
+ 
+@@ -762,23 +779,37 @@ __ieee80211_amsdu_copy(struct sk_buff *s
+ 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,
+-			      const u8 *check_da, const u8 *check_sa)
++			      const u8 *check_da, const u8 *check_sa,
++			      bool mesh_control)
+ {
+ 	unsigned int hlen = ALIGN(extra_headroom, 4);
+ 	struct sk_buff *frame = NULL;
+ 	int offset = 0, remaining;
+-	struct ethhdr eth;
++	struct {
++		struct ethhdr eth;
++		uint8_t flags;
++	} hdr;
+ 	bool reuse_frag = skb->head_frag && !skb_has_frag_list(skb);
+ 	bool reuse_skb = false;
+ 	bool last = false;
++	int copy_len = sizeof(hdr.eth);
++
++	if (iftype == NL80211_IFTYPE_MESH_POINT)
++		copy_len = sizeof(hdr);
+ 
+ 	while (!last) {
+ 		unsigned int subframe_len;
+-		int len;
++		int len, mesh_len = 0;
+ 		u8 padding;
+ 
+-		skb_copy_bits(skb, offset, &eth, sizeof(eth));
+-		len = ntohs(eth.h_proto);
++		skb_copy_bits(skb, offset, &hdr, copy_len);
++		if (iftype == NL80211_IFTYPE_MESH_POINT)
++			mesh_len = __ieee80211_get_mesh_hdrlen(hdr.flags);
++		if (mesh_control)
++			len = le16_to_cpu(*(__le16 *)&hdr.eth.h_proto) + mesh_len;
++		else
++			len = ntohs(hdr.eth.h_proto);
++
+ 		subframe_len = sizeof(struct ethhdr) + len;
+ 		padding = (4 - subframe_len) & 0x3;
+ 
+@@ -787,16 +818,16 @@ void ieee80211_amsdu_to_8023s(struct sk_
+ 		if (subframe_len > remaining)
+ 			goto purge;
+ 		/* mitigate A-MSDU aggregation injection attacks */
+-		if (ether_addr_equal(eth.h_dest, rfc1042_header))
++		if (ether_addr_equal(hdr.eth.h_dest, rfc1042_header))
+ 			goto purge;
+ 
+ 		offset += sizeof(struct ethhdr);
+ 		last = remaining <= subframe_len + padding;
+ 
+ 		/* FIXME: should we really accept multicast DA? */
+-		if ((check_da && !is_multicast_ether_addr(eth.h_dest) &&
+-		     !ether_addr_equal(check_da, eth.h_dest)) ||
+-		    (check_sa && !ether_addr_equal(check_sa, eth.h_source))) {
++		if ((check_da && !is_multicast_ether_addr(hdr.eth.h_dest) &&
++		     !ether_addr_equal(check_da, hdr.eth.h_dest)) ||
++		    (check_sa && !ether_addr_equal(check_sa, hdr.eth.h_source))) {
+ 			offset += len + padding;
+ 			continue;
+ 		}
+@@ -808,7 +839,7 @@ void ieee80211_amsdu_to_8023s(struct sk_
+ 			reuse_skb = true;
+ 		} else {
+ 			frame = __ieee80211_amsdu_copy(skb, hlen, offset, len,
+-						       reuse_frag);
++						       reuse_frag, 32 + mesh_len);
+ 			if (!frame)
+ 				goto purge;
+ 
+@@ -819,10 +850,11 @@ void ieee80211_amsdu_to_8023s(struct sk_
+ 		frame->dev = skb->dev;
+ 		frame->priority = skb->priority;
+ 
+-		if (likely(ieee80211_get_8023_tunnel_proto(frame->data, &eth.h_proto)))
++		if (likely(iftype != NL80211_IFTYPE_MESH_POINT &&
++			   ieee80211_get_8023_tunnel_proto(frame->data, &hdr.eth.h_proto)))
+ 			skb_pull(frame, ETH_ALEN + 2);
+ 
+-		memcpy(skb_push(frame, sizeof(eth)), &eth, sizeof(eth));
++		memcpy(skb_push(frame, sizeof(hdr.eth)), &hdr.eth, sizeof(hdr.eth));
+ 		__skb_queue_tail(list, frame);
+ 	}
+ 
diff --git a/package/kernel/mac80211/patches/subsys/316-wifi-mac80211-add-a-workaround-for-receiving-non-sta.patch b/package/kernel/mac80211/patches/subsys/316-wifi-mac80211-add-a-workaround-for-receiving-non-sta.patch
new file mode 100644
index 0000000000..6dc98ae16a
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/316-wifi-mac80211-add-a-workaround-for-receiving-non-sta.patch
@@ -0,0 +1,145 @@
+From: Felix Fietkau <nbd at 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 at 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,
diff --git a/package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch b/package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch
index 13b374aae7..70d4e89c90 100644
--- a/package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch
+++ b/package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch
@@ -87,7 +87,7 @@
  	CFG80211_TESTMODE_DUMP(ieee80211_testmode_dump)
 --- a/net/mac80211/ieee80211_i.h
 +++ b/net/mac80211/ieee80211_i.h
-@@ -1521,6 +1521,7 @@ struct ieee80211_local {
+@@ -1520,6 +1520,7 @@ struct ieee80211_local {
  	int dynamic_ps_forced_timeout;
  
  	int user_power_level; /* in dBm, for all interfaces */
diff --git a/package/kernel/mac80211/patches/subsys/800-mac80211-mask-nested-A-MSDU-support-for-mesh.patch b/package/kernel/mac80211/patches/subsys/800-mac80211-mask-nested-A-MSDU-support-for-mesh.patch
deleted file mode 100644
index 56cc523022..0000000000
--- a/package/kernel/mac80211/patches/subsys/800-mac80211-mask-nested-A-MSDU-support-for-mesh.patch
+++ /dev/null
@@ -1,33 +0,0 @@
-From 313d8c18385f10957402b475f9b0c209ceab6c5a Mon Sep 17 00:00:00 2001
-From: David Bauer <mail at david-bauer.net>
-Date: Fri, 8 Oct 2021 00:25:19 +0200
-Subject: [PATCH] mac80211: mask nested A-MSDU support for mesh
-
-mac80211 incorrectly processes A-MSDUs contained in A-MPDU frames. This
-results in dropped packets and severely impacted throughput.
-
-As a workaround, don't indicate support for A-MSDUs contained in
-A-MPDUs. This improves throughput over mesh links by factor 10.
-
-Ref: https://github.com/openwrt/mt76/issues/450
-
-Signed-off-by: David Bauer <mail at david-bauer.net>
----
- net/mac80211/agg-rx.c | 4 +++-
- 1 file changed, 3 insertions(+), 1 deletion(-)
-
---- a/net/mac80211/agg-rx.c
-+++ b/net/mac80211/agg-rx.c
-@@ -254,7 +254,11 @@ static void ieee80211_send_addba_resp(st
- 	mgmt->u.action.u.addba_resp.action_code = WLAN_ACTION_ADDBA_RESP;
- 	mgmt->u.action.u.addba_resp.dialog_token = dialog_token;
- 
--	capab = u16_encode_bits(amsdu, IEEE80211_ADDBA_PARAM_AMSDU_MASK);
-+	capab = 0;
-+#ifdef CPTCFG_MAC80211_MESH
-+	if (!sta->mesh)
-+#endif
-+		capab = u16_encode_bits(amsdu, IEEE80211_ADDBA_PARAM_AMSDU_MASK);
- 	capab |= u16_encode_bits(policy, IEEE80211_ADDBA_PARAM_POLICY_MASK);
- 	capab |= u16_encode_bits(tid, IEEE80211_ADDBA_PARAM_TID_MASK);
- 	capab |= u16_encode_bits(buf_size, IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK);




More information about the lede-commits mailing list