From a5bac33bdecfe0bdd24217fbcfefbc127079695b Mon Sep 17 00:00:00 2001
From: Anjan Chanda <anjan.chanda@genexis.eu>
Date: Mon, 9 Jun 2025 17:02:32 +0200
Subject: [PATCH 1/2] update node's stalist from Topology Response

---
 src/cntlr.c     | 21 ++++++++++++++++
 src/cntlr.h     |  1 +
 src/cntlr_map.c | 65 +++++++++++++++++++++++++++----------------------
 3 files changed, 58 insertions(+), 29 deletions(-)

diff --git a/src/cntlr.c b/src/cntlr.c
index f40e635b..49a9a247 100644
--- a/src/cntlr.c
+++ b/src/cntlr.c
@@ -521,6 +521,27 @@ struct sta *node_find_sta(struct node *n, uint8_t *macaddr)
 	return NULL;
 }
 
+void node_update_stalist(struct node *n, uint8_t *stalist, int num)
+{
+	struct sta *s = NULL, *tmp;
+
+	list_for_each_entry_safe(s, tmp, &n->stalist, list) {
+		bool found = false;
+
+		for (int i = 0; i < num; i++) {
+			if (!memcmp(s->macaddr, &stalist[i*6], 6)) {
+				found = true;
+				break;
+			}
+		}
+
+		if (!found) {
+			list_del(&s->list);
+			n->sta_count--;
+		}
+	}
+}
+
 struct unassoc_sta_metrics *cntlr_find_usta_metric(struct controller *c,
                                                    struct sta *s,
                                                    uint8_t *agent_macaddr)
diff --git a/src/cntlr.h b/src/cntlr.h
index a7ea588a..3667a16c 100644
--- a/src/cntlr.h
+++ b/src/cntlr.h
@@ -415,6 +415,7 @@ void cntrl_send_max_wifi_bh_hops_policy(struct controller *c);
 void node_add_sta(struct node *n, struct sta *s);
 void node_del_sta(struct node *n, struct sta *s);
 struct sta *node_find_sta(struct node *n, uint8_t *sta_macaddr);
+void node_update_stalist(struct node *n, uint8_t *stalist, int num);
 
 int cntlr_register_steer_module(struct controller *c, const char *name);
 int cntlr_unregister_steer_module(struct controller *c, char *name);
diff --git a/src/cntlr_map.c b/src/cntlr_map.c
index 4cd4022c..ad7beaee 100644
--- a/src/cntlr_map.c
+++ b/src/cntlr_map.c
@@ -566,19 +566,19 @@ int handle_topology_response(void *cntlr, struct cmdu_buff *cmdu, struct node *n
 #endif
 
 	if (tv[TOPOLOGY_RESPONSE_ASSOCIATED_CLIENTS_IDX][0]) {
-		struct tlv_assoc_client *tlv;
-		uint8_t *tv_data;
+		struct tlv_assoc_client *tlv = (struct tlv_assoc_client *)
+				tv[TOPOLOGY_RESPONSE_ASSOCIATED_CLIENTS_IDX][0]->data;
+		uint8_t *tv_data = (uint8_t *)tlv;
 		int i, offset = 0;
+		uint8_t stas[256 * 6];
+		int max_stas = sizeof(stas) / sizeof(stas[0]);
+		memset(stas, 0, sizeof(stas));
+		int idx = 0;
 
-		tlv = (struct tlv_assoc_client *)
-			tv[TOPOLOGY_RESPONSE_ASSOCIATED_CLIENTS_IDX][0]->data;
 		if (!tlv)
 			return -1;
 
-		tv_data = (uint8_t *)tlv;
-
 		offset += 1; /* num_bss */
-
 		for (i = 0; i < tlv->num_bss; i++) {
 			struct netif_iface *bss_iface;
 			uint8_t bssid[6] = {0};
@@ -597,14 +597,18 @@ int handle_topology_response(void *cntlr, struct cmdu_buff *cmdu, struct node *n
 				continue;
 			}
 
+			if (!num_client)
+				continue;
+
 			for (j = 0; j < num_client; j++) {
 				struct netif_iface *bsta_iface = NULL;
 				uint8_t macaddr[6] = {0};
 				uint16_t conntime;
-				enum sta_state old_state;
 				struct sta *s;
 
 				memcpy(macaddr, &tv_data[offset], 6);
+				if (idx < max_stas)
+					memcpy(&stas[6*idx++], &tv_data[offset], 6);
 				offset += 6; /* macaddr */
 				conntime = BUF_GET_BE16(tv_data[offset]);
 				offset += 2; /* conntime */
@@ -613,6 +617,8 @@ int handle_topology_response(void *cntlr, struct cmdu_buff *cmdu, struct node *n
 
 				s = cntlr_find_sta(c->sta_table, macaddr);
 				if (!s) {
+					struct cmdu_buff *txcmdu;
+
 					s = cntlr_add_sta(c, c->sta_table, macaddr);
 					if (!s) {
 						cntlr_dbg(LOG_STA, "%s: failed to add STA " MACFMT "\n",
@@ -620,37 +626,37 @@ int handle_topology_response(void *cntlr, struct cmdu_buff *cmdu, struct node *n
 						continue;
 					}
 
+					s->state = STA_CONNECTED;
+					memcpy(s->bssid, bssid, 6);
+					memset(s->ssid, 0, sizeof(s->ssid));
+					memcpy(s->ssid, bss_iface->bss->ssid, bss_iface->bss->ssidlen);
+					s->ssidlen = bss_iface->bss->ssidlen;
+					s->de_sta->conn_time = conntime;
+					s->assoc_time = time(NULL) - conntime;
+
+					node_add_sta(n, s);
 					cntlr_dbg(LOG_STA, "%s: STA " MACFMT " connected to Node " MACFMT "\n",
 						  __func__, MAC2STR(s->macaddr), MAC2STR(n->almacaddr));
-				}
-				old_state = s->state;
 
-				s->is_bsta = bsta_iface || bss_iface->bss->is_bbss ? true : false;
-				memcpy(s->bssid, bssid, 6);
-				memset(s->ssid, 0, sizeof(s->ssid));
-				memcpy(s->ssid, bss_iface->bss->ssid, bss_iface->bss->ssidlen);
-				s->ssidlen = bss_iface->bss->ssidlen;
-
-				s->de_sta->conn_time = conntime;
-				s->state = conntime != 0 ? STA_ASSOCIATED : STA_DISCONNECTED;
-				if (old_state != STA_ASSOCIATED && s->state == STA_ASSOCIATED) {
-					struct cmdu_buff *txcmdu;
-
-					node_add_sta(n, s);
-					time(&s->assoc_time);
 					txcmdu = cntlr_gen_client_caps_query(c,
-									     c->almacaddr,
-									     s->macaddr,
-									     s->bssid);
+										 c->almacaddr,
+										 s->macaddr,
+										 s->bssid);
 					if (txcmdu) {
 						send_cmdu(c, txcmdu);
 						cmdu_free(txcmdu);
 					}
-				} else if (old_state != STA_DISCONNECTED && s->state == STA_DISCONNECTED) {
-					node_del_sta(n, s);
-					time(&s->disassoc_time);
+				} else {
+					/* ignore conflicting STA-association from multiple Agents */
+					if (memcmp(s->agent_almacaddr, n->almacaddr, 6) ||
+						memcmp(s->bssid, bssid, 6)) {
+						continue;
+					}
+
+					s->de_sta->conn_time = conntime;
 				}
 
+				s->is_bsta = bsta_iface || bss_iface->bss->is_bbss ? true : false;
 				if (bsta_iface) {
 					memcpy(bsta_iface->upstream_bssid, bssid, 6);
 
@@ -663,6 +669,7 @@ int handle_topology_response(void *cntlr, struct cmdu_buff *cmdu, struct node *n
 				cntlr_update_sta_steer_data(c, s);
 			}
 		}
+		node_update_stalist(n, stas, (idx <= max_stas ? idx : max_stas));
 	}
 
 	/* Check opclass preferency age */
-- 
GitLab


From 7c783545bb06b9edbcd537f852f9171db42cbbf3 Mon Sep 17 00:00:00 2001
From: Filip Matusiak <filip.matusiak@iopsys.eu>
Date: Mon, 2 Jun 2025 16:18:32 +0200
Subject: [PATCH 2/2] Use timer to remove stale clients

- Use sta timer instead of iterating over stalists every second
- Only remove stations with no active node from client hashtable
- Fix topology notification to properly update stalists
- Remove STA from old node before adding to new one
- Don't add sta with MAC unset
- Free all STA upon exit
---
 src/cntlr.c     | 142 +++++++++++++++++++++++++++++++-----------------
 src/cntlr.h     |   5 +-
 src/cntlr_map.c |  75 ++++++++++++-------------
 src/sta.c       |  74 ++++++++++++++++++++-----
 src/sta.h       |   9 ++-
 5 files changed, 198 insertions(+), 107 deletions(-)

diff --git a/src/cntlr.c b/src/cntlr.c
index 49a9a247..4d4f7733 100644
--- a/src/cntlr.c
+++ b/src/cntlr.c
@@ -231,6 +231,23 @@ struct node *cntlr_find_node_with_bssid(struct controller *c, uint8_t *bssid)
 	return NULL;
 }
 
+/* returns first found node with given STA MAC on its stalist */
+struct node *cntlr_find_node_with_sta(struct controller *c, uint8_t *stamacaddr)
+{
+	struct node *n = NULL;
+
+	list_for_each_entry(n, &c->nodelist, list) {
+		struct sta *e = NULL;
+
+		list_for_each_entry(e, &n->stalist, list) {
+			if (!memcmp(e->macaddr, stamacaddr, 6))
+				return n;
+		}
+	}
+
+	return NULL;
+}
+
 #if (EASYMESH_VERSION >= 6)
 bool cntlr_radio_support_ap_wifi7(struct wifi7_radio_capabilities *wifi7_caps)
 {
@@ -482,31 +499,77 @@ void cntlr_bcn_metrics_timer_cb(atimer_t *t)
 	}
 }
 
-void node_add_sta(struct node *n, struct sta *s)
+static void cntlr_freeze_sta(struct controller *c, struct sta *s)
+{
+	timer_del(&s->bcn_metrics_timer);
+	timer_del(&s->btm_req_timer);
+	timer_del(&s->ageout_timer);
+
+	sta_free_assoc_frame(s);
+	sta_free_bcn_metrics(s);
+	cntlr_clean_bcnreqlist_sta(c, s);
+}
+
+static void cntlr_remove_sta(struct controller *c, struct sta *s)
+{
+	struct node *n = NULL;
+
+	do {
+		n = cntlr_find_node_with_sta(c, s->macaddr);
+		if (n)
+			node_del_sta(n, s);
+	} while (n);
+
+	cntlr_freeze_sta(c, s);
+	cntlr_del_sta(c->sta_table, s);
+}
+
+void cntlr_sta_ageout_timer_cb(atimer_t *t)
+{
+	trace("%s: --->\n", __func__);
+
+	struct sta *s = container_of(t, struct sta, ageout_timer);
+	struct controller *c = s->cntlr;
+
+	if (!c)
+		return;
+
+	cntlr_dbg(LOG_STA, "%s: Delete STA " MACFMT " after %ds of disassociation\n",
+		  __func__, MAC2STR(s->macaddr), c->cfg.stale_sta_timeout);
+	cntlr_remove_sta(c, s);
+}
+
+int node_add_sta(struct node *n, struct sta *s)
 {
 	if (WARN_ON(node_find_sta(n, s->macaddr))) {
 		cntlr_dbg(LOG_STA,
 			  "%s: Warn! STA " MACFMT " already in node " MACFMT"\n",
 			  __func__, MAC2STR(s->macaddr), MAC2STR(n->almacaddr));
-		return;
+		return -1;
 	}
 
 	memcpy(s->agent_almacaddr, n->almacaddr, 6);
 	list_add(&s->list, &n->stalist);
 	n->sta_count++;
+	sta_inc_ref(s);
+
+	return 0;
 }
 
-void node_del_sta(struct node *n, struct sta *s)
+int node_del_sta(struct node *n, struct sta *s)
 {
 	if (WARN_ON(!node_find_sta(n, s->macaddr))) {
 		cntlr_dbg(LOG_STA,
 			  "%s: Warn! STA " MACFMT " not in node " MACFMT"\n",
 			  __func__, MAC2STR(s->macaddr), MAC2STR(n->almacaddr));
-		return;
+		return -1;
 	}
 
 	list_del(&s->list);
 	n->sta_count--;
+	sta_dec_ref(s);
+
+	return 0;
 }
 
 struct sta *node_find_sta(struct node *n, uint8_t *macaddr)
@@ -538,6 +601,7 @@ void node_update_stalist(struct node *n, uint8_t *stalist, int num)
 		if (!found) {
 			list_del(&s->list);
 			n->sta_count--;
+			sta_dec_ref(s);
 		}
 	}
 }
@@ -556,23 +620,6 @@ struct unassoc_sta_metrics *cntlr_find_usta_metric(struct controller *c,
 	return NULL;
 }
 
-static void cntlr_freeze_sta(struct controller *c, struct sta *s)
-{
-	timer_del(&s->bcn_metrics_timer);
-	timer_del(&s->btm_req_timer);
-
-	sta_free_assoc_frame(s);
-	sta_free_bcn_metrics(s);
-	cntlr_clean_bcnreqlist_sta(c, s);
-}
-
-static void cntlr_remove_sta(struct controller *c, struct node *n, struct sta *s)
-{
-	cntlr_freeze_sta(c, s);
-	node_del_sta(n, s);
-	cntlr_del_sta(c->sta_table, s->macaddr);
-}
-
 struct cmdu_buff *cntlr_query_sta_metric(struct controller *c, struct sta *s)
 {
 	if (!c || !s)
@@ -900,7 +947,7 @@ static void node_clean_stalist(struct controller *c, struct node *n)
 		return;
 
 	list_for_each_entry_safe(s, tmp, &n->stalist, list) {
-		cntlr_remove_sta(c, n, s);
+		node_del_sta(n, s);
 	}
 
 	if (WARN_ON(n->sta_count != 0)) {
@@ -915,6 +962,27 @@ static void cntlr_clean_mac_hashtable(struct controller *c)
 	mactable_flush(c->mac_table);
 }
 
+static void cntlr_clean_all_sta(struct controller *c)
+{
+	struct hlist_node *tmp = NULL;
+	int i;
+
+	for (i = 0; i < MAC_HASHTABLE_SIZE; i++) {
+		struct sta *s = NULL;
+
+		if (hlist_empty(&c->sta_table[i]))
+			continue;
+
+		hlist_for_each_entry_safe(s, tmp, &c->sta_table[i], hlist) {
+			hlist_del(&s->hlist, &c->sta_table[i]);
+			cntlr_freeze_sta(c, s);
+			cntlr_free_sta(s);
+		}
+	}
+
+	c->num_sta = 0;
+}
+
 static void cntlr_clean_nodelist(struct controller *c)
 {
 	struct node *n = NULL, *tmp;
@@ -1720,34 +1788,6 @@ static void cntlr_event_handler(struct ubus_context *ctx,
 	free(str);
 }
 
-//FIXME: move to history
-static void cntlr_remove_stale_sta(struct controller *c)
-{
-	struct node *n = NULL;
-	time_t curr_time;
-
-	if (!c)
-		return;
-
-	time(&curr_time);
-	list_for_each_entry(n, &c->nodelist, list) {
-		struct sta *s = NULL, *tmp;
-
-		list_for_each_entry_safe(s, tmp, &n->stalist, list) {
-			if (s->state != STA_ASSOCIATED && s->disassoc_time != 0) {
-				if ((curr_time - s->disassoc_time) >= c->cfg.stale_sta_timeout) {
-					cntlr_dbg(LOG_STA,
-						  "%s: Delete STA " MACFMT " after %ds of disassociation\n",
-						  __func__, MAC2STR(s->macaddr),
-						  c->cfg.stale_sta_timeout);
-
-					cntlr_remove_sta(c, n, s);
-				}
-			}
-		}
-	}
-}
-
 static void cntlr_periodic_run(atimer_t *t)
 {
 	struct controller *c = container_of(t, struct controller, heartbeat);
@@ -1774,7 +1814,6 @@ static void cntlr_periodic_run(atimer_t *t)
 	}
 #endif
 	cntlr_ageout_nodes(c);
-	cntlr_remove_stale_sta(c);
 	timer_set(&c->heartbeat, 1 * 1000);
 }
 
@@ -1925,6 +1964,7 @@ out_exit:
 	cntlr_clean_bcnreqlist(c);
 	cntlr_clean_linklist(c);
 	cntlr_clean_nodelist(c);
+	cntlr_clean_all_sta(c);
 	free_topology(&c->topology);
 	ubus_unregister_event_handler(ctx, &c->evh);
 	cntlr_remove_object(c);
diff --git a/src/cntlr.h b/src/cntlr.h
index 3667a16c..d36b5d6c 100644
--- a/src/cntlr.h
+++ b/src/cntlr.h
@@ -366,6 +366,7 @@ struct node *cntlr_add_node(struct controller *c, uint8_t *almacaddr);
 struct node *cntlr_alloc_node(struct controller *c, uint8_t *almacaddr);
 struct node *cntlr_find_node(struct controller *c, uint8_t *almacaddr);
 struct node *cntlr_find_node_with_bssid(struct controller *c, uint8_t *bssid);
+struct node *cntlr_find_node_with_sta(struct controller *c, uint8_t *stamacaddr);
 
 struct netif_link *cntlr_alloc_link(struct controller *c,
 		uint8_t *upstream, uint8_t *downstream);
@@ -412,8 +413,8 @@ int cntlr_sync_dyn_controller_config(struct controller *c, uint8_t *agent);
 void cntrl_send_max_wifi_bh_hops_policy(struct controller *c);
 
 
-void node_add_sta(struct node *n, struct sta *s);
-void node_del_sta(struct node *n, struct sta *s);
+int node_add_sta(struct node *n, struct sta *s);
+int node_del_sta(struct node *n, struct sta *s);
 struct sta *node_find_sta(struct node *n, uint8_t *sta_macaddr);
 void node_update_stalist(struct node *n, uint8_t *stalist, int num);
 
diff --git a/src/cntlr_map.c b/src/cntlr_map.c
index ad7beaee..e98bf2eb 100644
--- a/src/cntlr_map.c
+++ b/src/cntlr_map.c
@@ -228,25 +228,24 @@ int handle_topology_notification(void *cntlr, struct cmdu_buff *cmdu,
 			return 0;
 		}
 
-		bsta_iface = cntlr_find_iface_type(c, ev->macaddr, MAC_ENTRY_BSTA);
-
 		s = cntlr_find_sta(c->sta_table, ev->macaddr);
-		if (associated) {
-			struct node *old_n = NULL;
-
-			if (!s) {
+		if (s) {
+			if (associated) {
 				struct cmdu_buff *txcmdu;
 
-				s = cntlr_add_sta(c, c->sta_table, ev->macaddr);
-				if (!s) {
-					cntlr_dbg(LOG_STA, "%s: failed to add STA " MACFMT "\n",
-						  __func__, MAC2STR(ev->macaddr));
-					return -1;
-				}
-
 				time(&s->assoc_time);
-				s->state = STA_ASSOCIATED;
 				memcpy(s->bssid, ev->bssid, 6);
+				s->state = STA_ASSOCIATED;
+
+				if (memcmp(s->agent_almacaddr, n->almacaddr, 6)) {
+					/* associated to a new node - remove from the old one */
+					struct node *old_n = NULL;
+
+					old_n = cntlr_find_node(c, s->agent_almacaddr);
+					if (old_n)
+						node_del_sta(old_n, s);
+				}
+
 				node_add_sta(n, s);
 
 				txcmdu = cntlr_gen_client_caps_query(c, c->almacaddr, s->macaddr, s->bssid);
@@ -254,26 +253,31 @@ int handle_topology_notification(void *cntlr, struct cmdu_buff *cmdu,
 					send_cmdu(c, txcmdu);
 					cmdu_free(txcmdu);
 				}
-
-				goto inform_steer_plugins;
-			}
-
-			/* remove sta from old-node and add to new-node */
-			old_n = cntlr_find_node(c, s->agent_almacaddr);
-			if (old_n) {
-				if (node_find_sta(old_n, s->macaddr)) {
+			} else {
+				if (!memcmp(s->agent_almacaddr, n->almacaddr, 6)) {
+					/* disassociated from current node */
 					time(&s->disassoc_time);
+					memset(s->bssid, 0, sizeof(s->bssid));
 					s->state = STA_DISCONNECTED;
-					node_del_sta(old_n, s);
 				}
-			}
 
-			if (!node_find_sta(n, ev->macaddr)) {
+				node_del_sta(n, s);
+			}
+		} else { /* unknown sta */
+			if (associated) {
 				struct cmdu_buff *txcmdu;
 
+				s = cntlr_add_sta(c, c->sta_table, ev->macaddr);
+				if (!s) {
+					cntlr_dbg(LOG_STA, "%s: failed to add STA " MACFMT "\n",
+						  __func__, MAC2STR(ev->macaddr));
+					return -1;
+				}
+
 				time(&s->assoc_time);
-				s->state = STA_ASSOCIATED;
 				memcpy(s->bssid, ev->bssid, 6);
+				s->state = STA_ASSOCIATED;
+
 				node_add_sta(n, s);
 
 				txcmdu = cntlr_gen_client_caps_query(c, c->almacaddr, s->macaddr, s->bssid);
@@ -281,34 +285,27 @@ int handle_topology_notification(void *cntlr, struct cmdu_buff *cmdu,
 					send_cmdu(c, txcmdu);
 					cmdu_free(txcmdu);
 				}
-			}
-		} else {
-			if (!s) {
+			} else {
 				cntlr_dbg(LOG_STA,
 					  "Ignore unknown STA " MACFMT " disassoc event\n",
 					  MAC2STR(ev->macaddr));
 				return 0;
 			}
-
-			if (node_find_sta(n, ev->macaddr)) {
-				time(&s->disassoc_time);
-				s->state = STA_DISCONNECTED;
-				node_del_sta(n, s);
-			}
 		}
 
+		bsta_iface = cntlr_find_iface_type(c, ev->macaddr, MAC_ENTRY_BSTA);
 		s->is_bsta = bsta_iface || bh ? true : false;
 
 		cntlr_info(LOG_STA, "%s: STA " MACFMT " %s Node " MACFMT"\n",
 			   __func__, MAC2STR(s->macaddr),
 			   s->state == STA_ASSOCIATED ? "associated to" : "disconnected from",
 			   MAC2STR(s->agent_almacaddr));
-	}
 
-inform_steer_plugins:
-	if (s) {
 		cntlr_update_sta_steer_data(c, s);
+	}
 
+	if (s) {
+		/* Inform steering plugins */
 		c->inform_cmdu_type = CMDU_TYPE_TOPOLOGY_NOTIFICATION;
 		c->inform_sta_num = 1;
 		memset(c->inform_stalist, 0, sizeof(c->inform_stalist));
@@ -571,7 +568,7 @@ int handle_topology_response(void *cntlr, struct cmdu_buff *cmdu, struct node *n
 		uint8_t *tv_data = (uint8_t *)tlv;
 		int i, offset = 0;
 		uint8_t stas[256 * 6];
-		int max_stas = sizeof(stas) / sizeof(stas[0]);
+		int max_stas = sizeof(stas) / 6;
 		memset(stas, 0, sizeof(stas));
 		int idx = 0;
 
diff --git a/src/sta.c b/src/sta.c
index 9782d552..2ef03adb 100644
--- a/src/sta.c
+++ b/src/sta.c
@@ -15,6 +15,7 @@
 #include <easy/easy.h>
 #include <wifidefs.h>
 
+#include "cntlr.h"
 #include "steer_module.h"
 #include "timer.h"
 #include "utils/debug.h"
@@ -23,6 +24,7 @@
 
 extern void cntlr_bcn_metrics_timer_cb(atimer_t *t);
 extern void cntlr_btm_req_timer_cb(atimer_t *t);
+extern void cntlr_sta_ageout_timer_cb(atimer_t *t);
 
 struct sta *cntlr_find_sta(struct hlist_head *table, uint8_t *macaddr)
 {
@@ -53,6 +55,7 @@ static struct sta *sta_alloc(uint8_t *macaddr)
 	memcpy(s->macaddr, macaddr, 6);
 	timer_init(&s->bcn_metrics_timer, cntlr_bcn_metrics_timer_cb);
 	timer_init(&s->btm_req_timer, cntlr_btm_req_timer_cb);
+	timer_init(&s->ageout_timer, cntlr_sta_ageout_timer_cb);
 	time(&s->lookup_time);
 
 	s->de_sta = (struct wifi_sta_element *)(s + 1);
@@ -101,9 +104,9 @@ struct sta *cntlr_add_sta(void *cntlr, struct hlist_head *table, uint8_t *macadd
 
 		if (least_used_sta) {
 			cntlr_dbg(LOG_STA, "%s: remove least used STA " MACFMT
-				  " to add new STA " MACFMT "\n", __func__,
-				  MAC2STR(least_used_sta->de_sta->macaddr),
-				  MAC2STR(macaddr));
+					" to add new STA " MACFMT "\n", __func__,
+					MAC2STR(least_used_sta->de_sta->macaddr),
+					MAC2STR(macaddr));
 
 			node_remove_sta(c, n, least_used_sta);
 		} else {
@@ -112,6 +115,10 @@ struct sta *cntlr_add_sta(void *cntlr, struct hlist_head *table, uint8_t *macadd
 		}
 	}
 #endif
+	if (WARN_ON(hwaddr_is_zero(macaddr))) {
+		/* should never happen */
+		return NULL;
+	}
 
 	s = sta_alloc(macaddr);
 	if (!s)
@@ -125,24 +132,39 @@ struct sta *cntlr_add_sta(void *cntlr, struct hlist_head *table, uint8_t *macadd
 	return s;
 }
 
-void cntlr_del_sta(struct hlist_head *table, uint8_t *macaddr)
+void cntlr_del_sta_hash(struct hlist_head *table, uint8_t *macaddr)
 {
+	int idx;
 	struct hlist_node *tmp = NULL;
 	struct sta *s = NULL;
-	int idx;
+	bool found = false;
 
 	idx = sta_hash(macaddr);
-	if (hlist_empty(&table[idx]))
-		return;
-
-	hlist_for_each_entry_safe(s, tmp, &table[idx], hlist) {
-		if (!memcmp(s->macaddr, macaddr, 6)) {
-			hlist_del(&s->hlist, &table[idx]);
-			s->cntlr = NULL;
-			sta_free(s);
-			return;
+	if (!hlist_empty(&table[idx])) {
+		hlist_for_each_entry_safe(s, tmp, &table[idx], hlist) {
+			if (!memcmp(s->macaddr, macaddr, 6)) {
+				hlist_del(&s->hlist, &table[idx]);
+				found = true;
+			}
 		}
 	}
+
+	if (!found) {
+		cntlr_warn(LOG_STA, "%s: STA " MACFMT " not found in table\n",
+				  __func__, MAC2STR(macaddr));
+	}
+}
+
+void cntlr_free_sta(struct sta *del)
+{
+	del->cntlr = NULL;
+	sta_free(del);
+}
+
+void cntlr_del_sta(struct hlist_head *table, struct sta *del)
+{
+	cntlr_del_sta_hash(table, del->macaddr);
+	cntlr_free_sta(del);
 }
 
 int sta_link_metrics_process(struct sta *s)
@@ -191,3 +213,27 @@ void sta_free_assoc_frame(struct sta *s)
 	free(s->de_sta->reassoc_frame);
 	s->de_sta->reassoc_framelen = 0;
 }
+
+int sta_inc_ref(struct sta *s)
+{
+	s->nref++;
+
+	if (timer_pending(&s->ageout_timer))
+		timer_del(&s->ageout_timer);
+
+	return s->nref;
+}
+
+int sta_dec_ref(struct sta *s)
+{
+	struct controller *c = s->cntlr;
+
+	if (WARN_ON(s->nref == 0))
+		return 0;
+
+	s->nref--;
+	if (s->nref == 0)
+		timer_set(&s->ageout_timer, c->cfg.stale_sta_timeout * 1000);
+
+	return s->nref;
+}
diff --git a/src/sta.h b/src/sta.h
index 244691f1..29b6271b 100644
--- a/src/sta.h
+++ b/src/sta.h
@@ -48,6 +48,8 @@ struct sta {
 	time_t assoc_time;
 	time_t disassoc_time;
 	uint16_t disassoc_reason;
+	atimer_t ageout_timer;
+	uint8_t nref;	/* num of nodes STA is associated to */
 
 	struct wifi_sta_element *de_sta;
 
@@ -73,10 +75,15 @@ struct sta {
 
 struct sta *cntlr_find_sta(struct hlist_head *table, uint8_t *macaddr);
 struct sta *cntlr_add_sta(void *cntlr, struct hlist_head *table, uint8_t *macaddr);
-void cntlr_del_sta(struct hlist_head *table, uint8_t *macaddr);
+void cntlr_free_sta(struct sta *s);
+void cntlr_del_sta_hash(struct hlist_head *table, uint8_t *macaddr);
+void cntlr_del_sta(struct hlist_head *table, struct sta *del);
 
 int sta_link_metrics_process(struct sta *s);
 void sta_free_bcn_metrics(struct sta *s);
 void sta_free_usta_metrics(struct sta *s);
 void sta_free_assoc_frame(struct sta *s);
+
+int sta_inc_ref(struct sta *s);
+int sta_dec_ref(struct sta *s);
 #endif /* STA_H */
-- 
GitLab