diff mbox series

ap: never disconnect idle stations sooner than ap_max_inactivity

Message ID CAG0XsJEivMe8Dp+c0yuLtgZ50a6ToR3YGNsg8=ZNzJ068yb2tA@mail.gmail.com
State Changes Requested
Headers show
Series ap: never disconnect idle stations sooner than ap_max_inactivity | expand

Commit Message

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

hostapd adopts the client-advertised BSS Max Idle Period from the
(re)association request and lets it override the AP-wide
ap_max_inactivity, in two places:

- ap_handle_timer():
    if (sta->max_idle_period)
            max_inactivity = (sta->max_idle_period * 1024 + 999) / 1000;
- ap_sta_alloc(): the same computation for the initial timer
  registration.

Some clients (Apple) advertise a short BSS Max Idle Period (~20 units,
about 20 s) for power saving - they expect the AP to buffer frames for
them, not to be disconnected. The result is deterministic: with the
screen off (idle), the client is removed exactly 20 s after
association:

deauthenticated due to inactivity (timer DEAUTH/REMOVE)

Raising ap_max_inactivity has no effect for such clients because the
adoption overwrites it - we verified 20-second kicks with
ap_max_inactivity=86400 present in the running configuration (MT7996
AP-MLD, hostapd 831364bf). Clients without the element keep using the
AP-wide timeout, which is why the bug looks client-specific.

Fix: never disconnect a station sooner than the AP-wide
ap_max_inactivity - floor the adopted value in both places.

Verification: an Apple MLO client left idle with the screen off was
previously kicked at exactly 20 s on every attempt; with this patch it
stays associated indefinitely. Clients that do not advertise BSS Max
Idle Period are unaffected. (Also reported to OpenWrt:
https://github.com/openwrt/openwrt/issues/25416 )

---
 src/ap/sta_info.c | 36 ++++++++++++++++++++++++++++++++----
 1 file changed, 32 insertions(+), 4 deletions(-)

 	}
 	sta->supported_rates_len = i;

-	if (sta->max_idle_period)
-		max_inactivity = (sta->max_idle_period * 1024 + 999) / 1000;
+	if (sta->max_idle_period) {
+		/*
+		 * Never disconnect a station sooner than the AP-wide
+		 * ap_max_inactivity: some clients (e.g. Apple) advertise a
+		 * short BSS Max Idle Period for power saving and expect
+		 * buffered keep-alives, not disassociation. Adopting the
+		 * value as-is made idle stations get kicked ~20s after
+		 * association when the data poll went unanswered.
+		 */
+		int adopted = (sta->max_idle_period * 1024 + 999) / 1000;
+
+		if (adopted < max_inactivity)
+			adopted = max_inactivity;
+		max_inactivity = adopted;
+	}

 	if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_INACTIVITY_TIMER)) {
 		wpa_printf(MSG_DEBUG, "%s: register ap_handle_timer timeout "

Comments

Jouni Malinen Oct. 5, 2026, 1:37 p.m. UTC | #1
On Sat, Sep 26, 2026 at 08:39:57AM -0700, hc86862997@gmail.com wrote:
> hostapd adopts the client-advertised BSS Max Idle Period from the
> (re)association request and lets it override the AP-wide
> ap_max_inactivity, in two places:
> 
> - ap_handle_timer():
>     if (sta->max_idle_period)
>             max_inactivity = (sta->max_idle_period * 1024 + 999) / 1000;
> - ap_sta_alloc(): the same computation for the initial timer
>   registration.
> 
> Some clients (Apple) advertise a short BSS Max Idle Period (~20 units,
> about 20 s) for power saving - they expect the AP to buffer frames for
> them, not to be disconnected. The result is deterministic: with the
> screen off (idle), the client is removed exactly 20 s after
> association:

The BSS max idle period indicated in the (Re)Association Request frame
is an explicit request from the STA to use the provided idle timeout. If
it is known that the particular clients referenced here do this
incorrectly (i.e., ask for 20000 TUs but do not send keep-alive frames
before that times out), this should be reported to the STA vendor as an
issue that needs to be fixed in the STA.

> Raising ap_max_inactivity has no effect for such clients because the
> adoption overwrites it

By design.. I.e., that is the correct AP behavior on accepting a STA
requested value.

> Fix: never disconnect a station sooner than the AP-wide
> ap_max_inactivity - floor the adopted value in both places.

That would break the expected functionality. The correct fix for this
would be to fix the STA. If that is not doable, potentially adding a new
configuration parameter to add a minimum acceptable timeout from STA
could be considered as a workaround (i.e., min_acceptable_idle_period to
complement the already available max_acceptable_idle_period).

> Verification: an Apple MLO client left idle with the screen off was
> previously kicked at exactly 20 s on every attempt; with this patch it
> stays associated indefinitely. Clients that do not advertise BSS Max
> Idle Period are unaffected. (Also reported to OpenWrt:
> https://github.com/openwrt/openwrt/issues/25416 )
> 
> ---

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.
diff mbox series

Patch

--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -631,8 +631,22 @@ 
 		return;
 	}

-	if (sta->max_idle_period)
-		max_inactivity = (sta->max_idle_period * 1024 + 999) / 1000;
+	if (sta->max_idle_period) {
+		/*
+		 * Never disconnect a station sooner than the AP-wide
+		 * ap_max_inactivity: some clients (e.g. Apple) advertise a
+		 * short BSS Max Idle Period for power saving and expect
+		 * buffered keep-alives, not disassociation. Adopting the
+		 * value as-is made idle stations get kicked ~20s after
+		 * association when the data poll went unanswered.
+		 */
+		int adopted = (sta->max_idle_period * 1024 + 999) / 1000;
+
+		if (adopted < max_inactivity)
+			adopted = max_inactivity;
+		max_inactivity = adopted;
+	}

 	if ((sta->flags & WLAN_STA_ASSOC) &&
 	    (sta->timeout_next == STA_NULLFUNC ||
@@ -945,8 +959,22 @@