From b198280df1575b49232a01905351802c53861466 Mon Sep 17 00:00:00 2001
From: Marek Puzyniak <marek.puzyniak@iopsys.eu>
Date: Tue, 28 Nov 2023 08:59:56 +0000
Subject: [PATCH] Deauth sta left after successful steer

Generally after successful steering station is disassociated
from source BSSID, but sometimes it happens that station
is not disassociated from source BSSID and is associated
to targed BSSID at the same time. To avoid that situation we start
timer to disconnect wifi station at it timeout when station is still
associated to source BSSID.

Signed-off-by: Marek Puzyniak <marek.puzyniak@iopsys.eu>
---
 src/agent.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++++-
 src/agent.h |  2 ++
 2 files changed, 50 insertions(+), 1 deletion(-)

diff --git a/src/agent.c b/src/agent.c
index 2c97ce3ec..0feb2d219 100644
--- a/src/agent.c
+++ b/src/agent.c
@@ -1316,6 +1316,16 @@ static void wifi_sta_steer_timeout(atimer_t *t)
 	timer_set(&s->sta_steer_timer, 1000);
 }
 
+static void wifi_sta_steered_deauth(atimer_t *t)
+{
+	struct sta *s = container_of(t, struct sta, sta_steer_deauth_timer);
+	struct netif_ap *vif = s->vif;
+
+	warn("%s: Disconnect station: " MACFMT " from interface: %s with reson:%i\n",
+		__func__, MAC2STR(s->macaddr), vif->name, WIFI_REASON_BSS_TRANSITION_DISASSOC);
+	wifi_disconnect_sta(vif->name, s->macaddr, WIFI_REASON_BSS_TRANSITION_DISASSOC);
+}
+
 static int cond_refresh_sta_neighbor_list(struct agent *a, struct sta *s)
 {
 	struct netif_ap *vif = s->vif;
@@ -1514,6 +1524,11 @@ static int wifi_add_sta(struct agent *a, const char *vif,
 				timer_set(&sptr->sta_finalize_timer,
 						ifptr->cfg->steer_legacy_retry_secs * 1000);
 			}
+			/* In case station is already on stalist, cancel steered station
+			 * deauthentication timer if it is stil runnning.
+			 */
+			if (timer_pending(&sptr->sta_steer_deauth_timer))
+				timer_del(&sptr->sta_steer_deauth_timer);
 			return 0;
 		}
 	}
@@ -1531,6 +1546,7 @@ static int wifi_add_sta(struct agent *a, const char *vif,
 	timer_init(&new->sta_bcn_req_timer, wifi_sta_bcn_req);
 	timer_init(&new->sta_steer_timer, wifi_sta_steer_timeout);
 	timer_init(&new->sta_finalize_timer, wifi_sta_finalize);
+	timer_init(&new->sta_steer_deauth_timer, wifi_sta_steered_deauth);
 	timestamp_update(&new->last_update);
 	INIT_LIST_HEAD(&new->sta_nbrlist);
 	INIT_LIST_HEAD(&new->pref_nbrlist);
@@ -1581,6 +1597,7 @@ static int wifi_del_sta(struct agent *a, const char *vif,
 			timer_del(&s->sta_steer_timer);
 			timer_del(&s->sta_bcn_req_timer);
 			timer_del(&s->sta_timer);
+			timer_del(&s->sta_steer_deauth_timer);
 			list_del(&s->list);
 			list_flush(&s->pref_nbrlist, struct pref_neighbor, list);
 			list_flush(&s->sta_nbrlist, struct sta_neighbor, list);
@@ -2367,7 +2384,37 @@ static void wifi_sta_event_handler(void *c, struct blob_attr *msg)
 		trace("%s: btm-resp for "MACFMT" status %d\n",
 			  __func__, MAC2STR(mac), status);
 
-		if (status) {
+		/* Generally after successful steering station is disassociated from source BSSID,
+		 * but sometimes it happens that station is not disassociated from source BSSID
+		 * and is associated to targed BSSID at the same time. To avoid that situation
+		 * we start timer here to disconnect wifi station when it left on source BSSID.
+		 * When driver removes station after successful steer then timer will never end
+		 * because it will be deleted together with station, but when timer hits timeout
+		 * then it means station was not disconnected and funtion wifi_sta_steered_deauth
+		 * will do this.
+		 */
+		if (!status) {
+			struct netif_ap *p = NULL;
+			struct sta *s = NULL;
+
+			list_for_each_entry(p, &a->aplist, list) {
+				if (!strcmp(p->name, ifname)) {
+					break;
+				}
+			}
+
+			if (!p) {
+				warn("%s: Unable to find iface %s\n", __func__, ifname);
+				return;
+			}
+
+			list_for_each_entry(s, &p->stalist, list) {
+				if (!memcmp(s->macaddr, mac, 6)) {
+					timer_set(&s->sta_steer_deauth_timer, STA_STEERED_DEAUTH_INTERVAL * 1000);
+					break;
+				}
+			}
+		} else {
 			/* TODO:
 			 * update reject counter and retry steer later
 			 */
diff --git a/src/agent.h b/src/agent.h
index 31eb2092d..1a083c073 100644
--- a/src/agent.h
+++ b/src/agent.h
@@ -149,6 +149,7 @@ struct wifi_assoc_frame {
 #define STA_NBR_REFRESH_CNT             120        /** x5 = 10 mins in auto */
 #define STA_NBR_LIST_INTERVAL           2000  /** fetch bcn rpt after 2 secs of requesting*/
 #define STA_NBR_AGEOUT_INTERVAL         10000   /** sta bcnreport is dynamic; expires quickly */
+#define STA_STEERED_DEAUTH_INTERVAL     10   /** 10 sec after successful sterring sta will be cleaned up */
 
 typedef struct cntlr_preflist {
 	int num;
@@ -213,6 +214,7 @@ struct sta {
 	uint32_t steer_opportunity_tmo;     /** steer op timeout in msec */
 	struct timespec steer_opportunity;  /** steer opportunity time window */
 	atimer_t sta_steer_timer;           /** steer opportunity timer */
+	atimer_t sta_steer_deauth_timer;    /** time to deauth sta left after successful ster */
 	atimer_t sta_finalize_timer;        /** for sta cleanup */
 	int ref;                            /** ref counting purpose */
 	bool legacy_steered;                /** legacy steered */
-- 
GitLab