diff mbox series

ap: skip untagged STA VLAN binding for MLO stations

Message ID CAG0XsJHUbC3YUs17JRfyEz-=T9AktgS-uWNvRHbk_a9pebDwWw@mail.gmail.com
State Changes Requested
Headers show
Series ap: skip untagged STA VLAN binding for MLO stations | expand

Commit Message

hc86862997@gmail.com Sept. 26, 2026, 3:39 p.m. UTC
Hi,

hostapd's dynamic-VLAN-for-MLO support sends SET_STATION with
NL80211_ATTR_STA_VLAN (plus the MLD link id) for MLO stations even when
no VLAN is configured (vlan_id == 0), and the bind is deliberately not
skipped for vlan_id == old_vlanid == 0 (the "needs rebind after
sta_remove" path in ap_sta_bind_vlan_helper()).

On kernels without ML VLAN support (no CFG80211_STA_AP_ML_STA;
mac80211/cfg80211 reject VLAN operations for ML stations) the kernel
answers -EINVAL, and every rebind - triggered by association and by
each rekey/EAPOL reauth - fails association processing and tears the
MLO client down:

hostapd: nl80211: NL80211_ATTR_STA_VLAN (addr=<sta> ifname=ap-mld1
vlan_id=0) failed: -22 (Invalid argument)

Observed as a Wi-Fi 7 MLO client being kicked off an ap-mld interface
every ~60-90 seconds, deterministically, on MT7996 AP hardware (hostapd
831364bf, kernel 6.18.53 / backports 7.2).

These two guards skip the driver call for untagged MLO stations only;
dynamic VLAN keeps its previous behaviour until the kernel side gains
ML VLAN support:

1. src/ap/sta_info.c - skip the helper-level bind for MLO stations with
   vlan_id == 0 (covers the normal association path).
2. src/drivers/driver_nl80211.c - funnel guard in i802_set_sta_vlan():
   skip any untagged (vlan_id == 0) bind that carries a link id, which
   covers every caller by construction.

With this applied, zero STA_VLAN errors across many MLO associations
and rekeys, and the client stays connected; legacy clients on non-MLD
interfaces and dynamic-VLAN users behave exactly as before. (Also
reported to OpenWrt, which carries the patch:
https://github.com/openwrt/openwrt/issues/25415 )

---
 src/ap/sta_info.c          | 13 +++++++++++++
 src/drivers/driver_nl80211.c | 12 ++++++++++++
 2 files changed, 25 insertions(+)

Comments

Jouni Malinen Oct. 5, 2026, 1:40 p.m. UTC | #1
On Sat, Sep 26, 2026 at 03:39:44PM +0000, hc86862997@gmail.com wrote:
> hostapd's dynamic-VLAN-for-MLO support sends SET_STATION with
> NL80211_ATTR_STA_VLAN (plus the MLD link id) for MLO stations even when
> no VLAN is configured (vlan_id == 0), and the bind is deliberately not
> skipped for vlan_id == old_vlanid == 0 (the "needs rebind after
> sta_remove" path in ap_sta_bind_vlan_helper()).
> 
> On kernels without ML VLAN support (no CFG80211_STA_AP_ML_STA;
> mac80211/cfg80211 reject VLAN operations for ML stations) the kernel
> answers -EINVAL, and every rebind - triggered by association and by
> each rekey/EAPOL reauth - fails association processing and tears the
> MLO client down:
> 
> hostapd: nl80211: NL80211_ATTR_STA_VLAN (addr=<sta> ifname=ap-mld1
> vlan_id=0) failed: -22 (Invalid argument)
> 
> Observed as a Wi-Fi 7 MLO client being kicked off an ap-mld interface
> every ~60-90 seconds, deterministically, on MT7996 AP hardware (hostapd
> 831364bf, kernel 6.18.53 / backports 7.2).
> 
> These two guards skip the driver call for untagged MLO stations only;
> dynamic VLAN keeps its previous behaviour until the kernel side gains
> ML VLAN support:
> 
> 1. src/ap/sta_info.c - skip the helper-level bind for MLO stations with
>    vlan_id == 0 (covers the normal association path).
> 2. src/drivers/driver_nl80211.c - funnel guard in i802_set_sta_vlan():
>    skip any untagged (vlan_id == 0) bind that carries a link id, which
>    covers every caller by construction.
> 
> With this applied, zero STA_VLAN errors across many MLO associations
> and rekeys, and the client stays connected; legacy clients on non-MLD
> interfaces and dynamic-VLAN users behave exactly as before. (Also
> reported to OpenWrt, which carries the patch:
> https://github.com/openwrt/openwrt/issues/25415 )
> 
> ---

Please read the top level CONTRIBUTIONS file. I would need a
Signed-off-by: line from the author or copyright holder of the patch to
be able to consider applying it to hostap.git.

> --- a/src/ap/sta_info.c
> +++ b/src/ap/sta_info.c
> @@ -1493,6 +1493,19 @@
> +	/*
> +	 * The kernel side used in this build rejects VLAN operations for
> +	 * MLO stations with -EINVAL (see the driver_nl80211.c part of this
> +	 * patch), and the error makes ap_sta_bind_vlan() fail association

This comment should not reference "this patch" since that context is
quite unclear once the code is applied. Furthermore, sta_info.c should
not have any nl80211 specific behavior or comments since this is
supposed to be independent of the driver interface.

> +	/*
> +	 * Every STA VLAN binding funnels through here.  On an MLD AP each
> +	 * command carries NL80211_ATTR_MLO_LINK_ID, and the kernel side
> +	 * used in this build (no ML VLAN support) rejects such untagged

What is "this build" referring to? That sounds quite unclear and most
likely incorrect for many cases.

> +	 * binds with -EINVAL, which hostapd turns into a station teardown.
> +	 * vlan_id 0 carries no VLAN semantics, so skip the command whenever
> +	 * a link id is present; the station stays bound to its own BSS
> +	 * interface as intended.  Dynamic VLAN (vlan_id > 0) keeps its
> +	 * previous behaviour.
> +	 */
> +	if (vlan_id == 0 && link_id != NL80211_DRV_LINK_ID_NA)
> +		return 0;

Where is the dynamic VLAN case handled for ML associations if this place
skips it?
diff mbox series

Patch

--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -1493,6 +1493,19 @@ 
 	}
 #endif /* CONFIG_IEEE80211BE */

+	/*
+	 * The kernel side used in this build rejects VLAN operations for
+	 * MLO stations with -EINVAL (see the driver_nl80211.c part of this
+	 * patch), and the error makes ap_sta_bind_vlan() fail association
+	 * processing, which bounces MLO clients.  An untagged station
+	 * (vlan_id 0) has no VLAN binding to perform anyway, so skip the
+	 * driver call in that case.  Dynamic VLAN together with MLO keeps
+	 * its previous (rejected) behaviour until the kernel side gains
+	 * ML VLAN support.
+	 */
+	if (ap_sta_is_mld(hapd, sta) && sta->vlan_id == 0)
+		return 0;
+
 	if ((sta->flags & WLAN_STA_WDS) && sta->vlan_id == 0) {
 		wpa_printf(MSG_DEBUG,
 			   "Do not override WDS VLAN assignment for STA "
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -9134,6 +9134,18 @@ 
 		   ", ifname=%s[%d], vlan_id=%d)",
 		   bss->ifname, if_nametoindex(bss->ifname),
 		   MAC2STR(addr), ifname, if_nametoindex(ifname), vlan_id);
+	/*
+	 * Every STA VLAN binding funnels through here.  On an MLD AP each
+	 * command carries NL80211_ATTR_MLO_LINK_ID, and the kernel side
+	 * used in this build (no ML VLAN support) rejects such untagged
+	 * binds with -EINVAL, which hostapd turns into a station teardown.
+	 * vlan_id 0 carries no VLAN semantics, so skip the command whenever
+	 * a link id is present; the station stays bound to its own BSS
+	 * interface as intended.  Dynamic VLAN (vlan_id > 0) keeps its
+	 * previous behaviour.
+	 */
+	if (vlan_id == 0 && link_id != NL80211_DRV_LINK_ID_NA)
+		return 0;
 	if (!(msg = nl80211_bss_msg(bss, 0, NL80211_CMD_SET_STATION)) ||
 	    nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr) ||
 	    (vlan_id && (drv->capa.flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD) &&