From fcba102e0e4a3d39316ca9286b5619f161da4baa Mon Sep 17 00:00:00 2001 From: Nora Schiffer Date: Sun, 28 Jun 2026 17:07:28 +0200 Subject: [PATCH 01/15] batman-adv: create hardif only for netdevs that are part of a mesh batman-adv is using netdev notifiers to create a hard_iface struct for every Ethernet-like netdev in the system. These hardifs are tracked in a global linked list, which results in a few performance issues: Lookups in this list are O(n) in the total number of netdevs. As a hardif is looked up when a netdev is removed, this also takes O(n) in the number of netdevs, and removing n netdevs may take O(n^2). This slowdown will always happen when the batman-adv module is loaded, no mesh needs to be active. With the hardif being referenced as iflink private data, the global list is only needed for hardifs that are *not* part of a mesh (that is, the hardif is unused). To prepare for removing the global list, only create a hardif struct when an interface is added to a mesh and destroy it on removal. As adding/removing and enabling/disabling a hardif become one and the same, batadv_hardif_add_interface() is merged into batadv_hardif_enable_interface(), and batadv_hardif_remove_interface() can be dropped altogether. Signed-off-by: Nora Schiffer Signed-off-by: Sven Eckelmann --- net/batman-adv/hard-interface.c | 120 +++++++++++--------------------- net/batman-adv/hard-interface.h | 2 +- net/batman-adv/mesh-interface.c | 13 +--- 3 files changed, 44 insertions(+), 91 deletions(-) diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 03d01c20a954..9c9a892d22c6 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -723,33 +723,58 @@ batadv_hardif_deactivate_interface(struct batadv_hard_iface *hard_iface) } /** - * batadv_hardif_enable_interface() - Enslave hard interface to mesh interface - * @hard_iface: hard interface to add to mesh interface + * batadv_hardif_enable_interface() - Enslave interface to mesh interface + * @net_dev: netdev struct of the interface to add to mesh interface * @mesh_iface: netdev struct of the mesh interface * * Return: 0 on success or negative error number in case of failure */ -int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, +int batadv_hardif_enable_interface(struct net_device *net_dev, struct net_device *mesh_iface) { struct batadv_priv *bat_priv; __be16 ethertype = htons(ETH_P_BATMAN); int max_header_len = batadv_max_header_len(); + struct batadv_hard_iface *hard_iface; unsigned int required_mtu; unsigned int hardif_mtu; bool fragmentation; int ret; - hardif_mtu = READ_ONCE(hard_iface->net_dev->mtu); + ASSERT_RTNL(); + + if (!batadv_is_valid_iface(net_dev)) + return -EINVAL; + + hardif_mtu = READ_ONCE(net_dev->mtu); required_mtu = READ_ONCE(mesh_iface->mtu) + max_header_len; if (hardif_mtu < ETH_MIN_MTU + max_header_len) return -EINVAL; - if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) - goto out; + hard_iface = kzalloc_obj(*hard_iface, GFP_ATOMIC); + if (!hard_iface) + return -ENOMEM; - kref_get(&hard_iface->refcount); + netdev_hold(net_dev, &hard_iface->dev_tracker, GFP_ATOMIC); + hard_iface->net_dev = net_dev; + + hard_iface->if_status = BATADV_IF_INACTIVE; + + INIT_LIST_HEAD(&hard_iface->list); + INIT_HLIST_HEAD(&hard_iface->neigh_list); + + mutex_init(&hard_iface->bat_iv.ogm_buff_mutex); + spin_lock_init(&hard_iface->neigh_list_lock); + kref_init(&hard_iface->refcount); + + hard_iface->num_bcasts = BATADV_NUM_BCASTS_DEFAULT; + if (batadv_is_wifi_hardif(hard_iface)) + hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS; + + WRITE_ONCE(hard_iface->hop_penalty, 0); + + batadv_v_hardif_init(hard_iface); netdev_hold(mesh_iface, &hard_iface->meshif_dev_tracker, GFP_ATOMIC); hard_iface->mesh_iface = mesh_iface; @@ -764,9 +789,6 @@ int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, if (ret < 0) goto err_upper; - hard_iface->if_status = BATADV_IF_INACTIVE; - - kref_get(&hard_iface->refcount); hard_iface->batman_adv_ptype.type = ethertype; hard_iface->batman_adv_ptype.func = batadv_batman_skb_recv; hard_iface->batman_adv_ptype.dev = hard_iface->net_dev; @@ -802,7 +824,9 @@ int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, if (bat_priv->algo_ops->iface.enabled) bat_priv->algo_ops->iface.enabled(hard_iface); -out: + list_add_tail_rcu(&hard_iface->list, &batadv_hardif_list); + batadv_hardif_generation++; + return 0; err_upper: @@ -823,15 +847,19 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); struct batadv_hard_iface *primary_if = NULL; + ASSERT_RTNL(); + batadv_hardif_deactivate_interface(hard_iface); if (hard_iface->if_status != BATADV_IF_INACTIVE) goto out; + list_del_rcu(&hard_iface->list); + batadv_hardif_generation++; + batadv_info(hard_iface->mesh_iface, "Removing interface: %s\n", hard_iface->net_dev->name); dev_remove_pack(&hard_iface->batman_adv_ptype); - batadv_hardif_put(hard_iface); primary_if = batadv_primary_if_get_selected(bat_priv); if (hard_iface == primary_if) { @@ -844,7 +872,7 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) } bat_priv->algo_ops->iface.disable(hard_iface); - hard_iface->if_status = BATADV_IF_NOT_IN_USE; + hard_iface->if_status = BATADV_IF_TO_BE_REMOVED; /* delete all references to this hard_iface */ batadv_purge_orig_ref(bat_priv); @@ -865,63 +893,6 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) batadv_hardif_put(primary_if); } -static struct batadv_hard_iface * -batadv_hardif_add_interface(struct net_device *net_dev) -{ - struct batadv_hard_iface *hard_iface; - - ASSERT_RTNL(); - - if (!batadv_is_valid_iface(net_dev)) - return NULL; - - hard_iface = kzalloc_obj(*hard_iface, GFP_ATOMIC); - if (!hard_iface) - return NULL; - - netdev_hold(net_dev, &hard_iface->dev_tracker, GFP_ATOMIC); - hard_iface->net_dev = net_dev; - - hard_iface->mesh_iface = NULL; - hard_iface->if_status = BATADV_IF_NOT_IN_USE; - - INIT_LIST_HEAD(&hard_iface->list); - INIT_HLIST_HEAD(&hard_iface->neigh_list); - - mutex_init(&hard_iface->bat_iv.ogm_buff_mutex); - spin_lock_init(&hard_iface->neigh_list_lock); - kref_init(&hard_iface->refcount); - - hard_iface->num_bcasts = BATADV_NUM_BCASTS_DEFAULT; - if (batadv_is_wifi_hardif(hard_iface)) - hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS; - - WRITE_ONCE(hard_iface->hop_penalty, 0); - - batadv_v_hardif_init(hard_iface); - - kref_get(&hard_iface->refcount); - list_add_tail_rcu(&hard_iface->list, &batadv_hardif_list); - batadv_hardif_generation++; - - return hard_iface; -} - -static void batadv_hardif_remove_interface(struct batadv_hard_iface *hard_iface) -{ - ASSERT_RTNL(); - - /* first deactivate interface */ - if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) - batadv_hardif_disable_interface(hard_iface); - - if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) - return; - - hard_iface->if_status = BATADV_IF_TO_BE_REMOVED; - batadv_hardif_put(hard_iface); -} - /** * batadv_hard_if_event_meshif() - Handle events for mesh interfaces * @event: NETDEV_* event to handle @@ -1082,10 +1053,6 @@ static int batadv_hard_if_event(struct notifier_block *this, batadv_wifi_net_device_event(event, net_dev); hard_iface = batadv_hardif_get_by_netdev(net_dev); - if (!hard_iface && (event == NETDEV_REGISTER || - event == NETDEV_POST_TYPE_CHANGE)) - hard_iface = batadv_hardif_add_interface(net_dev); - if (!hard_iface) goto out; @@ -1099,10 +1066,7 @@ static int batadv_hard_if_event(struct notifier_block *this, break; case NETDEV_UNREGISTER: case NETDEV_PRE_TYPE_CHANGE: - list_del_rcu(&hard_iface->list); - batadv_hardif_generation++; - - batadv_hardif_remove_interface(hard_iface); + batadv_hardif_disable_interface(hard_iface); break; case NETDEV_CHANGEMTU: if (hard_iface->mesh_iface) diff --git a/net/batman-adv/hard-interface.h b/net/batman-adv/hard-interface.h index af31696c3978..6d72dbdd5c20 100644 --- a/net/batman-adv/hard-interface.h +++ b/net/batman-adv/hard-interface.h @@ -75,7 +75,7 @@ u32 batadv_hardif_get_wifi_flags(struct batadv_hard_iface *hard_iface); bool batadv_is_wifi_hardif(struct batadv_hard_iface *hard_iface); struct batadv_hard_iface* batadv_hardif_get_by_netdev(const struct net_device *net_dev); -int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, +int batadv_hardif_enable_interface(struct net_device *net_dev, struct net_device *mesh_iface); void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface); int batadv_hardif_min_mtu(struct net_device *mesh_iface); diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index 44026810b99c..a37368c1f5b5 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -836,18 +836,7 @@ static int batadv_meshif_slave_add(struct net_device *dev, struct net_device *slave_dev, struct netlink_ext_ack *extack) { - struct batadv_hard_iface *hard_iface; - int ret = -EINVAL; - - hard_iface = batadv_hardif_get_by_netdev(slave_dev); - if (!hard_iface || hard_iface->mesh_iface) - goto out; - - ret = batadv_hardif_enable_interface(hard_iface, dev); - -out: - batadv_hardif_put(hard_iface); - return ret; + return batadv_hardif_enable_interface(slave_dev, dev); } /** From 49e9de6e124fe80da68dfe9e7fe4e0b1ddfc4b35 Mon Sep 17 00:00:00 2001 From: Nora Schiffer Date: Sun, 28 Jun 2026 17:07:29 +0200 Subject: [PATCH 02/15] batman-adv: remove global hardif list With interfaces being kept track of as iflink private data, there is no need for the global list anymore. batadv_hardif_get_by_netdev() can now use netdev_master_upper_dev_get()+netdev_lower_dev_get_private() to find the hardif corresponding to a netdev. Signed-off-by: Nora Schiffer Signed-off-by: Sven Eckelmann --- net/batman-adv/hard-interface.c | 29 ++++++++++++----------------- net/batman-adv/hard-interface.h | 2 +- net/batman-adv/main.c | 5 ----- net/batman-adv/main.h | 1 - net/batman-adv/netlink.c | 2 ++ net/batman-adv/types.h | 3 --- 6 files changed, 15 insertions(+), 27 deletions(-) diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 9c9a892d22c6..ace81348ddef 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -75,21 +75,21 @@ void batadv_hardif_release(struct kref *ref) * Return: batadv_hard_iface of net_dev (with increased refcnt), NULL on errors */ struct batadv_hard_iface * -batadv_hardif_get_by_netdev(const struct net_device *net_dev) +batadv_hardif_get_by_netdev(struct net_device *net_dev) { struct batadv_hard_iface *hard_iface; + struct net_device *mesh_iface; - rcu_read_lock(); - list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) { - if (hard_iface->net_dev == net_dev && - kref_get_unless_zero(&hard_iface->refcount)) - goto out; - } + ASSERT_RTNL(); - hard_iface = NULL; + mesh_iface = netdev_master_upper_dev_get(net_dev); + if (!mesh_iface || !batadv_meshif_is_valid(mesh_iface)) + return NULL; + + hard_iface = netdev_lower_dev_get_private(mesh_iface, net_dev); + if (!kref_get_unless_zero(&hard_iface->refcount)) + return NULL; -out: - rcu_read_unlock(); return hard_iface; } @@ -761,7 +761,6 @@ int batadv_hardif_enable_interface(struct net_device *net_dev, hard_iface->if_status = BATADV_IF_INACTIVE; - INIT_LIST_HEAD(&hard_iface->list); INIT_HLIST_HEAD(&hard_iface->neigh_list); mutex_init(&hard_iface->bat_iv.ogm_buff_mutex); @@ -780,6 +779,7 @@ int batadv_hardif_enable_interface(struct net_device *net_dev, hard_iface->mesh_iface = mesh_iface; bat_priv = netdev_priv(hard_iface->mesh_iface); + batadv_hardif_generation++; ret = netdev_master_upper_dev_link(hard_iface->net_dev, mesh_iface, hard_iface, NULL, NULL); if (ret) @@ -824,9 +824,6 @@ int batadv_hardif_enable_interface(struct net_device *net_dev, if (bat_priv->algo_ops->iface.enabled) bat_priv->algo_ops->iface.enabled(hard_iface); - list_add_tail_rcu(&hard_iface->list, &batadv_hardif_list); - batadv_hardif_generation++; - return 0; err_upper: @@ -854,9 +851,6 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) if (hard_iface->if_status != BATADV_IF_INACTIVE) goto out; - list_del_rcu(&hard_iface->list); - batadv_hardif_generation++; - batadv_info(hard_iface->mesh_iface, "Removing interface: %s\n", hard_iface->net_dev->name); dev_remove_pack(&hard_iface->batman_adv_ptype); @@ -879,6 +873,7 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) batadv_purge_outstanding_packets(bat_priv, hard_iface); netdev_put(hard_iface->mesh_iface, &hard_iface->meshif_dev_tracker); + batadv_hardif_generation++; netdev_upper_dev_unlink(hard_iface->net_dev, hard_iface->mesh_iface); batadv_hardif_recalc_extra_skbroom(hard_iface->mesh_iface); diff --git a/net/batman-adv/hard-interface.h b/net/batman-adv/hard-interface.h index 6d72dbdd5c20..aa9275dec097 100644 --- a/net/batman-adv/hard-interface.h +++ b/net/batman-adv/hard-interface.h @@ -74,7 +74,7 @@ u32 batadv_netdev_get_wifi_flags(struct net_device *net_dev); u32 batadv_hardif_get_wifi_flags(struct batadv_hard_iface *hard_iface); bool batadv_is_wifi_hardif(struct batadv_hard_iface *hard_iface); struct batadv_hard_iface* -batadv_hardif_get_by_netdev(const struct net_device *net_dev); +batadv_hardif_get_by_netdev(struct net_device *net_dev); int batadv_hardif_enable_interface(struct net_device *net_dev, struct net_device *mesh_iface); void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface); diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index 3c4572284b53..1d82f3a841a1 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -59,10 +59,6 @@ #include "tp_meter.h" #include "translation-table.h" -/* List manipulations on hardif_list have to be rtnl_lock()'ed, - * list traversals just rcu-locked - */ -struct list_head batadv_hardif_list; unsigned int batadv_hardif_generation; static int (*batadv_rx_handler[256])(struct sk_buff *skb, struct batadv_hard_iface *recv_if); @@ -95,7 +91,6 @@ static int __init batadv_init(void) if (ret < 0) return ret; - INIT_LIST_HEAD(&batadv_hardif_list); batadv_algo_init(); batadv_recv_handler_init(); diff --git a/net/batman-adv/main.h b/net/batman-adv/main.h index f68fc8b7239c..e34145047a34 100644 --- a/net/batman-adv/main.h +++ b/net/batman-adv/main.h @@ -226,7 +226,6 @@ static inline int batadv_print_vid(unsigned short vid) return -1; } -extern struct list_head batadv_hardif_list; extern unsigned int batadv_hardif_generation; extern struct workqueue_struct *batadv_event_workqueue; diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c index 4cf9e3c54ad3..62ea91aa3ead 100644 --- a/net/batman-adv/netlink.c +++ b/net/batman-adv/netlink.c @@ -1211,7 +1211,9 @@ batadv_netlink_get_hardif_from_ifindex(struct batadv_priv *bat_priv, if (!hard_dev) return ERR_PTR(-ENODEV); + rtnl_lock(); hard_iface = batadv_hardif_get_by_netdev(hard_dev); + rtnl_unlock(); if (!hard_iface) goto err_put_harddev; diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index b1f9f8964c3f..1671380b3792 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -214,9 +214,6 @@ struct batadv_wifi_net_device_state { * struct batadv_hard_iface - network device known to batman-adv */ struct batadv_hard_iface { - /** @list: list node for batadv_hardif_list */ - struct list_head list; - /** @if_status: status of the interface for batman-adv */ char if_status; From f846e779532ea99dea34005c1cfbb4820b582d1e Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 28 Jun 2026 17:07:30 +0200 Subject: [PATCH 03/15] batman-adv: make hard_iface->mesh_iface immutable With the hard_iface now being created for a specific mesh_iface, it is beneficial not to set mesh_iface to NULL when the interface is disabled, but instead keeping it immutable after the initial setup of the hard_iface. By also holding the reference to the mesh_iface until the hard_iface is released, hard_ifaces iterated over under RCU will always point to a valid mesh_iface. Co-developed-by: Nora Schiffer Signed-off-by: Nora Schiffer Signed-off-by: Sven Eckelmann --- net/batman-adv/hard-interface.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index ace81348ddef..a0b8b06f9a64 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -63,6 +63,7 @@ void batadv_hardif_release(struct kref *ref) struct batadv_hard_iface *hard_iface; hard_iface = container_of(ref, struct batadv_hard_iface, refcount); + netdev_put(hard_iface->mesh_iface, &hard_iface->meshif_dev_tracker); netdev_put(hard_iface->net_dev, &hard_iface->dev_tracker); kfree_rcu(hard_iface, rcu); @@ -829,8 +830,6 @@ int batadv_hardif_enable_interface(struct net_device *net_dev, err_upper: netdev_upper_dev_unlink(hard_iface->net_dev, mesh_iface); err_dev: - hard_iface->mesh_iface = NULL; - netdev_put(mesh_iface, &hard_iface->meshif_dev_tracker); batadv_hardif_put(hard_iface); return ret; } @@ -871,7 +870,6 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) /* delete all references to this hard_iface */ batadv_purge_orig_ref(bat_priv); batadv_purge_outstanding_packets(bat_priv, hard_iface); - netdev_put(hard_iface->mesh_iface, &hard_iface->meshif_dev_tracker); batadv_hardif_generation++; netdev_upper_dev_unlink(hard_iface->net_dev, hard_iface->mesh_iface); @@ -881,7 +879,6 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) if (list_empty(&hard_iface->mesh_iface->adj_list.lower)) batadv_gw_check_client_stop(bat_priv); - hard_iface->mesh_iface = NULL; batadv_hardif_put(hard_iface); out: From 48ea7b6b7b9474d8c350b1180f49a210d972f99c Mon Sep 17 00:00:00 2001 From: Nora Schiffer Date: Sun, 28 Jun 2026 17:07:31 +0200 Subject: [PATCH 04/15] batman-adv: remove BATADV_IF_NOT_IN_USE hardif state With hardifs only existing while an interface is part of a mesh, the BATADV_IF_NOT_IN_USE state has become redundant. Signed-off-by: Nora Schiffer Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_iv_ogm.c | 3 +-- net/batman-adv/bat_v_elp.c | 3 +-- net/batman-adv/hard-interface.c | 9 --------- net/batman-adv/hard-interface.h | 6 ------ net/batman-adv/originator.c | 4 ---- 5 files changed, 2 insertions(+), 23 deletions(-) diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c index bb2f012b454e..4514c51bba77 100644 --- a/net/batman-adv/bat_iv_ogm.c +++ b/net/batman-adv/bat_iv_ogm.c @@ -910,8 +910,7 @@ static void batadv_iv_ogm_schedule_buff(struct batadv_hard_iface *hard_iface) static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface) { - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE || - hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) + if (hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) return; mutex_lock(&hard_iface->bat_iv.ogm_buff_mutex); diff --git a/net/batman-adv/bat_v_elp.c b/net/batman-adv/bat_v_elp.c index 4841f0f1a9b1..bc3e4f264afa 100644 --- a/net/batman-adv/bat_v_elp.c +++ b/net/batman-adv/bat_v_elp.c @@ -311,8 +311,7 @@ static void batadv_v_elp_periodic_work(struct work_struct *work) goto out; /* we are in the process of shutting this interface down */ - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE || - hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) + if (hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) goto out; /* the interface was enabled but may not be ready yet */ diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index a0b8b06f9a64..86010bc32818 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -547,9 +547,6 @@ static void batadv_check_known_mac_addr(const struct batadv_hard_iface *hard_ifa if (tmp_hard_iface == hard_iface) continue; - if (tmp_hard_iface->if_status == BATADV_IF_NOT_IN_USE) - continue; - if (!batadv_compare_eth(tmp_hard_iface->net_dev->dev_addr, hard_iface->net_dev->dev_addr)) continue; @@ -575,9 +572,6 @@ static void batadv_hardif_recalc_extra_skbroom(struct net_device *mesh_iface) rcu_read_lock(); netdev_for_each_lower_private_rcu(mesh_iface, hard_iface, iter) { - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE) - continue; - lower_header_len = max_t(unsigned short, lower_header_len, hard_iface->net_dev->hard_header_len); @@ -1065,9 +1059,6 @@ static int batadv_hard_if_event(struct notifier_block *this, batadv_update_min_mtu(hard_iface->mesh_iface); break; case NETDEV_CHANGEADDR: - if (hard_iface->if_status == BATADV_IF_NOT_IN_USE) - goto hardif_put; - batadv_check_known_mac_addr(hard_iface); bat_priv = netdev_priv(hard_iface->mesh_iface); diff --git a/net/batman-adv/hard-interface.h b/net/batman-adv/hard-interface.h index aa9275dec097..935f47ca9a48 100644 --- a/net/batman-adv/hard-interface.h +++ b/net/batman-adv/hard-interface.h @@ -21,12 +21,6 @@ * enum batadv_hard_if_state - State of a hard interface */ enum batadv_hard_if_state { - /** - * @BATADV_IF_NOT_IN_USE: interface is not used as slave interface of a - * batman-adv mesh interface - */ - BATADV_IF_NOT_IN_USE, - /** * @BATADV_IF_TO_BE_REMOVED: interface will be removed from mesh * interface diff --git a/net/batman-adv/originator.c b/net/batman-adv/originator.c index 9b38bd9e8da7..48f837cf665a 100644 --- a/net/batman-adv/originator.c +++ b/net/batman-adv/originator.c @@ -1033,7 +1033,6 @@ batadv_purge_neigh_ifinfo(struct batadv_priv *bat_priv, /* don't purge if the interface is not (going) down */ if (if_outgoing->if_status != BATADV_IF_INACTIVE && - if_outgoing->if_status != BATADV_IF_NOT_IN_USE && if_outgoing->if_status != BATADV_IF_TO_BE_REMOVED) continue; @@ -1077,7 +1076,6 @@ batadv_purge_orig_ifinfo(struct batadv_priv *bat_priv, /* don't purge if the interface is not (going) down */ if (if_outgoing->if_status != BATADV_IF_INACTIVE && - if_outgoing->if_status != BATADV_IF_NOT_IN_USE && if_outgoing->if_status != BATADV_IF_TO_BE_REMOVED) continue; @@ -1127,10 +1125,8 @@ batadv_purge_orig_neighbors(struct batadv_priv *bat_priv, if (batadv_has_timed_out(last_seen, BATADV_PURGE_TIMEOUT) || if_incoming->if_status == BATADV_IF_INACTIVE || - if_incoming->if_status == BATADV_IF_NOT_IN_USE || if_incoming->if_status == BATADV_IF_TO_BE_REMOVED) { if (if_incoming->if_status == BATADV_IF_INACTIVE || - if_incoming->if_status == BATADV_IF_NOT_IN_USE || if_incoming->if_status == BATADV_IF_TO_BE_REMOVED) batadv_dbg(BATADV_DBG_BATMAN, bat_priv, "neighbor purge: originator %pM, neighbor: %pM, iface: %s\n", From cbda6a6cf2a5abe0dda94819f4e1aaba40678913 Mon Sep 17 00:00:00 2001 From: Nora Schiffer Date: Sun, 28 Jun 2026 17:07:32 +0200 Subject: [PATCH 05/15] batman-adv: move hardif generation counter into batadv_priv The counter doesn't need to be global. Signed-off-by: Nora Schiffer Signed-off-by: Sven Eckelmann --- net/batman-adv/hard-interface.c | 4 ++-- net/batman-adv/main.c | 1 - net/batman-adv/main.h | 2 -- net/batman-adv/netlink.c | 2 +- net/batman-adv/types.h | 3 +++ 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 86010bc32818..9b8108d464db 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -774,7 +774,7 @@ int batadv_hardif_enable_interface(struct net_device *net_dev, hard_iface->mesh_iface = mesh_iface; bat_priv = netdev_priv(hard_iface->mesh_iface); - batadv_hardif_generation++; + bat_priv->hardif_generation++; ret = netdev_master_upper_dev_link(hard_iface->net_dev, mesh_iface, hard_iface, NULL, NULL); if (ret) @@ -865,7 +865,7 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) batadv_purge_orig_ref(bat_priv); batadv_purge_outstanding_packets(bat_priv, hard_iface); - batadv_hardif_generation++; + bat_priv->hardif_generation++; netdev_upper_dev_unlink(hard_iface->net_dev, hard_iface->mesh_iface); batadv_hardif_recalc_extra_skbroom(hard_iface->mesh_iface); diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index 1d82f3a841a1..badc1df0af1d 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -59,7 +59,6 @@ #include "tp_meter.h" #include "translation-table.h" -unsigned int batadv_hardif_generation; static int (*batadv_rx_handler[256])(struct sk_buff *skb, struct batadv_hard_iface *recv_if); diff --git a/net/batman-adv/main.h b/net/batman-adv/main.h index e34145047a34..e738758ee4a7 100644 --- a/net/batman-adv/main.h +++ b/net/batman-adv/main.h @@ -226,8 +226,6 @@ static inline int batadv_print_vid(unsigned short vid) return -1; } -extern unsigned int batadv_hardif_generation; - extern struct workqueue_struct *batadv_event_workqueue; int batadv_mesh_init(struct net_device *mesh_iface); diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c index 62ea91aa3ead..d2bc48c70714 100644 --- a/net/batman-adv/netlink.c +++ b/net/batman-adv/netlink.c @@ -968,7 +968,7 @@ batadv_netlink_dump_hardif(struct sk_buff *msg, struct netlink_callback *cb) bat_priv = netdev_priv(mesh_iface); rtnl_lock(); - cb->seq = batadv_hardif_generation << 1 | 1; + cb->seq = bat_priv->hardif_generation << 1 | 1; netdev_for_each_lower_private(mesh_iface, hard_iface, iter) { if (i++ < skip) diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index 1671380b3792..e1463a029e83 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -1676,6 +1676,9 @@ struct batadv_priv { /** @tp_num: number of currently active tp sessions */ atomic_t tp_num; + /** @hardif_generation: generation counter added to netlink hardif dumps */ + unsigned int hardif_generation; + /** @orig_work: work queue callback item for orig node purging */ struct delayed_work orig_work; From 2b429dbc50b43db1d85429d5bab0498bc4906736 Mon Sep 17 00:00:00 2001 From: Nora Schiffer Date: Sun, 28 Jun 2026 17:07:33 +0200 Subject: [PATCH 06/15] batman-adv: drop unneeded goto and initialization from batadv_hardif_disable_interface() The only use of the label was too early for primary_if to be set anyways. Also move the put of primary_if further up to hold the reference only as long as necessary, hopefully avoiding the need to re-introduce the goto label with future code changes. Signed-off-by: Nora Schiffer Signed-off-by: Sven Eckelmann --- net/batman-adv/hard-interface.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 9b8108d464db..6fc49ad47fd8 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -835,14 +835,14 @@ int batadv_hardif_enable_interface(struct net_device *net_dev, void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) { struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); - struct batadv_hard_iface *primary_if = NULL; + struct batadv_hard_iface *primary_if; ASSERT_RTNL(); batadv_hardif_deactivate_interface(hard_iface); if (hard_iface->if_status != BATADV_IF_INACTIVE) - goto out; + return; batadv_info(hard_iface->mesh_iface, "Removing interface: %s\n", hard_iface->net_dev->name); @@ -857,6 +857,7 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) batadv_hardif_put(new_if); } + batadv_hardif_put(primary_if); bat_priv->algo_ops->iface.disable(hard_iface); hard_iface->if_status = BATADV_IF_TO_BE_REMOVED; @@ -874,9 +875,6 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) batadv_gw_check_client_stop(bat_priv); batadv_hardif_put(hard_iface); - -out: - batadv_hardif_put(primary_if); } /** From b58c36b804f4447655a9eb353bf3a9d310f50e06 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 4 Jun 2026 08:30:07 +0200 Subject: [PATCH 07/15] batman-adv: drop NULL check for immutable hardif->mesh_iface The batadv_hard_iface->mesh_iface became immutable after the global batadv_hardif_list was removed and batadv_hard_iface only exists when it is assigned to an mesh_iface. This member can never become NULL and thus a check is now unnecessary. Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_v_elp.c | 6 ------ net/batman-adv/bridge_loop_avoidance.c | 9 ++------- net/batman-adv/hard-interface.c | 8 ++------ net/batman-adv/main.c | 3 --- 4 files changed, 4 insertions(+), 22 deletions(-) diff --git a/net/batman-adv/bat_v_elp.c b/net/batman-adv/bat_v_elp.c index bc3e4f264afa..262e40040007 100644 --- a/net/batman-adv/bat_v_elp.c +++ b/net/batman-adv/bat_v_elp.c @@ -90,12 +90,6 @@ static bool batadv_v_elp_get_throughput(struct batadv_hardif_neigh_node *neigh, u32 throughput; int ret; - /* don't query throughput when no longer associated with any - * batman-adv interface - */ - if (!mesh_iface) - return false; - /* if the user specified a customised value for this interface, then * return it directly */ diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c index 5c73f6ba16cf..f9a1fadf8de9 100644 --- a/net/batman-adv/bridge_loop_avoidance.c +++ b/net/batman-adv/bridge_loop_avoidance.c @@ -344,7 +344,6 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac, struct sk_buff *skb; struct ethhdr *ethhdr; struct batadv_hard_iface *primary_if; - struct net_device *mesh_iface; u8 *hw_src; struct batadv_bla_claim_dst local_claim_dest; __be32 zeroip = 0; @@ -357,14 +356,10 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac, sizeof(local_claim_dest)); local_claim_dest.type = claimtype; - mesh_iface = READ_ONCE(primary_if->mesh_iface); - if (!mesh_iface) - goto out; - skb = arp_create(ARPOP_REPLY, ETH_P_ARP, /* IP DST: 0.0.0.0 */ zeroip, - mesh_iface, + primary_if->mesh_iface, /* IP SRC: 0.0.0.0 */ zeroip, /* Ethernet DST: Broadcast */ @@ -442,7 +437,7 @@ static void batadv_bla_send_claim(struct batadv_priv *bat_priv, const u8 *mac, } skb_reset_mac_header(skb); - skb->protocol = eth_type_trans(skb, mesh_iface); + skb->protocol = eth_type_trans(skb, primary_if->mesh_iface); batadv_inc_counter(bat_priv, BATADV_CNT_RX); batadv_add_counter(bat_priv, BATADV_CNT_RX_BYTES, skb->len + ETH_HLEN); diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 6fc49ad47fd8..b6867576bbaf 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -246,7 +246,7 @@ struct net_device *__batadv_get_real_netdev(struct net_device *netdev) } hard_iface = batadv_hardif_get_by_netdev(netdev); - if (!hard_iface || !hard_iface->mesh_iface) + if (!hard_iface) goto out; net = dev_net(hard_iface->mesh_iface); @@ -540,9 +540,6 @@ static void batadv_check_known_mac_addr(const struct batadv_hard_iface *hard_ifa const struct batadv_hard_iface *tmp_hard_iface; struct list_head *iter; - if (!mesh_iface) - return; - netdev_for_each_lower_private(mesh_iface, tmp_hard_iface, iter) { if (tmp_hard_iface == hard_iface) continue; @@ -1053,8 +1050,7 @@ static int batadv_hard_if_event(struct notifier_block *this, batadv_hardif_disable_interface(hard_iface); break; case NETDEV_CHANGEMTU: - if (hard_iface->mesh_iface) - batadv_update_min_mtu(hard_iface->mesh_iface); + batadv_update_min_mtu(hard_iface->mesh_iface); break; case NETDEV_CHANGEADDR: batadv_check_known_mac_addr(hard_iface); diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index badc1df0af1d..04bb030ef299 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -444,9 +444,6 @@ int batadv_batman_skb_recv(struct sk_buff *skb, struct net_device *dev, if (unlikely(skb->mac_len != ETH_HLEN || !skb_mac_header(skb))) goto err_free; - if (!hard_iface->mesh_iface) - goto err_free; - bat_priv = netdev_priv(hard_iface->mesh_iface); if (READ_ONCE(bat_priv->mesh_state) != BATADV_MESH_ACTIVE) From 5fa5f64fc0189bb37eeb4fe11687648c557c6f99 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 4 Jun 2026 08:45:30 +0200 Subject: [PATCH 08/15] Revert "batman-adv: v: stop OGMv2 on disabled interface" With the immutability guarantee of batadv_hard_iface->mesh_iface, the check for "changed" (or NULL) mesh_iface doesn't work anymore and is also no longer necessary. The extra (complicated) code for the sending of OGMv2s can therefore be removed and the original code can be used again. This reverts commit f8ce8b8331a1bc44ad4905886a482214d428b253. Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_v_ogm.c | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c index 037921aad35d..e921d49f7ece 100644 --- a/net/batman-adv/bat_v_ogm.c +++ b/net/batman-adv/bat_v_ogm.c @@ -115,14 +115,14 @@ static void batadv_v_ogm_start_timer(struct batadv_priv *bat_priv) /** * batadv_v_ogm_send_to_if() - send a batman ogm using a given interface - * @bat_priv: the bat priv with all the mesh interface information * @skb: the OGM to send * @hard_iface: the interface to use to send the OGM */ -static void batadv_v_ogm_send_to_if(struct batadv_priv *bat_priv, - struct sk_buff *skb, +static void batadv_v_ogm_send_to_if(struct sk_buff *skb, struct batadv_hard_iface *hard_iface) { + struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); + if (hard_iface->if_status != BATADV_IF_ACTIVE) { kfree_skb(skb); return; @@ -189,7 +189,6 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface) /** * batadv_v_ogm_aggr_send() - flush & send aggregation queue - * @bat_priv: the bat priv with all the mesh interface information * @hard_iface: the interface with the aggregation queue to flush * * Aggregates all OGMv2 packets currently in the aggregation queue into a @@ -199,8 +198,7 @@ static void batadv_v_ogm_aggr_list_free(struct batadv_hard_iface *hard_iface) * * Caller needs to hold the hard_iface->bat_v.aggr_list.lock. */ -static void batadv_v_ogm_aggr_send(struct batadv_priv *bat_priv, - struct batadv_hard_iface *hard_iface) +static void batadv_v_ogm_aggr_send(struct batadv_hard_iface *hard_iface) { unsigned int aggr_len = hard_iface->bat_v.aggr_len; struct sk_buff *skb_aggr; @@ -230,26 +228,21 @@ static void batadv_v_ogm_aggr_send(struct batadv_priv *bat_priv, consume_skb(skb); } - batadv_v_ogm_send_to_if(bat_priv, skb_aggr, hard_iface); + batadv_v_ogm_send_to_if(skb_aggr, hard_iface); } /** * batadv_v_ogm_queue_on_if() - queue a batman ogm on a given interface - * @bat_priv: the bat priv with all the mesh interface information * @skb: the OGM to queue * @hard_iface: the interface to queue the OGM on */ -static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv, - struct sk_buff *skb, +static void batadv_v_ogm_queue_on_if(struct sk_buff *skb, struct batadv_hard_iface *hard_iface) { - if (hard_iface->mesh_iface != bat_priv->mesh_iface) { - kfree_skb(skb); - return; - } + struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); if (!READ_ONCE(bat_priv->aggregated_ogms)) { - batadv_v_ogm_send_to_if(bat_priv, skb, hard_iface); + batadv_v_ogm_send_to_if(skb, hard_iface); return; } @@ -260,7 +253,7 @@ static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv, } if (!batadv_v_ogm_queue_left(skb, hard_iface)) - batadv_v_ogm_aggr_send(bat_priv, hard_iface); + batadv_v_ogm_aggr_send(hard_iface); hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb); __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb); @@ -357,7 +350,7 @@ static void batadv_v_ogm_send_meshif(struct batadv_priv *bat_priv) break; } - batadv_v_ogm_queue_on_if(bat_priv, skb_tmp, hard_iface); + batadv_v_ogm_queue_on_if(skb_tmp, hard_iface); batadv_hardif_put(hard_iface); } rcu_read_unlock(); @@ -397,14 +390,12 @@ void batadv_v_ogm_aggr_work(struct work_struct *work) { struct batadv_hard_iface_bat_v *batv; struct batadv_hard_iface *hard_iface; - struct batadv_priv *bat_priv; batv = container_of(work, struct batadv_hard_iface_bat_v, aggr_wq.work); hard_iface = container_of(batv, struct batadv_hard_iface, bat_v); - bat_priv = netdev_priv(hard_iface->mesh_iface); spin_lock_bh(&hard_iface->bat_v.aggr_list.lock); - batadv_v_ogm_aggr_send(bat_priv, hard_iface); + batadv_v_ogm_aggr_send(hard_iface); spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock); batadv_v_ogm_start_queue_timer(hard_iface); @@ -601,7 +592,7 @@ static void batadv_v_ogm_forward(struct batadv_priv *bat_priv, if_outgoing->net_dev->name, ntohl(ogm_forward->throughput), ogm_forward->ttl, if_incoming->net_dev->name); - batadv_v_ogm_queue_on_if(bat_priv, skb, if_outgoing); + batadv_v_ogm_queue_on_if(skb, if_outgoing); out: batadv_orig_ifinfo_put(orig_ifinfo); From ba2d97e8736ef2752ac717bc21a111b63b7e6496 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 4 Jun 2026 08:51:30 +0200 Subject: [PATCH 09/15] batman-adv: iv: drop migration check for batadv_hard_iface With the immutability guarantee of batadv_hard_iface->mesh_iface, the check for "changed" (or NULL) mesh_iface is no longer necessary because a batadv_hard_iface can no longer migrate from one batadv_mesh_iface to another one. Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_iv_ogm.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c index 4514c51bba77..22622283f59b 100644 --- a/net/batman-adv/bat_iv_ogm.c +++ b/net/batman-adv/bat_iv_ogm.c @@ -404,23 +404,14 @@ static void batadv_iv_ogm_send_to_if(struct batadv_forw_packet *forw_packet, /* send a batman ogm packet */ static void batadv_iv_ogm_emit(struct batadv_forw_packet *forw_packet) { - struct net_device *mesh_iface; - if (!forw_packet->if_incoming) { pr_err("Error - can't forward packet: incoming iface not specified\n"); return; } - mesh_iface = forw_packet->if_incoming->mesh_iface; - if (WARN_ON(!forw_packet->if_outgoing)) return; - if (forw_packet->if_outgoing->mesh_iface != mesh_iface) { - pr_warn("%s: mesh interface switch for queued OGM\n", __func__); - return; - } - if (forw_packet->if_incoming->if_status != BATADV_IF_ACTIVE) return; From 278e5890704ceb5b93f4d13c44a640c44ed35a92 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 14 Jun 2026 11:55:27 +0200 Subject: [PATCH 10/15] batman-adv: tvlv: extract tvlv header iterator batadv_tvlv_containers_contain() and batadv_tvlv_containers_process() are using the same code to iterate through the TVLV containers. To simplify the code, extract the shared portions of both functions. Signed-off-by: Sven Eckelmann --- net/batman-adv/tvlv.c | 86 +++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c index 1c9fb21985f6..49bf2ed9ecdc 100644 --- a/net/batman-adv/tvlv.c +++ b/net/batman-adv/tvlv.c @@ -442,6 +442,54 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv, return NET_RX_SUCCESS; } +/** + * batadv_tvlv_hdr_next() - move a tvlv buffer cursor to the next container + * @tvlv_value: cursor into the tvlv buffer, advanced past the returned + * container's content on success + * @tvlv_value_len: remaining length of the tvlv buffer, reduced by the returned + * container's size on success + * + * Parses a single container header at the current cursor position and, if a + * complete container is available, advances the cursor and remaining length + * past it. The returned header stays valid; its content is located at + * (returned header + 1) and is ntohs(hdr->len) bytes long. + * + * Return: pointer to the next tvlv container header, or NULL if no further + * complete container is present in the buffer. + */ +static struct batadv_tvlv_hdr *batadv_tvlv_hdr_next(void **tvlv_value, u16 *tvlv_value_len) +{ + struct batadv_tvlv_hdr *tvlv_hdr; + u16 tvlv_value_cont_len; + void *tvlv_value_cont; + u16 tvlv_len; + + tvlv_value_cont = *tvlv_value; + tvlv_len = *tvlv_value_len; + + if (tvlv_len < sizeof(*tvlv_hdr)) + return NULL; + + tvlv_hdr = tvlv_value_cont; + tvlv_value_cont_len = ntohs(tvlv_hdr->len); + tvlv_value_cont = tvlv_hdr + 1; + tvlv_len -= sizeof(*tvlv_hdr); + + if (tvlv_value_cont_len > tvlv_len) + return NULL; + + /* the next tvlv header is accessed assuming (at least) 2-byte + * alignment, so it must start at an even offset. + */ + if (tvlv_value_cont_len & 1) + return NULL; + + *tvlv_value = (u8 *)tvlv_value_cont + tvlv_value_cont_len; + *tvlv_value_len = tvlv_len - tvlv_value_cont_len; + + return tvlv_hdr; +} + /** * batadv_tvlv_containers_contain() - check if a tvlv buffer holds a container * @tvlv_value: tvlv content @@ -457,28 +505,10 @@ static bool batadv_tvlv_containers_contain(void *tvlv_value, u8 version) { struct batadv_tvlv_hdr *tvlv_hdr; - u16 tvlv_value_cont_len; - - while (tvlv_value_len >= sizeof(*tvlv_hdr)) { - tvlv_hdr = tvlv_value; - tvlv_value_cont_len = ntohs(tvlv_hdr->len); - tvlv_value = tvlv_hdr + 1; - tvlv_value_len -= sizeof(*tvlv_hdr); - - if (tvlv_value_cont_len > tvlv_value_len) - break; - - /* the next tvlv header is accessed assuming (at least) 2-byte - * alignment, so it must start at an even offset. - */ - if (tvlv_value_cont_len & 1) - break; + while ((tvlv_hdr = batadv_tvlv_hdr_next(&tvlv_value, &tvlv_value_len))) { if (tvlv_hdr->type == type && tvlv_hdr->version == version) return true; - - tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len; - tvlv_value_len -= tvlv_value_cont_len; } return false; @@ -511,20 +541,8 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv, u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND; int ret = NET_RX_SUCCESS; - while (tvlv_value_len >= sizeof(*tvlv_hdr)) { - tvlv_hdr = tvlv_value; + while ((tvlv_hdr = batadv_tvlv_hdr_next(&tvlv_value, &tvlv_value_len))) { tvlv_value_cont_len = ntohs(tvlv_hdr->len); - tvlv_value = tvlv_hdr + 1; - tvlv_value_len -= sizeof(*tvlv_hdr); - - if (tvlv_value_cont_len > tvlv_value_len) - break; - - /* the next tvlv header is accessed assuming (at least) 2-byte - * alignment, so it must start at an even offset. - */ - if (tvlv_value_cont_len & 1) - break; tvlv_handler = batadv_tvlv_handler_get(bat_priv, tvlv_hdr->type, @@ -532,11 +550,9 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv, ret |= batadv_tvlv_call_handler(bat_priv, tvlv_handler, packet_type, orig_node, skb, - tvlv_value, + tvlv_hdr + 1, tvlv_value_cont_len); batadv_tvlv_handler_put(tvlv_handler); - tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len; - tvlv_value_len -= tvlv_value_cont_len; } if (packet_type != BATADV_IV_OGM && From 71b7987ec001a60dff689e56f4b041cfc4222c6b Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 10 Jun 2026 23:16:18 +0200 Subject: [PATCH 11/15] batman-adv: tp_meter: simplify unordered ack calculation When batadv_tp_ack_unordered() goes through the list of unacked sequence numbers and checks for now closed gaps, it is first calculating a delta of the sequence numbers which could be acked. Just to revert this calculation in the next steps to the sequence number which would be ackable. Skip the delta step and directly work with the sequence numbers. Signed-off-by: Sven Eckelmann --- net/batman-adv/tp_meter.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index c2eea7dbc488..b7fee6e55f03 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -1493,10 +1493,10 @@ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) if (batadv_seq_before(tp_vars->last_recv, un->seqno)) break; - to_ack = un->seqno + un->len - tp_vars->last_recv; + to_ack = un->seqno + un->len; - if (batadv_seq_before(tp_vars->last_recv, un->seqno + un->len)) - tp_vars->last_recv += to_ack; + if (batadv_seq_before(tp_vars->last_recv, to_ack)) + tp_vars->last_recv = to_ack; list_del(&un->list); kfree(un); From 936a1c10cecee351e10f9efc85532e9f7477f67c Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 10 Jun 2026 23:14:34 +0200 Subject: [PATCH 12/15] batman-adv: tp_meter: combine adjacent/overlapping unacked entries Right at the point when the receiver gets the first packet with a seqno gap (due to some packet loss/reordering), entries in the unacked list are created. They are (besides direct seqno matches) are not combined. A lot more then necessary entries are therefore created. Not for each gap but for each packet. This increases the memory consumption and management overhead. But it is trivial to handle overlapping or adjacent sequence number ranges during the insert. Only the handling of closed gaps by a new packets requires an extra step after the insert. Signed-off-by: Sven Eckelmann --- net/batman-adv/tp_meter.c | 65 ++++++++++++++++++++++++++++++++------- net/batman-adv/types.h | 2 +- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index b7fee6e55f03..5cc719c81ea0 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -1406,6 +1406,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, __must_hold(&tp_vars->common.unacked_lock) { struct batadv_tp_unacked *un, *new; + struct batadv_tp_unacked *safe; bool added = false; new = kmalloc_obj(*new, GFP_ATOMIC); @@ -1430,20 +1431,46 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, * seqno than all the others already stored. */ list_for_each_entry_reverse(un, &tp_vars->common.unacked_list, list) { - /* check for duplicates */ - if (new->seqno == un->seqno) { - if (new->len > un->len) - un->len = new->len; - kfree(new); - added = true; - break; - } - - /* look for the right position */ + /* look for the right position - an un which is smaller */ if (batadv_seq_before(new->seqno, un->seqno)) continue; - /* as soon as an entry having a bigger seqno is found, the new + /* smaller/equal seqno was found but they might be directly + * after another or overlapping. keep only a single entry + * + * It is already known that: + * + * un->seqno <= new->seqno + * + * When establishing that: + * + * new->seqno <= un->seqno + un->len + * + * Then it is not necessary to add a new entry because the + * smaller/equal seqno of un might already contain the new + * received packet or we only add new data directly after + * the end of un. The latter can be identified using: + * + * un->seqno + un->len <= new->seqno + new->len + */ + if (!batadv_seq_before(un->seqno + un->len, new->seqno)) { + /* new data directly after un? */ + if (!batadv_seq_before(new->seqno + new->len, + un->seqno + un->len)) + un->len = new->seqno + new->len - un->seqno; + + /* un now represents both old un + new */ + kfree(new); + added = true; + + /* un has to be used to check if the gap to the next + * seqno range was closed + */ + new = un; + break; + } + + /* as soon as an entry having a smaller seqno is found, the new * one is attached _after_ it. In this way the list is kept in * ascending order */ @@ -1459,6 +1486,22 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, tp_vars->common.unacked_count++; } + /* check if new filled the gap to the next list entries */ + un = new; + list_for_each_entry_safe_continue(un, safe, &tp_vars->common.unacked_list, list) { + if (batadv_seq_before(new->seqno + new->len, un->seqno)) + break; + + /* next entry is overlapping or adjacent - combine both */ + if (batadv_seq_before(new->seqno + new->len, + un->seqno + un->len)) + new->len = un->seqno + un->len - new->seqno; + + list_del(&un->list); + kfree(un); + tp_vars->common.unacked_count--; + } + /* remove the last (biggest) unacked seqno when list is too large */ if (tp_vars->common.unacked_count > BATADV_TP_MAX_UNACKED) { un = list_last_entry(&tp_vars->common.unacked_list, diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index e1463a029e83..c2ab00d8ef16 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -1332,7 +1332,7 @@ struct batadv_tp_unacked { u32 seqno; /** @len: length of the packet */ - u16 len; + u32 len; /** @list: list node for &batadv_tp_vars_common.unacked_list */ struct list_head list; From 3bbb8b94217ae27210935f1cb40306be7200da40 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Wed, 10 Jun 2026 21:05:37 +0200 Subject: [PATCH 13/15] batman-adv: tp_meter: keep unacked list for receivers There is no need to share the unacked list between sender and receivers. Only receivers will ever write to and read from it. The initialization in batadv_tp_start() was therefore never needed. After its removal, it is enough to just store it in struct batadv_tp_receiver. Signed-off-by: Sven Eckelmann --- net/batman-adv/tp_meter.c | 110 +++++++++++++++++++++----------------- net/batman-adv/types.h | 20 +++---- 2 files changed, 71 insertions(+), 59 deletions(-) diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index 5cc719c81ea0..f18ce360839d 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -358,28 +358,16 @@ batadv_tp_list_find_sender_session(struct batadv_priv *bat_priv, const u8 *dst, } /** - * batadv_tp_vars_common_release() - release batadv_tp_vars_common from lists + * batadv_tp_sender_release() - release batadv_tp_sender * and queue for free after rcu grace period - * @ref: kref pointer of the batadv_tp_vars_common + * @ref: kref pointer of the batadv_tp_sender */ -static void batadv_tp_vars_common_release(struct kref *ref) +static void batadv_tp_sender_release(struct kref *ref) { - struct batadv_tp_vars_common *tp_vars; - struct batadv_tp_unacked *un, *safe; + struct batadv_tp_sender *tp_vars; - tp_vars = container_of(ref, struct batadv_tp_vars_common, refcount); - - /* lock should not be needed because this object is now out of any - * context! - */ - spin_lock_bh(&tp_vars->unacked_lock); - list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { - list_del(&un->list); - kfree(un); - } - spin_unlock_bh(&tp_vars->unacked_lock); - - kfree_rcu(tp_vars, rcu); + tp_vars = container_of(ref, struct batadv_tp_sender, common.refcount); + kfree_rcu(tp_vars, common.rcu); } /** @@ -392,7 +380,7 @@ static void batadv_tp_sender_put(struct batadv_tp_sender *tp_vars) if (!tp_vars) return; - kref_put(&tp_vars->common.refcount, batadv_tp_vars_common_release); + kref_put(&tp_vars->common.refcount, batadv_tp_sender_release); } /** @@ -1145,9 +1133,6 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst, init_waitqueue_head(&tp_vars->more_bytes); init_completion(&tp_vars->finished); - spin_lock_init(&tp_vars->common.unacked_lock); - INIT_LIST_HEAD(&tp_vars->common.unacked_list); - spin_lock_init(&tp_vars->cc_lock); tp_vars->prerandom_offset = 0; @@ -1251,6 +1236,33 @@ batadv_tp_list_find_receiver_session(struct batadv_priv *bat_priv, const u8 *dst return tp_vars; } +/** + * batadv_tp_receiver_release() - release batadv_tp_receiver + * and queue for free after rcu grace period + * @ref: kref pointer of the batadv_tp_receiver + */ +static void batadv_tp_receiver_release(struct kref *ref) +{ + struct batadv_tp_receiver *tp_vars; + struct batadv_tp_unacked *safe; + struct batadv_tp_unacked *un; + + tp_vars = container_of(ref, struct batadv_tp_receiver, common.refcount); + + /* lock should not be needed because this object is now out of any + * context! + */ + spin_lock_bh(&tp_vars->unacked_lock); + list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { + list_del(&un->list); + kfree(un); + tp_vars->unacked_count--; + } + spin_unlock_bh(&tp_vars->unacked_lock); + + kfree_rcu(tp_vars, common.rcu); +} + /** * batadv_tp_receiver_put() - decrement the batadv_tp_receiver * refcounter and possibly release it @@ -1261,7 +1273,7 @@ static void batadv_tp_receiver_put(struct batadv_tp_receiver *tp_vars) if (!tp_vars) return; - kref_put(&tp_vars->common.refcount, batadv_tp_vars_common_release); + kref_put(&tp_vars->common.refcount, batadv_tp_receiver_release); } /** @@ -1304,13 +1316,13 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t) if (batadv_tp_list_detach(&tp_vars->common)) batadv_tp_receiver_put(tp_vars); - spin_lock_bh(&tp_vars->common.unacked_lock); - list_for_each_entry_safe(un, safe, &tp_vars->common.unacked_list, list) { + spin_lock_bh(&tp_vars->unacked_lock); + list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } - spin_unlock_bh(&tp_vars->common.unacked_lock); + spin_unlock_bh(&tp_vars->unacked_lock); /* drop reference of timer */ if (WARN_ON(atomic_xchg(&tp_vars->receiving, 0) != 1)) @@ -1403,7 +1415,7 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst, */ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, u32 seqno, u32 payload_len) - __must_hold(&tp_vars->common.unacked_lock) + __must_hold(&tp_vars->unacked_lock) { struct batadv_tp_unacked *un, *new; struct batadv_tp_unacked *safe; @@ -1417,9 +1429,9 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, new->len = payload_len; /* if the list is empty immediately attach this new object */ - if (list_empty(&tp_vars->common.unacked_list)) { - list_add(&new->list, &tp_vars->common.unacked_list); - tp_vars->common.unacked_count++; + if (list_empty(&tp_vars->unacked_list)) { + list_add(&new->list, &tp_vars->unacked_list); + tp_vars->unacked_count++; return true; } @@ -1430,7 +1442,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, * the last received packet (the one being processed now) has a bigger * seqno than all the others already stored. */ - list_for_each_entry_reverse(un, &tp_vars->common.unacked_list, list) { + list_for_each_entry_reverse(un, &tp_vars->unacked_list, list) { /* look for the right position - an un which is smaller */ if (batadv_seq_before(new->seqno, un->seqno)) continue; @@ -1476,19 +1488,19 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, */ list_add(&new->list, &un->list); added = true; - tp_vars->common.unacked_count++; + tp_vars->unacked_count++; break; } /* received packet with smallest seqno out of order; add it to front */ if (!added) { - list_add(&new->list, &tp_vars->common.unacked_list); - tp_vars->common.unacked_count++; + list_add(&new->list, &tp_vars->unacked_list); + tp_vars->unacked_count++; } /* check if new filled the gap to the next list entries */ un = new; - list_for_each_entry_safe_continue(un, safe, &tp_vars->common.unacked_list, list) { + list_for_each_entry_safe_continue(un, safe, &tp_vars->unacked_list, list) { if (batadv_seq_before(new->seqno + new->len, un->seqno)) break; @@ -1499,16 +1511,16 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } /* remove the last (biggest) unacked seqno when list is too large */ - if (tp_vars->common.unacked_count > BATADV_TP_MAX_UNACKED) { - un = list_last_entry(&tp_vars->common.unacked_list, + if (tp_vars->unacked_count > BATADV_TP_MAX_UNACKED) { + un = list_last_entry(&tp_vars->unacked_list, struct batadv_tp_unacked, list); list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } return true; @@ -1520,7 +1532,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, * @tp_vars: the private data of the current TP meter session */ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) - __must_hold(&tp_vars->common.unacked_lock) + __must_hold(&tp_vars->unacked_lock) { struct batadv_tp_unacked *un, *safe; u32 to_ack; @@ -1528,7 +1540,7 @@ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) /* go through the unacked packet list and possibly ACK them as * well */ - list_for_each_entry_safe(un, safe, &tp_vars->common.unacked_list, list) { + list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { /* the list is ordered, therefore it is possible to stop as soon * there is a gap between the last acked seqno and the seqno of * the packet under inspection @@ -1543,7 +1555,7 @@ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) list_del(&un->list); kfree(un); - tp_vars->common.unacked_count--; + tp_vars->unacked_count--; } } @@ -1590,9 +1602,9 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv, tp_vars->common.bat_priv = bat_priv; kref_init(&tp_vars->common.refcount); - spin_lock_init(&tp_vars->common.unacked_lock); - INIT_LIST_HEAD(&tp_vars->common.unacked_list); - tp_vars->common.unacked_count = 0; + spin_lock_init(&tp_vars->unacked_lock); + INIT_LIST_HEAD(&tp_vars->unacked_list); + tp_vars->unacked_count = 0; kref_get(&tp_vars->common.refcount); timer_setup(&tp_vars->common.timer, batadv_tp_receiver_shutdown, 0); @@ -1652,7 +1664,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, WRITE_ONCE(tp_vars->last_recv_time, jiffies); } - spin_lock_bh(&tp_vars->common.unacked_lock); + spin_lock_bh(&tp_vars->unacked_lock); /* if the packet is a duplicate, it may be the case that an ACK has been * lost. Resend the ACK @@ -1668,7 +1680,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, * not been enqueued correctly */ if (!batadv_tp_handle_out_of_order(tp_vars, seqno, payload_len)) { - spin_unlock_bh(&tp_vars->common.unacked_lock); + spin_unlock_bh(&tp_vars->unacked_lock); goto out; } @@ -1684,7 +1696,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, send_ack: to_ack = tp_vars->last_recv; - spin_unlock_bh(&tp_vars->common.unacked_lock); + spin_unlock_bh(&tp_vars->unacked_lock); /* send the ACK. If the received packet was out of order, the ACK that * is going to be sent is a duplicate (the sender will count them and diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index c2ab00d8ef16..c194d8069774 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -1334,7 +1334,7 @@ struct batadv_tp_unacked { /** @len: length of the packet */ u32 len; - /** @list: list node for &batadv_tp_vars_common.unacked_list */ + /** @list: list node for &batadv_tp_receiver.unacked_list */ struct list_head list; }; @@ -1357,15 +1357,6 @@ struct batadv_tp_vars_common { /** @session: TP session identifier */ u8 session[2]; - /** @unacked_list: list of unacked packets (meta-info only) */ - struct list_head unacked_list; - - /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */ - spinlock_t unacked_lock; - - /** @unacked_count: number of unacked entries */ - size_t unacked_count; - /** @refcount: number of context where the object is used */ struct kref refcount; @@ -1479,6 +1470,15 @@ struct batadv_tp_receiver { /** @last_recv_time: time (jiffies) a msg was received */ unsigned long last_recv_time; + + /** @unacked_list: list of unacked packets (meta-info only) */ + struct list_head unacked_list; + + /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */ + spinlock_t unacked_lock; + + /** @unacked_count: number of unacked entries */ + size_t unacked_count; }; /** From 6cd45ef4dfa5ba4daae126143a3d009d36687d80 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 11 Jun 2026 09:48:34 +0200 Subject: [PATCH 14/15] batman-adv: tp_meter: adjust name of receiver lock The lock used to protect the receiver from reading/writing in parallel to ack sequence number relevant data was still called unacked_lock. But it is no longer only about the unacked_list. Use a broader term to reflect this. Signed-off-by: Sven Eckelmann --- net/batman-adv/tp_meter.c | 20 ++++++++++---------- net/batman-adv/types.h | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index f18ce360839d..ffd3171d4b99 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -1252,13 +1252,13 @@ static void batadv_tp_receiver_release(struct kref *ref) /* lock should not be needed because this object is now out of any * context! */ - spin_lock_bh(&tp_vars->unacked_lock); + spin_lock_bh(&tp_vars->ack_seqno_lock); list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { list_del(&un->list); kfree(un); tp_vars->unacked_count--; } - spin_unlock_bh(&tp_vars->unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); kfree_rcu(tp_vars, common.rcu); } @@ -1316,13 +1316,13 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t) if (batadv_tp_list_detach(&tp_vars->common)) batadv_tp_receiver_put(tp_vars); - spin_lock_bh(&tp_vars->unacked_lock); + spin_lock_bh(&tp_vars->ack_seqno_lock); list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) { list_del(&un->list); kfree(un); tp_vars->unacked_count--; } - spin_unlock_bh(&tp_vars->unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); /* drop reference of timer */ if (WARN_ON(atomic_xchg(&tp_vars->receiving, 0) != 1)) @@ -1415,7 +1415,7 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst, */ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, u32 seqno, u32 payload_len) - __must_hold(&tp_vars->unacked_lock) + __must_hold(&tp_vars->ack_seqno_lock) { struct batadv_tp_unacked *un, *new; struct batadv_tp_unacked *safe; @@ -1532,7 +1532,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, * @tp_vars: the private data of the current TP meter session */ static void batadv_tp_ack_unordered(struct batadv_tp_receiver *tp_vars) - __must_hold(&tp_vars->unacked_lock) + __must_hold(&tp_vars->ack_seqno_lock) { struct batadv_tp_unacked *un, *safe; u32 to_ack; @@ -1602,7 +1602,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv, tp_vars->common.bat_priv = bat_priv; kref_init(&tp_vars->common.refcount); - spin_lock_init(&tp_vars->unacked_lock); + spin_lock_init(&tp_vars->ack_seqno_lock); INIT_LIST_HEAD(&tp_vars->unacked_list); tp_vars->unacked_count = 0; @@ -1664,7 +1664,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, WRITE_ONCE(tp_vars->last_recv_time, jiffies); } - spin_lock_bh(&tp_vars->unacked_lock); + spin_lock_bh(&tp_vars->ack_seqno_lock); /* if the packet is a duplicate, it may be the case that an ACK has been * lost. Resend the ACK @@ -1680,7 +1680,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, * not been enqueued correctly */ if (!batadv_tp_handle_out_of_order(tp_vars, seqno, payload_len)) { - spin_unlock_bh(&tp_vars->unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); goto out; } @@ -1696,7 +1696,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv, send_ack: to_ack = tp_vars->last_recv; - spin_unlock_bh(&tp_vars->unacked_lock); + spin_unlock_bh(&tp_vars->ack_seqno_lock); /* send the ACK. If the received packet was out of order, the ACK that * is going to be sent is a duplicate (the sender will count them and diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index c194d8069774..cd12755d21f3 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -1474,8 +1474,8 @@ struct batadv_tp_receiver { /** @unacked_list: list of unacked packets (meta-info only) */ struct list_head unacked_list; - /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */ - spinlock_t unacked_lock; + /** @ack_seqno_lock: protect unacked_list + &batadv_tp_receiver.last_recv */ + spinlock_t ack_seqno_lock; /** @unacked_count: number of unacked entries */ size_t unacked_count; From 247691642fd4de7a029de253e47dba936542ce9f Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 11 Jun 2026 10:48:34 +0200 Subject: [PATCH 15/15] batman-adv: tp_meter: delay allocation of unacked entry When batadv_tp_handle_out_of_order() searches the already existing list of unacked packets, it can often find an entry to merge with. In this case, it would be a waste of time and resources to allocate a batadv_tp_unacked which is then immediately freed again. Instead, search first through the list. Only when no mergeable entry could be found, it is necessary to record the place to allocate+store the new entry. Signed-off-by: Sven Eckelmann --- net/batman-adv/tp_meter.c | 88 ++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 47 deletions(-) diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c index ffd3171d4b99..00467aa79de9 100644 --- a/net/batman-adv/tp_meter.c +++ b/net/batman-adv/tp_meter.c @@ -1417,26 +1417,15 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, u32 seqno, u32 payload_len) __must_hold(&tp_vars->ack_seqno_lock) { - struct batadv_tp_unacked *un, *new; + struct list_head *pos = &tp_vars->unacked_list; + struct batadv_tp_unacked *new = NULL; + u32 end_seqno = seqno + payload_len; struct batadv_tp_unacked *safe; - bool added = false; + struct batadv_tp_unacked *un; - new = kmalloc_obj(*new, GFP_ATOMIC); - if (unlikely(!new)) - return false; - - new->seqno = seqno; - new->len = payload_len; - - /* if the list is empty immediately attach this new object */ - if (list_empty(&tp_vars->unacked_list)) { - list_add(&new->list, &tp_vars->unacked_list); - tp_vars->unacked_count++; - return true; - } - - /* otherwise loop over the list and either drop the packet because this - * is a duplicate or store it at the right position. + /* loop over the list to find either an existing entry which the new + * seqno range can be merged with or the position at which a new entry + * has to be inserted. * * The iteration is done in the reverse way because it is likely that * the last received packet (the one being processed now) has a bigger @@ -1444,7 +1433,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, */ list_for_each_entry_reverse(un, &tp_vars->unacked_list, list) { /* look for the right position - an un which is smaller */ - if (batadv_seq_before(new->seqno, un->seqno)) + if (batadv_seq_before(seqno, un->seqno)) continue; /* smaller/equal seqno was found but they might be directly @@ -1452,62 +1441,67 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_receiver *tp_vars, * * It is already known that: * - * un->seqno <= new->seqno + * un->seqno <= seqno * * When establishing that: * - * new->seqno <= un->seqno + un->len + * seqno <= un->seqno + un->len * * Then it is not necessary to add a new entry because the * smaller/equal seqno of un might already contain the new * received packet or we only add new data directly after * the end of un. The latter can be identified using: * - * un->seqno + un->len <= new->seqno + new->len + * un->seqno + un->len <= end_seqno */ - if (!batadv_seq_before(un->seqno + un->len, new->seqno)) { + if (!batadv_seq_before(un->seqno + un->len, seqno)) { /* new data directly after un? */ - if (!batadv_seq_before(new->seqno + new->len, - un->seqno + un->len)) - un->len = new->seqno + new->len - un->seqno; + if (!batadv_seq_before(end_seqno, un->seqno + un->len)) + un->len = end_seqno - un->seqno; - /* un now represents both old un + new */ - kfree(new); - added = true; - - /* un has to be used to check if the gap to the next - * seqno range was closed + /* un now represents both old un + new range and has to + * be used to check if the gap to the next seqno range + * was closed */ new = un; - break; + } else { + /* as soon as an entry having a smaller seqno is found, + * the new one is attached _after_ it. In this way the + * list is kept in ascending order + */ + pos = &un->list; } - /* as soon as an entry having a smaller seqno is found, the new - * one is attached _after_ it. In this way the list is kept in - * ascending order - */ - list_add(&new->list, &un->list); - added = true; - tp_vars->unacked_count++; break; } - /* received packet with smallest seqno out of order; add it to front */ - if (!added) { - list_add(&new->list, &tp_vars->unacked_list); + /* no entry to merge with was found; insert a new one after the entry + * with the next smaller seqno (or at the front of the list when the + * new seqno is the smallest or the list is empty) + */ + if (!new) { + new = kmalloc_obj(*new, GFP_ATOMIC); + if (unlikely(!new)) + return false; + + new->seqno = seqno; + new->len = payload_len; + + list_add(&new->list, pos); tp_vars->unacked_count++; } /* check if new filled the gap to the next list entries */ un = new; list_for_each_entry_safe_continue(un, safe, &tp_vars->unacked_list, list) { - if (batadv_seq_before(new->seqno + new->len, un->seqno)) + if (batadv_seq_before(end_seqno, un->seqno)) break; /* next entry is overlapping or adjacent - combine both */ - if (batadv_seq_before(new->seqno + new->len, - un->seqno + un->len)) - new->len = un->seqno + un->len - new->seqno; + if (batadv_seq_before(end_seqno, un->seqno + un->len)) { + end_seqno = un->seqno + un->len; + new->len = end_seqno - new->seqno; + } list_del(&un->list); kfree(un);