forked from marfrit/kernel-agent
b04c8cd501
PR #33's per-series mirrors were generated against the bes2600-dkms cleanups branch (rooted at fe73571) without rebasing onto the v7.0-danctnix1 kernel baseline. Result: per-commit diffs carried stale baseline context (e.g. from_timer rather than the new timer_container_of API), so the cumulative no longer applied cleanly to ohm's actual base. pkgrel=6 build #1 failed with 'Hunk #3 FAILED' in Patch D's sta.c. Fix: in marfrit/bes2600-dkms, create danctnix-sync branch (fe73571 + drop-in replace bes2600/ with v7.0-danctnix1's drivers/staging/bes2600/), rebase cleanups onto it as cleanups-rebased-on-danctnix, manually resolve the resulting conflicts keeping each commit's intent + the new baseline context, rebase Patch H accordingly. Format-patch and re-route to the same series-dir names as PR #33. Conflict resolution notes: - 'remove userspace /dev/bes2600 character device interface' commit: the chardev wrapper was removed but two utility funcs that danctnix's bes2600_btuart.c depends on (bes2600_chrdev_is_bus_error, bes2600_chrdev_switch_subsys_glb) were re-added with EXPORT_SYMBOL_GPL. bes2600_switch_bt re-added as static (file-local, called only from bes2600_chrdev_switch_subsys_glb). - Patch D (atomicize ba_lock): re-resolved bes2600_ba_timer's timer_container_of() vs from_timer() to keep the new API. - SCS Makefile @@ hunk counts corrected from -9,6 +9,10 to -9,6 +9,11 (the original was actually wrong; build-via-fuzz was masking it). Cumulative b2sum: ka-promote ohm now emits eb179c03f35a4dbaec2e40036f0033ef04985bb6b14ab22419d68e5caaa5874f... (279 554 bytes, 32 patches resolved). pkgrel=6 built from this manifest + installed on ohm 2026-05-19 ~23:39. Functional verification: bes2600 + bes2600_btuart both load, Pattern A 0 over fresh boot, wlan0 associates to newton. srcversion 1A919EED0E6DC2478559B17 differs from pkgrel=5's BEB625FA... — the reconstruction is functionally equivalent (5 GHz working, no firmware/driver race conditions) but NOT byte-equivalent (the chardev utility re-add chose different formatting than the original danctnix code). Byte-equivalence is not a goal; per-series traceability and working hardware are. Closes (proper this time): #29. Refs: #28, #30, #33 (the half-working attempt), #31, #32.
222 lines
9.0 KiB
Diff
222 lines
9.0 KiB
Diff
From 91640bd96d36dd5769b1325e1b2130a95277e0e7 Mon Sep 17 00:00:00 2001
|
|
From: "Claude (noether)" <claude@reauktion.de>
|
|
Date: Wed, 6 May 2026 19:50:52 +0200
|
|
Subject: [PATCH 20/29] bes2600: pre-empt AP-deauth-6 with mac80211 reassoc on
|
|
decrypt-fail storm
|
|
|
|
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.)
|
|
|
|
Add a sliding-window counter on each bes2600_vif: when 5 decrypt
|
|
failures fire within 5 s, schedule a worker that calls
|
|
ieee80211_connection_loss(vif). mac80211 then performs immediate
|
|
disassociation; userspace (NetworkManager / wpa_supplicant) reconnects
|
|
with fresh keys before the AP gets a chance to fire its unprotected
|
|
deauth.
|
|
|
|
Predicted Phase 7 delta vs the unpatched baseline:
|
|
- decrypt-burst rate: unchanged (this does not address root cause)
|
|
- AP-deauth-6 rate: <= 0.2 of baseline
|
|
- conditional probability of >5s blackhole given a burst:
|
|
100% -> <= 10%
|
|
- worst-case recovery time: 109s -> <5s
|
|
|
|
Contract pin: ieee80211_connection_loss() per
|
|
include/net/mac80211.h: "may also be called if the connection needs to
|
|
be terminated for some other reason... will cause immediate change to
|
|
disassociated state, without connection recovery attempts." Userspace
|
|
recovery is the existing NM/wpa_supplicant path. The worker context
|
|
satisfies the implicit process-context expectation.
|
|
|
|
Files touched:
|
|
- bes2600/bes2600.h: 4 new fields on struct bes2600_vif + 2 prototypes
|
|
- bes2600/txrx.c: new helpers + the call site at the existing
|
|
WSM_STATUS_DECRYPTFAILURE log point (the unconditional "goto drop"
|
|
branch in bes2600_rx_cb)
|
|
- bes2600/sta.c: bes2600_decrypt_storm_init() in bes2600_vif_setup;
|
|
cancel_work_sync() in bes2600_remove_interface, alongside the
|
|
existing per-vif cancel_*_work_sync block. Safe under the kernel
|
|
cancel_work_sync contract: the work_struct is INIT_WORK'd in setup,
|
|
so the call is valid; it blocks until any in-flight handler returns,
|
|
ensuring no use-after-free of priv when mac80211 frees the vif; and
|
|
it is idempotent (subsequent calls just return false).
|
|
- bes2600/debug.c: DecryptStormRecoveries seq_printf in the per-vif
|
|
status seq_file output
|
|
|
|
Threshold (5/5s) 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. The cw1200/cw1260
|
|
ancestor has no equivalent storm-recovery; this is a clean addition.
|
|
|
|
checkpatch.pl --no-tree --strict: clean (0/0/0).
|
|
|
|
Signed-off-by: Claude (noether) <claude@reauktion.de>
|
|
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
---
|
|
bes2600/bes2600.h | 9 ++++++
|
|
bes2600/debug.c | 2 ++
|
|
bes2600/sta.c | 2 ++
|
|
bes2600/txrx.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++
|
|
4 files changed, 87 insertions(+)
|
|
|
|
diff --git a/drivers/staging/bes2600/bes2600.h b/drivers/staging/bes2600/bes2600.h
|
|
index 0e60960..66482f7 100644
|
|
--- a/drivers/staging/bes2600/bes2600.h
|
|
+++ b/drivers/staging/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/drivers/staging/bes2600/debug.c b/drivers/staging/bes2600/debug.c
|
|
index 5228b22..ca223dd 100644
|
|
--- a/drivers/staging/bes2600/debug.c
|
|
+++ b/drivers/staging/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/drivers/staging/bes2600/sta.c b/drivers/staging/bes2600/sta.c
|
|
index bc6d483..139bdae 100644
|
|
--- a/drivers/staging/bes2600/sta.c
|
|
+++ b/drivers/staging/bes2600/sta.c
|
|
@@ -464,6 +464,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);
|
|
|
|
timer_delete_sync(&priv->mcast_timeout);
|
|
/* TODO:COMBO: May be reset of these variables "delayed_link_loss and
|
|
@@ -2639,6 +2640,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/drivers/staging/bes2600/txrx.c b/drivers/staging/bes2600/txrx.c
|
|
index 017f0d8..f6a66d6 100644
|
|
--- a/drivers/staging/bes2600/txrx.c
|
|
+++ b/drivers/staging/bes2600/txrx.c
|
|
@@ -26,6 +26,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 */
|
|
@@ -1694,6 +1766,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;
|
|
}
|
|
}
|
|
--
|
|
2.54.0
|
|
|