mt76: mt7996 sends 802.11s mesh peering frames with zero addr3, breaking interop with ath11k (patch + validation)

openwrt at optimcloud.com openwrt at optimcloud.com
Fri Sep 11 00:03:27 PDT 2026


Reporting an 802.11s interoperability bug in mt76/mt7996, with a patch that
is validated on hardware. Pull request against openwrt/mt76 is open here:

  https://github.com/openwrt/mt76/pull/1132

Details below.

---

mt7996 transmits 802.11s Mesh Peering Management frames with
Address 3 = 00:00:00:00:00:00. This breaks mesh interoperability with
peers that filter received management frames on BSSID; in particular a
BPI-R4 (mt7996) cannot form a peering with ath11k (QCN9074) nodes.

Root cause
----------
mac80211 deliberately leaves bss_conf.bssid as zero_addr for mesh
interfaces, because an MBSS has no BSSID in the infrastructure sense:

  net/mac80211/mesh.c:  sdata->vif.bss_conf.bssid = zero_addr;

mt7996_mcu_bss_basic_tlv() (drivers/net/wireless/mediatek/mt76/mt7996/mcu.c)
copies that value into the hardware BSS entry:

  memcpy(bss->bssid, link_conf->bssid, ETH_ALEN);

and the mt7996 hardware stamps the programmed BSS BSSID into Address 3 of
management frames it transmits. Every Mesh Peering Management frame
(Self-Protected Action, category 15) therefore leaves the radio with
addr3 zeroed.

That value is wrong for the wire. mac80211's own kernel MPM puts the
transmitter's MAC in addr3:

  net/mac80211/mesh_plink.c:  memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);

and hostapd/wpa_supplicant does the same for user-space MPM
(wpa_driver_nl80211_send_action() is called with bssid = wpa_s->own_addr).
bss_conf.bssid == zero is a "no associated BSS" marker, not a value to
transmit.

Observed behaviour
------------------
Measured with a laptop in monitor mode as an independent observer, plus
instrumentation on both endpoints.

Frames as handed to the driver (pr_info added at mt7996_tx() entry),
and the same frames on air in the same 25 s window:

  mt7996_tx() entry : 1035 frames, addr3 = 02:0c:43:26:60:11 (own MAC)
  on air            :  915 frames, addr3 = 00:00:00:00:00:00

so the header is correct when mac80211 hands it over and zeroed by the
time it is transmitted. mld=0 on every sample, so the MLD address
rewrite in mt7996_tx() is not involved (it is gated on
ieee80211_vif_is_mld() and would also have rewritten addr2, which is
intact).

Effect on the peer (ath11k QCN9074, same 35 s window):

  BPI Mesh Peering Open   -> peer : 217 on air, 0 delivered to host
  BPI Peering Confirm     -> peer : 194 on air, 0 delivered to host
  BPI Authentication (SAE)-> peer : 400 on air, 108 delivered to host

The Authentication frames carry a correct addr3 and are delivered, so SAE
completes; the peering frames are ACKed at the MAC layer and then
silently discarded, so MPM never completes. The peer eventually closes
with reason 56 (MESH_MAX_RETRIES) reporting our link ID as 0, i.e. it
never recorded the peering at all.

Ruled out by measurement before finding this: wpad/hostapd version
differences (sources byte-identical), RF/signal, sae_pwe, rsn_overriding,
the EHT elements mt7996 adds (suppressed and verified absent on air,
still failed), PHY rate (all frames legacy OFDM 6 Mbit/s), the Protected
bit (clear on both sides), and the QCA firmware build (swapped to a QSDK
build, no change).

Fix
---
Use the interface's own address as the BSS BSSID for mesh interfaces.

--- a/mt7996/mcu.c
+++ b/mt7996/mcu.c
@@ -1260,7 +1260,15 @@
                return 0;
        }

-       memcpy(bss->bssid, link_conf->bssid, ETH_ALEN);
+       /* mac80211 sets bss_conf.bssid = zero_addr for mesh interfaces, and
+        * the mt7996 HW stamps the programmed BSS BSSID into addr3 of TXed
+        * mgmt frames. That would send mesh peering frames with a zero addr3,
+        * which peers filtering on BSSID (e.g. ath11k) silently drop.
+        */
+       if (vif->type == NL80211_IFTYPE_MESH_POINT)
+               memcpy(bss->bssid, link_conf->addr, ETH_ALEN);
+       else
+               memcpy(bss->bssid, link_conf->bssid, ETH_ALEN);
        bss->bcn_interval = cpu_to_le16(link_conf->beacon_int);
        bss->dtim_period = link_conf->dtim_period;
        bss->phymode = mt76_connac_get_phy_mode(phy, vif,

Validation
----------
With the patch applied, on the same hardware and configuration:

  - on air: 211 mt7996 management frames now carry
    addr3 = 02:0c:43:26:60:11 (previously all zero)
  - the BPI-R4 reaches plink ESTAB with all three ath11k nodes
  - all four nodes report estab=3 and exchange batman-adv originators

Environment
-----------
  mt76           : openwrt/mt76 be5ce7910521492d4a2e4ce7ee3843680a46c047
                   (PKG_SOURCE_DATE 2026-09-01)
  BPI-R4         : OpenWrt r36157-f3614686ab, kernel 6.18.44, mt7996e
  peer           : IPQ5018 + QCN9074, ath11k, kernel 6.12.77,
                   mac80211 backports-6.18.7
  mesh           : 802.11s, SAE, 5 GHz ch36 HE80, batman-adv on top

Note: the same pattern appears in the shared helper used by the other
drivers, so they are likely affected too. I only have mt7996 hardware, so
I have not verified these:

  mt76_connac_mcu.c:2945  memcpy(bss->bssid, vif->bss_conf.bssid, ETH_ALEN);
                          (mt76_connac_mcu_bss_basic_tlv(), used by
                           mt7615/mt7915/mt7921)
  mt7925/mcu.c:2736       memcpy(basic_req->bssid, link_conf->bssid, ETH_ALEN);

In both the mesh case falls through the iftype switch without special
handling, so bss_conf.bssid (zero for mesh) is what gets programmed.



More information about the openwrt-devel mailing list