[PATCH batadv 4/4] batman-adv: tt: fix roaming state handling of a local client

Sven Eckelmann <[email protected]> Thu, 30 Jul 2026 19:32:59 +0200
Newsgroups org.open-mesh.lists.batman
Message-ID <[email protected]>
Turning the BATADV_TT_CLIENT_ROAM handling in batadv_tt_local_add() into an
atomic single claim changed the behaviour of the roaming check in three
ways.

* the cleanup of the global entry was moved out of the block which is
  only entered when the roaming process was not handled yet.
* the flag is now published before tt_global->roam_at is stamped,
  with the whole batadv_send_roam_adv() loop in between.

Revert this handling but avoid sending the initial roaming-away
announcements when another context took responsibility for it. To keep it
more readable, extract it in a new function.

Reported-by: Sashiko <[email protected]>
Fixes: 8b4cee3c9355 ("batman-adv: tt: use atomic flag modifications")
Signed-off-by: Sven Eckelmann <[email protected]>
---
 net/batman-adv/translation-table.c | 94 +++++++++++++++++++++++---------------
 1 file changed, 58 insertions(+), 36 deletions(-)

diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
index 7907f1c6..b2e15dd1 100644
--- a/net/batman-adv/translation-table.c
+++ b/net/batman-adv/translation-table.c
@@ -667,6 +667,63 @@ static void batadv_tt_global_free(struct batadv_priv *bat_priv,
 	batadv_tt_global_entry_put(tt_global);
 }
 
+/**
+ * batadv_tt_local_add_roam() - handle roamed clients during batadv_tt_local_add()
+ * @bat_priv: the bat priv with all the mesh interface information
+ * @tt_global: the global TT entry
+ * @roamed_back: whether @tt_global roamed back
+ */
+static void batadv_tt_local_add_roam(struct batadv_priv *bat_priv,
+				     struct batadv_tt_global_entry *tt_global,
+				     bool roamed_back)
+{
+	struct batadv_tt_orig_list_entry *orig_entry;
+	struct hlist_head *head;
+	u16 old_flags;
+
+	if (!tt_global)
+		return;
+
+	/* Check whether it is a roaming, but don't do anything if the roaming
+	 * process has already been handled
+	 */
+	if (atomic_read(&tt_global->common.flags) & BATADV_TT_CLIENT_ROAM)
+		return;
+
+	if (!roamed_back) {
+		/* The global entry has to be marked as ROAMING and has to be
+		 * kept for consistency purpose.
+		 *
+		 * batadv_tt_global_to_purge() evaluates roam_at as soon as it
+		 * observes BATADV_TT_CLIENT_ROAM, so the timeout has to be
+		 * stamped before the flag is published. Otherwise the entry can
+		 * be deleted right away as "Roaming timeout".
+		 */
+		tt_global->roam_at = jiffies;
+
+		/* The flag is claimed via an atomic test-and-set to make sure
+		 * that only a single context announces the roaming
+		 */
+		old_flags = atomic_fetch_or(BATADV_TT_CLIENT_ROAM,
+					    &tt_global->common.flags);
+		if (old_flags & BATADV_TT_CLIENT_ROAM)
+			return;
+	}
+
+	/* These node are probably going to update their tt table */
+	head = &tt_global->orig_list;
+	rcu_read_lock();
+	hlist_for_each_entry_rcu(orig_entry, head, list) {
+		batadv_send_roam_adv(bat_priv, tt_global->common.addr,
+				     tt_global->common.vid,
+				     orig_entry->orig_node);
+	}
+	rcu_read_unlock();
+
+	if (roamed_back)
+		batadv_tt_global_free(bat_priv, tt_global, "Roaming canceled");
+}
+
 /**
  * batadv_tt_local_add() - add a new client to the local table or update an
  *  existing client
@@ -685,14 +742,12 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr,
 {
 	struct batadv_priv *bat_priv = netdev_priv(mesh_iface);
 	struct batadv_tt_global_entry *tt_global = NULL;
-	struct batadv_tt_orig_list_entry *orig_entry;
 	struct batadv_tt_local_entry *tt_local;
 	struct net *net = dev_net(mesh_iface);
 	struct net_device *in_dev = NULL;
 	struct batadv_meshif_vlan *vlan;
 	bool roamed_back = false;
 	bool iif_is_wifi = false;
-	struct hlist_head *head;
 	int packet_size_max;
 	u8 new_remote_flags;
 	bool ret = false;
@@ -701,7 +756,6 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr,
 	int table_size;
 	u32 match_mark;
 	u8 clear_flags;
-	u16 old_flags;
 	u8 set_flags;
 	u16 flags;
 
@@ -821,39 +875,7 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr,
 	batadv_tt_local_event(bat_priv, tt_local, BATADV_NO_FLAGS);
 
 check_roaming:
-	/* Check whether it is a roaming, but don't do anything if the roaming
-	 * process has already been handled. The ROAM flag is claimed via an
-	 * atomic test-and-set to make sure that only a single context handles
-	 * the advertisement for a client
-	 */
-	old_flags = BATADV_TT_CLIENT_ROAM;
-	if (tt_global &&
-	    !(atomic_read(&tt_global->common.flags) & BATADV_TT_CLIENT_ROAM))
-		old_flags = atomic_fetch_or(BATADV_TT_CLIENT_ROAM,
-					    &tt_global->common.flags);
-
-	if (tt_global && !(old_flags & BATADV_TT_CLIENT_ROAM)) {
-		/* These node are probably going to update their tt table */
-		head = &tt_global->orig_list;
-		rcu_read_lock();
-		hlist_for_each_entry_rcu(orig_entry, head, list) {
-			batadv_send_roam_adv(bat_priv, tt_global->common.addr,
-					     tt_global->common.vid,
-					     orig_entry->orig_node);
-		}
-		rcu_read_unlock();
-
-		if (!roamed_back) {
-			/* The global entry was marked as ROAMING and
-			 * has to be kept for consistency purpose
-			 */
-			tt_global->roam_at = jiffies;
-		}
-	}
-
-	/* clean up independent of the roaming advertisement handler */
-	if (tt_global && roamed_back)
-		batadv_tt_global_free(bat_priv, tt_global, "Roaming canceled");
+	batadv_tt_local_add_roam(bat_priv, tt_global, roamed_back);
 
 	/* store the current remote flags before altering them. This helps
 	 * understanding is flags are changing or not

-- 
2.47.3