diff --git a/bes2600/bes2600.h b/bes2600/bes2600.h index 0e60960..66482f7 100644 --- a/bes2600/bes2600.h +++ b/bes2600/bes2600.h @@ -596,6 +596,11 @@ struct bes2600_vif { unsigned long rx_timestamp; u32 cipherType; + /* Decrypt-storm fast-recover (Trigger B). See txrx.c. */ + unsigned long decrypt_storm_window_start; + unsigned int decrypt_storm_count; + unsigned int decrypt_storm_recoveries; + struct work_struct decrypt_storm_recover_work; /* AP powersave */ u32 link_id_map; @@ -856,4 +861,8 @@ int bes2600_btusb_setup_pipes(struct sbus_priv *sbus_priv); void bes2600_btusb_uninit(struct usb_interface *interface); #endif +/* Decrypt-storm fast-recover helpers — see txrx.c. */ +void bes2600_decrypt_storm_init(struct bes2600_vif *priv); +void bes2600_decrypt_storm_account(struct bes2600_vif *priv); + #endif /* BES2600_H */ diff --git a/bes2600/debug.c b/bes2600/debug.c index 5228b22..ca223dd 100644 --- a/bes2600/debug.c +++ b/bes2600/debug.c @@ -542,6 +542,8 @@ static int bes2600_status_show_priv(struct seq_file *seq, void *v) priv->listening ? " (listening)" : ""); seq_printf(seq, "Assoc: %s\n", bes2600_debug_join_status[priv->join_status]); + seq_printf(seq, "DecryptStormRecoveries: %u\n", + priv->decrypt_storm_recoveries); if (priv->rx_filter.promiscuous) seq_puts(seq, "Filter: promisc\n"); else if (priv->rx_filter.fcs) diff --git a/bes2600/sta.c b/bes2600/sta.c index aa69eb8..0723a7c 100644 --- a/bes2600/sta.c +++ b/bes2600/sta.c @@ -448,6 +448,7 @@ void bes2600_remove_interface(struct ieee80211_hw *dev, cancel_delayed_work_sync(&priv->join_timeout); cancel_delayed_work_sync(&priv->set_cts_work); cancel_delayed_work_sync(&priv->pending_offchanneltx_work); + cancel_work_sync(&priv->decrypt_storm_recover_work); del_timer_sync(&priv->mcast_timeout); /* TODO:COMBO: May be reset of these variables "delayed_link_loss and @@ -2619,6 +2620,7 @@ int bes2600_vif_setup(struct bes2600_vif *priv) /* Setup per vif workitems and locks */ spin_lock_init(&priv->vif_lock); + bes2600_decrypt_storm_init(priv); INIT_WORK(&priv->join_work, bes2600_join_work); INIT_DELAYED_WORK(&priv->join_timeout, bes2600_join_timeout); INIT_WORK(&priv->unjoin_work, bes2600_unjoin_work); diff --git a/bes2600/txrx.c b/bes2600/txrx.c index dbd1b23..346312c 100644 --- a/bes2600/txrx.c +++ b/bes2600/txrx.c @@ -25,6 +25,78 @@ #define BES2600_INVALID_RATE_ID (0xFF) +/* + * Decrypt-storm fast-recover (Trigger B). + * + * When the BES2600 firmware reports WSM_STATUS_DECRYPTFAILURE for a + * burst of received frames (typically because the host's PTK or GTK + * has fallen out of sync with the AP), the AP eventually concludes that + * the STA is not authenticated and emits an unprotected deauth-reason-6 + * ("Class 2 frame received from non-authenticated station"). On the + * deployed pinetab2 + bes2600 stack this AP-initiated deauth has been + * observed to leave the link blackholed for up to 109 s before + * userspace finds a different SSID/channel to recover on. (Receipts at + * https://git.reauktion.de/marfrit/besser, notes/phase5-2026-05-06.md.) + * + * Recovery here pre-empts the AP: when we see THRESHOLD decrypt + * failures within WINDOW, we ask mac80211 for a clean reassoc via + * ieee80211_connection_loss(), which causes immediate disassociation + * and lets userspace auto-reconnect with fresh keys. + * + * mac80211 contract: ieee80211_connection_loss() may be called + * regardless of IEEE80211_HW_CONNECTION_MONITOR; it causes immediate + * disassociation without driver-side recovery attempts. See + * include/net/mac80211.h for the canonical doc-comment. + * + * The threshold is set well above the steady-state per-vif + * decrypt-fail rate observed in measurement (~1/min even under + * sustained 1 MB/s load), so a true storm is required to trip it. + */ +#define BES2600_DECRYPT_STORM_THRESHOLD 5 +#define BES2600_DECRYPT_STORM_WINDOW_MS 5000 + +static void bes2600_decrypt_storm_recover_work(struct work_struct *work) +{ + struct bes2600_vif *priv = container_of(work, struct bes2600_vif, + decrypt_storm_recover_work); + + if (!priv->vif) + return; + + bes_warn("[bes2600] decrypt-storm fast-recover: forcing reassoc\n"); + ieee80211_connection_loss(priv->vif); + priv->decrypt_storm_recoveries++; +} + +void bes2600_decrypt_storm_init(struct bes2600_vif *priv) +{ + INIT_WORK(&priv->decrypt_storm_recover_work, + bes2600_decrypt_storm_recover_work); + priv->decrypt_storm_window_start = 0; + priv->decrypt_storm_count = 0; + priv->decrypt_storm_recoveries = 0; +} + +void bes2600_decrypt_storm_account(struct bes2600_vif *priv) +{ + unsigned long now = jiffies; + unsigned long window = msecs_to_jiffies(BES2600_DECRYPT_STORM_WINDOW_MS); + + if (priv->decrypt_storm_window_start == 0 || + time_after(now, priv->decrypt_storm_window_start + window)) { + priv->decrypt_storm_window_start = now; + priv->decrypt_storm_count = 1; + return; + } + + if (++priv->decrypt_storm_count >= BES2600_DECRYPT_STORM_THRESHOLD) { + priv->decrypt_storm_count = 0; + /* Skew the window so we don't re-fire on the same storm. */ + priv->decrypt_storm_window_start = now + window; + schedule_work(&priv->decrypt_storm_recover_work); + } +} + #ifdef CONFIG_BES2600_TESTMODE #include "bes_nl80211_testmode_msg.h" #endif /* CONFIG_BES2600_TESTMODE */ @@ -1672,6 +1744,8 @@ void bes2600_rx_cb(struct bes2600_vif *priv, goto drop; } else { bes_warn("[RX] Receive failure: %d.\n", arg->status); + if (arg->status == WSM_STATUS_DECRYPTFAILURE) + bes2600_decrypt_storm_account(priv); goto drop; } }