]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
batman-adv: stop tp_meter sessions during mesh teardown
authorJiexun Wang <wangjiexun2025@gmail.com>
Mon, 27 Apr 2026 06:43:34 +0000 (14:43 +0800)
committerSven Eckelmann <sven@narfation.org>
Sat, 2 May 2026 19:58:53 +0000 (21:58 +0200)
TP meter sessions remain linked on bat_priv->tp_list after the netlink
request has already finished. When the mesh interface is removed,
batadv_mesh_free() currently tears down the mesh without first draining
these sessions.

A running sender thread or a late incoming tp_meter packet can then keep
processing against a mesh instance which is already shutting down.
Synchronize tp_meter with the mesh lifetime by stopping all active
sessions from batadv_mesh_free() and waiting for sender threads to exit
before teardown continues.

Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
Cc: stable@kernel.org
Reported-by: Yuan Tan <yuantan098@gmail.com>
Reported-by: Yifan Wu <yifanwucs@gmail.com>
Reported-by: Juefei Pu <tomapufckgml@gmail.com>
Reported-by: Xin Liu <bird@lzu.edu.cn>
Co-developed-by: Luxing Yin <tr0jan@lzu.edu.cn>
Signed-off-by: Luxing Yin <tr0jan@lzu.edu.cn>
Signed-off-by: Jiexun Wang <wangjiexun2025@gmail.com>
Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
Signed-off-by: Sven Eckelmann <sven@narfation.org>
net/batman-adv/main.c
net/batman-adv/tp_meter.c
net/batman-adv/tp_meter.h
net/batman-adv/types.h

index 3a35aadd8b41913fbe1e16ada1203cf3b22fbb32..a4d33ee0fda59e22fd8c00a58d4278b53b6d7517 100644 (file)
@@ -249,6 +249,7 @@ void batadv_mesh_free(struct net_device *mesh_iface)
        atomic_set(&bat_priv->mesh_state, BATADV_MESH_DEACTIVATING);
 
        batadv_purge_outstanding_packets(bat_priv, NULL);
+       batadv_tp_stop_all(bat_priv);
 
        batadv_gw_node_free(bat_priv);
 
index d9a80e459c2e4a70cc61864b13f332ed7246e013..58ca59a2799ed1c0085c532d8a26f3c078b1281f 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/byteorder/generic.h>
 #include <linux/cache.h>
 #include <linux/compiler.h>
+#include <linux/completion.h>
 #include <linux/container_of.h>
 #include <linux/err.h>
 #include <linux/etherdevice.h>
@@ -365,23 +366,38 @@ static void batadv_tp_vars_put(struct batadv_tp_vars *tp_vars)
 }
 
 /**
- * batadv_tp_sender_cleanup() - cleanup sender data and drop and timer
- * @bat_priv: the bat priv with all the mesh interface information
- * @tp_vars: the private data of the current TP meter session to cleanup
+ * batadv_tp_list_detach() - remove tp session from mesh session list once
+ * @tp_vars: the private data of the current TP meter session
  */
-static void batadv_tp_sender_cleanup(struct batadv_priv *bat_priv,
-                                    struct batadv_tp_vars *tp_vars)
+static void batadv_tp_list_detach(struct batadv_tp_vars *tp_vars)
 {
-       cancel_delayed_work(&tp_vars->finish_work);
+       bool detached = false;
 
        spin_lock_bh(&tp_vars->bat_priv->tp_list_lock);
-       hlist_del_rcu(&tp_vars->list);
+       if (!hlist_unhashed(&tp_vars->list)) {
+               hlist_del_init_rcu(&tp_vars->list);
+               detached = true;
+       }
        spin_unlock_bh(&tp_vars->bat_priv->tp_list_lock);
 
+       if (!detached)
+               return;
+
+       atomic_dec(&tp_vars->bat_priv->tp_num);
+
        /* drop list reference */
        batadv_tp_vars_put(tp_vars);
+}
 
-       atomic_dec(&tp_vars->bat_priv->tp_num);
+/**
+ * batadv_tp_sender_cleanup() - cleanup sender data and drop and timer
+ * @tp_vars: the private data of the current TP meter session to cleanup
+ */
+static void batadv_tp_sender_cleanup(struct batadv_tp_vars *tp_vars)
+{
+       cancel_delayed_work_sync(&tp_vars->finish_work);
+
+       batadv_tp_list_detach(tp_vars);
 
        /* kill the timer and remove its reference */
        timer_delete_sync(&tp_vars->timer);
@@ -886,7 +902,8 @@ out:
        batadv_orig_node_put(orig_node);
 
        batadv_tp_sender_end(bat_priv, tp_vars);
-       batadv_tp_sender_cleanup(bat_priv, tp_vars);
+       batadv_tp_sender_cleanup(tp_vars);
+       complete(&tp_vars->finished);
 
        batadv_tp_vars_put(tp_vars);
 
@@ -918,7 +935,8 @@ static void batadv_tp_start_kthread(struct batadv_tp_vars *tp_vars)
                batadv_tp_vars_put(tp_vars);
 
                /* cleanup of failed tp meter variables */
-               batadv_tp_sender_cleanup(bat_priv, tp_vars);
+               batadv_tp_sender_cleanup(tp_vars);
+               complete(&tp_vars->finished);
                return;
        }
 
@@ -1024,6 +1042,7 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
        tp_vars->start_time = jiffies;
 
        init_waitqueue_head(&tp_vars->more_bytes);
+       init_completion(&tp_vars->finished);
 
        spin_lock_init(&tp_vars->unacked_lock);
        INIT_LIST_HEAD(&tp_vars->unacked_list);
@@ -1126,14 +1145,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
                   "Shutting down for inactivity (more than %dms) from %pM\n",
                   BATADV_TP_RECV_TIMEOUT, tp_vars->other_end);
 
-       spin_lock_bh(&tp_vars->bat_priv->tp_list_lock);
-       hlist_del_rcu(&tp_vars->list);
-       spin_unlock_bh(&tp_vars->bat_priv->tp_list_lock);
-
-       /* drop list reference */
-       batadv_tp_vars_put(tp_vars);
-
-       atomic_dec(&bat_priv->tp_num);
+       batadv_tp_list_detach(tp_vars);
 
        spin_lock_bh(&tp_vars->unacked_lock);
        list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
@@ -1496,6 +1508,52 @@ out:
        consume_skb(skb);
 }
 
+/**
+ * batadv_tp_stop_all() - stop all currently running tp meter sessions
+ * @bat_priv: the bat priv with all the mesh interface information
+ */
+void batadv_tp_stop_all(struct batadv_priv *bat_priv)
+{
+       struct batadv_tp_vars *tp_vars[BATADV_TP_MAX_NUM];
+       struct batadv_tp_vars *tp_var;
+       size_t count = 0;
+       size_t i;
+
+       spin_lock_bh(&bat_priv->tp_list_lock);
+       hlist_for_each_entry(tp_var, &bat_priv->tp_list, list) {
+               if (WARN_ON_ONCE(count >= BATADV_TP_MAX_NUM))
+                       break;
+
+               if (!kref_get_unless_zero(&tp_var->refcount))
+                       continue;
+
+               tp_vars[count++] = tp_var;
+       }
+       spin_unlock_bh(&bat_priv->tp_list_lock);
+
+       for (i = 0; i < count; i++) {
+               tp_var = tp_vars[i];
+
+               switch (tp_var->role) {
+               case BATADV_TP_SENDER:
+                       batadv_tp_sender_shutdown(tp_var,
+                                                 BATADV_TP_REASON_CANCEL);
+                       wake_up(&tp_var->more_bytes);
+                       wait_for_completion(&tp_var->finished);
+                       break;
+               case BATADV_TP_RECEIVER:
+                       batadv_tp_list_detach(tp_var);
+                       if (timer_shutdown_sync(&tp_var->timer))
+                               batadv_tp_vars_put(tp_var);
+                       break;
+               }
+
+               batadv_tp_vars_put(tp_var);
+       }
+
+       synchronize_net();
+}
+
 /**
  * batadv_tp_meter_init() - initialize global tp_meter structures
  */
index f0046d366eac6507d34892b9b4e52bd50cb24a87..4e97cd10cd025966b24659a532493ae1edb4da28 100644 (file)
@@ -17,6 +17,7 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
                     u32 test_length, u32 *cookie);
 void batadv_tp_stop(struct batadv_priv *bat_priv, const u8 *dst,
                    u8 return_value);
+void batadv_tp_stop_all(struct batadv_priv *bat_priv);
 void batadv_tp_meter_recv(struct batadv_priv *bat_priv, struct sk_buff *skb);
 
 #endif /* _NET_BATMAN_ADV_TP_METER_H_ */
index 8fc5fe0e9b05398c9e1af6417453f4a09bc74e14..daa06f421154290b5bed4068c225fe4f167a7b32 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/average.h>
 #include <linux/bitops.h>
 #include <linux/compiler.h>
+#include <linux/completion.h>
 #include <linux/if.h>
 #include <linux/if_ether.h>
 #include <linux/kref.h>
@@ -1328,6 +1329,9 @@ struct batadv_tp_vars {
        /** @finish_work: work item for the finishing procedure */
        struct delayed_work finish_work;
 
+       /** @finished: completion signaled when a sender thread exits */
+       struct completion finished;
+
        /** @test_length: test length in milliseconds */
        u32 test_length;