From 4c0a6a5b77152dce07804a60a453c779619a79d5 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Tue, 1 Sep 2026 18:29:29 +0200 Subject: [PATCH 1/9] batman-adv: bla: avoid double free after failed backbone_hash alloc When batadv_bla_init() fails to initialize the backbone_hash, it is freeing the (previously) allocated claim_hash. The initialization function will then return an error and the net_device initialization will stop. The destructor will be called instead and (indirectly via batadv_mesh_free() -> batadv_bla_free()) will try to free the bat_priv->bla.claim_hash again. The pointer must therefore be set to NULL after freeing it in the initialization error path. Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260831135117.574836-1-sw%40simonwunderlich.de?part=6 Signed-off-by: Sven Eckelmann --- net/batman-adv/bridge_loop_avoidance.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c index ad6ab4a50658..a96f269da8d3 100644 --- a/net/batman-adv/bridge_loop_avoidance.c +++ b/net/batman-adv/bridge_loop_avoidance.c @@ -1625,6 +1625,7 @@ int batadv_bla_init(struct batadv_priv *bat_priv) bat_priv->bla.backbone_hash = batadv_hash_new(32); if (!bat_priv->bla.backbone_hash) { batadv_hash_destroy(bat_priv->bla.claim_hash); + bat_priv->bla.claim_hash = NULL; return -ENOMEM; } From f11712fc3d5f9a92880a9474376692c28ada891c Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Tue, 8 Sep 2026 18:52:50 +0200 Subject: [PATCH 2/9] batman-adv: tt: clarify kernel-doc for batadv_tt_global_purge_local The new kernel-doc didn't mention the "non-multicast clients" limitation. It was also not clear that the roaming flag gets cleared when the announcement didn't contain the roaming flag. Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260907081824.2474040-1-sw%40simonwunderlich.de?part=9 Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 12ea557cb803..6d8fa6d28fee 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -1870,9 +1870,10 @@ batadv_tt_global_orig_entry_add(struct batadv_tt_global_entry *tt_global, * @tt_global_entry: the global TT entry of the announced client * @flags: TT flags announced for this non-mesh client * - * A client which is announced by another originator is no longer a local - * client. Remove it from the local table and take over the WIFI flag it was - * tracked with. + * A non-multicast client which is announced by another originator is no longer + * a local client. Remove it from the local table and (for the global entry) + * take over the WIFI flag it was tracked with. Reset the roaming flag in case + * the announcement didn't contain the roaming flag. */ static void batadv_tt_global_purge_local(struct batadv_priv *bat_priv, From 08967c26cc8d2dfc451354d4b5ba064e166c9e23 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Tue, 8 Sep 2026 18:52:50 +0200 Subject: [PATCH 3/9] batman-adv: tt: clarify responsibility for roam flag during removal If an local client gets removed due to an roam, then the local entry must be marked as "roamed" to handle the rerouting correctly. But this is not done by batadv_tt_local_remove() directly but by the new helper batadv_tt_local_mark_removed(). The former will only create similar flags for the roaming event. Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260907081824.2474040-1-sw%40simonwunderlich.de?part=10 Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 6d8fa6d28fee..78f032c9ad65 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -1461,7 +1461,10 @@ batadv_tt_local_mark_removed(struct batadv_tt_local_entry *tt_local_entry, scoped_guard(spinlock_bh, &common->flags_lock) { *curr_flags = common->flags; - /* mark the local client as ROAMed */ + /* if this global entry addition is due to a roaming, the node + * has to mark the local entry as "roamed" in order to + * correctly reroute packets later + */ if (roaming) common->flags |= BATADV_TT_CLIENT_ROAM; @@ -1532,22 +1535,22 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr, if (!tt_local_entry) return BATADV_NO_FLAGS; - flags = BATADV_TT_CLIENT_DEL; - /* if this global entry addition is due to a roaming, the node has to - * mark the local entry as "roamed" in order to correctly reroute - * packets later - */ - if (roaming) - flags |= BATADV_TT_CLIENT_ROAM; + if (batadv_tt_local_mark_removed(tt_local_entry, roaming, &curr_flags)) { + /* queue (roamed) del event which was prepared by + * batadv_tt_local_mark_removed() + */ + flags = BATADV_TT_CLIENT_DEL; + if (roaming) + flags |= BATADV_TT_CLIENT_ROAM; - if (batadv_tt_local_mark_removed(tt_local_entry, roaming, &curr_flags)) batadv_tt_local_set_pending_event(bat_priv, tt_local_entry, flags, message); - else + } else { /* if this client has been added right now, it is possible to * immediately purge it */ batadv_tt_local_remove_now(bat_priv, tt_local_entry); + } batadv_tt_local_entry_put(tt_local_entry); From be66c5ac5485b8902f10d16b30b45e6fbdaab339 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Tue, 8 Sep 2026 18:52:50 +0200 Subject: [PATCH 4/9] batman-adv: tt: soften kernel-doc for batadv_tt_local_remove_now() The "never announced" statement is too harsh for an entry which could have been actually be announced by a different context while the batadv_tt_local_remove_now() is in the process of being called. "not yet" might be more appropriate. Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260907081824.2474040-1-sw%40simonwunderlich.de?part=11 Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 78f032c9ad65..354d9416c1b7 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -1478,13 +1478,16 @@ batadv_tt_local_mark_removed(struct batadv_tt_local_entry *tt_local_entry, } /** - * batadv_tt_local_remove_now() - purge a local entry which was never announced + * batadv_tt_local_remove_now() - purge a local entry which was not (yet) announced * @bat_priv: the bat priv with all the mesh interface information * @tt_local_entry: local TT entry to purge * - * A client which was added right after the last ttvn increment was never sent - * to the other nodes. It can therefore be dropped from the local table without - * waiting for the next ttvn increment. + * A client which was added right after the last ttvn increment was not (yet) + * sent to the other nodes. It can therefore be dropped from the local table + * without waiting for the next ttvn increment. + * + * If it was still announced by a parallel context before it was removed from + * the hash, then the local TT size adjustment will be handled automatically. */ static void batadv_tt_local_remove_now(struct batadv_priv *bat_priv, From 597c0f2f69c070e8b087f503b12b35771d7c0761 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Tue, 1 Sep 2026 20:10:06 +0200 Subject: [PATCH 5/9] batman-adv: tt: only queue local del event after successful unlink When batadv_tt_local_remove_now() finds another local TT entry with the same MAC + VLAN, it will not try to remove it. A delete event must not be queued because it would otherwise cause the queued ADD event for the other TT entry to be dropped. Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260831135117.574836-1-sw%40simonwunderlich.de?part=8 Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 354d9416c1b7..c904d67791f8 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -1496,8 +1496,6 @@ batadv_tt_local_remove_now(struct batadv_priv *bat_priv, struct batadv_tt_common_entry *common = &tt_local_entry->common; struct hlist_node *tt_removed_node; - batadv_tt_local_event(bat_priv, tt_local_entry, BATADV_TT_CLIENT_DEL); - /* remove exactly this object when still present in hash */ tt_removed_node = batadv_hash_remove(bat_priv->tt.local_hash, batadv_compare_tt_entry, @@ -1505,6 +1503,8 @@ batadv_tt_local_remove_now(struct batadv_priv *bat_priv, if (!tt_removed_node) return; + batadv_tt_local_event(bat_priv, tt_local_entry, BATADV_TT_CLIENT_DEL); + /* batadv_tt_local_transition_new() may have committed the entry and * thus counted it in the local table size since the * BATADV_TT_CLIENT_NEW check in batadv_tt_local_mark_removed(). From f95b9f28a9df609be434c645d9de632041285262 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Tue, 1 Sep 2026 21:07:56 +0200 Subject: [PATCH 6/9] batman-adv: tt: queue local DEL event under bucket lock batadv_tt_local_remove() sets BATADV_TT_CLIENT_PENDING on an already announced local entry and only afterwards queues the DEL change event. It holds neither the hash bucket list_lock nor bat_priv->tt.commit_lock. It can therefore be potentially interrupted in the middle: CPU0 CPU1 batadv_tt_local_remove() flags |= ..._PENDING; batadv_tt_local_commit_changes() ..._purge_pending_clients() hlist_del_rcu(&...->hash_entry); batadv_tt_local_update_crc() atomic_inc(&bat_priv->tt.vn); batadv_tt_local_event() /* DEL queued only now */ The client then disappears from the local table and from the CRC of the new TTVN after batadv_tt_local_commit_changes() without a DEL change being announced for it. Neighbours receiving the new CRC without previously seeing the DEL will try to recover via a full table request. Move the event into batadv_tt_local_mark_removed() and hold the bucket list_lock of the entry around both the flag change and the DEL event to avoid this scenario. Fixes: 976b159b3c12 ("batman-adv: tt: use protected flag modifications") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260831135117.574836-1-sw%40simonwunderlich.de?part=12 Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 64 ++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index c904d67791f8..c229c51cafa7 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -1427,13 +1427,20 @@ int batadv_tt_local_dump(struct sk_buff *msg, struct netlink_callback *cb) * @message: debug message describing the reason for the change * * Schedule the TT change announcement for the entry. The caller must already - * have added BATADV_TT_CLIENT_PENDING to the @tt_local_entry + * have added BATADV_TT_CLIENT_PENDING to the @tt_local_entry and must hold the + * hash bucket list_lock of @tt_local_entry since setting the flag. */ static void batadv_tt_local_set_pending_event(struct batadv_priv *bat_priv, struct batadv_tt_local_entry *tt_local_entry, u16 flags, const char *message) { + struct batadv_hashtable *hash = bat_priv->tt.local_hash; + u32 i; + + i = batadv_choose_tt(&tt_local_entry->common, hash->size); + lockdep_assert_held(&hash->list_locks[i]); + batadv_tt_local_event(bat_priv, tt_local_entry, flags); batadv_dbg(BATADV_DBG_TT, bat_priv, @@ -1443,20 +1450,37 @@ batadv_tt_local_set_pending_event(struct batadv_priv *bat_priv, } /** - * batadv_tt_local_mark_removed() - mark a local entry as removed + * batadv_tt_local_mark_removed() - mark a local entry as removed and queue DEL + * @bat_priv: the bat priv with all the mesh interface information * @tt_local_entry: local TT entry to mark + * @message: message to append to the log on deletion * @roaming: true if the deletion is due to a roaming event * @curr_flags: pointer to store the flags of the entry before it was marked * + * An already announced entry is marked as BATADV_TT_CLIENT_PENDING and the + * (roamed) DEL change is queued. Both happen under the hash bucket list_lock + * of the entry to prevent concurrent batadv_tt_local_purge_pending_clients() + * from removing the entry. + * * Return: true if the entry has to be kept in the local table until the next * ttvn increment, false if it can be purged immediately. */ static bool -batadv_tt_local_mark_removed(struct batadv_tt_local_entry *tt_local_entry, - bool roaming, u16 *curr_flags) +batadv_tt_local_mark_removed(struct batadv_priv *bat_priv, + struct batadv_tt_local_entry *tt_local_entry, + const char *message, bool roaming, u16 *curr_flags) { + spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_tt_common_entry *common = &tt_local_entry->common; + struct batadv_hashtable *hash = bat_priv->tt.local_hash; bool pending = false; + u16 flags; + u32 i; + + i = batadv_choose_tt(common, hash->size); + list_lock = &hash->list_locks[i]; + + spin_lock_bh(list_lock); scoped_guard(spinlock_bh, &common->flags_lock) { *curr_flags = common->flags; @@ -1474,6 +1498,17 @@ batadv_tt_local_mark_removed(struct batadv_tt_local_entry *tt_local_entry, } } + if (pending) { + flags = BATADV_TT_CLIENT_DEL; + if (roaming) + flags |= BATADV_TT_CLIENT_ROAM; + + batadv_tt_local_set_pending_event(bat_priv, tt_local_entry, + flags, message); + } + + spin_unlock_bh(list_lock); + return pending; } @@ -1532,28 +1567,17 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr, { struct batadv_tt_local_entry *tt_local_entry; u16 curr_flags; - u16 flags; tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid); if (!tt_local_entry) return BATADV_NO_FLAGS; - if (batadv_tt_local_mark_removed(tt_local_entry, roaming, &curr_flags)) { - /* queue (roamed) del event which was prepared by - * batadv_tt_local_mark_removed() - */ - flags = BATADV_TT_CLIENT_DEL; - if (roaming) - flags |= BATADV_TT_CLIENT_ROAM; - - batadv_tt_local_set_pending_event(bat_priv, tt_local_entry, - flags, message); - } else { - /* if this client has been added right now, it is possible to - * immediately purge it - */ + /* if this client has been added right now, it is possible to + * immediately purge it + */ + if (!batadv_tt_local_mark_removed(bat_priv, tt_local_entry, message, + roaming, &curr_flags)) batadv_tt_local_remove_now(bat_priv, tt_local_entry); - } batadv_tt_local_entry_put(tt_local_entry); From a6746cb1907615a37acf26f7e529a022e99876a0 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 27 Sep 2026 13:18:17 +0200 Subject: [PATCH 7/9] batman-adv: tt: queue local DEL event before marking entry as pending batadv_tt_local_set_pending() originally queued the DEL change event and only afterwards set BATADV_TT_CLIENT_PENDING on the local entry. Commit 976b159b3c12 ("batman-adv: tt: use protected flag modifications") inverted this order for batadv_tt_local_remove() and batadv_tt_local_purge_list(). The hash bucket list_lock held around both steps does not help against batadv_tt_local_add_existing() because it is not holding it. CPU0 CPU1 flags |= ..._PENDING; batadv_tt_local_add_existing() flags &= ~..._PENDING; batadv_tt_local_event(ADD) batadv_tt_local_event(DEL) If the ADD was already announced by a commit in between, the DEL is sent in the next TTVN although the entry is no longer pending (after batadv_tt_local_add_existing()) and the local entry was never purged. The incorrect DEL will corrupt the CRC on neighbor nodes. A full table sync request is therefore issued to resolve this problem. When the DEL event is queued before the flag is set, a batadv_tt_local_add_existing() which clears the flag afterwards queues its ADD behind the DEL, and both cancel each other out. But the check whether an entry has to be removed must not be separated (by using two different critial flags_lock sections) from setting the flag (in batadv_tt_local_event) either. Otherwise batadv_tt_local_add_existing() could refresh the entry between both steps without queuing an ADD, and an active client would be announced as removed and purged. Fixes: 976b159b3c12 ("batman-adv: tt: use protected flag modifications") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260831135117.574836-1-sw%40simonwunderlich.de?part=12 Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 102 ++++++++++++++++------------- 1 file changed, 55 insertions(+), 47 deletions(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index c229c51cafa7..481dc6afaaba 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -461,23 +461,21 @@ static u16 batadv_tt_flags_get(struct batadv_tt_common_entry *common) } /** - * batadv_tt_local_event() - store a local TT event (ADD/DEL) + * __batadv_tt_local_event() - store a local TT event (ADD/DEL) with given flags * @bat_priv: the bat priv with all the mesh interface information - * @tt_local_entry: the TT entry involved in the event - * @event_flags: flags to store in the event structure + * @common: the TT entry involved in the event + * @flags: flags of the TT entry combined with the event flags */ -static void batadv_tt_local_event(struct batadv_priv *bat_priv, - struct batadv_tt_local_entry *tt_local_entry, - u8 event_flags) +static void __batadv_tt_local_event(struct batadv_priv *bat_priv, + const struct batadv_tt_common_entry *common, + u8 flags) { - struct batadv_tt_common_entry *common = &tt_local_entry->common; struct batadv_tt_change_node *tt_change_node; struct batadv_tt_change_node *entry; struct batadv_tt_change_node *safe; bool del_op_requested; bool del_op_entry; size_t changes; - u8 flags; tt_change_node = kmem_cache_alloc(batadv_tt_change_cache, GFP_ATOMIC); if (!tt_change_node) @@ -488,8 +486,6 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv, ether_addr_copy(tt_change_node->change.addr, common->addr); tt_change_node->change.vid = htons(common->vid); - flags = batadv_tt_flags_get(common) | event_flags; - tt_change_node->change.flags = flags; del_op_requested = flags & BATADV_TT_CLIENT_DEL; @@ -537,6 +533,23 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv, spin_unlock_bh(&bat_priv->tt.changes_list_lock); } +/** + * batadv_tt_local_event() - store a local TT event (ADD/DEL) + * @bat_priv: the bat priv with all the mesh interface information + * @tt_local_entry: the TT entry involved in the event + * @event_flags: flags to store in the event structure + */ +static void batadv_tt_local_event(struct batadv_priv *bat_priv, + struct batadv_tt_local_entry *tt_local_entry, + u8 event_flags) +{ + struct batadv_tt_common_entry *common = &tt_local_entry->common; + u8 flags; + + flags = batadv_tt_flags_get(common) | event_flags; + __batadv_tt_local_event(bat_priv, common, flags); +} + /** * batadv_tt_len() - compute length in bytes of given number of tt changes * @changes_num: number of tt changes @@ -1420,28 +1433,37 @@ int batadv_tt_local_dump(struct sk_buff *msg, struct netlink_callback *cb) } /** - * batadv_tt_local_set_pending_event() - trigger events for TT pending removal + * batadv_tt_local_set_pending() - mark local TT entry as pending removal * @bat_priv: the bat priv with all the mesh interface information - * @tt_local_entry: local TT entry which was marked as BATADV_TT_CLIENT_PENDING + * @tt_local_entry: local TT entry to mark as BATADV_TT_CLIENT_PENDING * @flags: TT change flags to announce together with the pending removal * @message: debug message describing the reason for the change * - * Schedule the TT change announcement for the entry. The caller must already - * have added BATADV_TT_CLIENT_PENDING to the @tt_local_entry and must hold the - * hash bucket list_lock of @tt_local_entry since setting the flag. + * Schedule the TT change announcement and set BATADV_TT_CLIENT_PENDING on the + * entry. The entry is kept in the local table until the next TTVN increment + * so that a consistency-check response can still be answered. + * + * Next to the flags_lock of the entry, the caller must hold the hash bucket + * list_lock of @tt_local_entry. Otherwise + * batadv_tt_local_purge_pending_clients() could remove the entry before its + * change was queued. */ static void -batadv_tt_local_set_pending_event(struct batadv_priv *bat_priv, - struct batadv_tt_local_entry *tt_local_entry, - u16 flags, const char *message) +batadv_tt_local_set_pending(struct batadv_priv *bat_priv, + struct batadv_tt_local_entry *tt_local_entry, + u16 flags, const char *message) + __must_hold(&tt_local_entry->common.flags_lock) { + struct batadv_tt_common_entry *common = &tt_local_entry->common; struct batadv_hashtable *hash = bat_priv->tt.local_hash; u32 i; - i = batadv_choose_tt(&tt_local_entry->common, hash->size); + i = batadv_choose_tt(common, hash->size); lockdep_assert_held(&hash->list_locks[i]); + lockdep_assert_held(&common->flags_lock); - batadv_tt_local_event(bat_priv, tt_local_entry, flags); + __batadv_tt_local_event(bat_priv, common, common->flags | flags); + common->flags |= BATADV_TT_CLIENT_PENDING; batadv_dbg(BATADV_DBG_TT, bat_priv, "Local tt entry (%pM, vid: %d) pending to be removed: %s\n", @@ -1460,7 +1482,8 @@ batadv_tt_local_set_pending_event(struct batadv_priv *bat_priv, * An already announced entry is marked as BATADV_TT_CLIENT_PENDING and the * (roamed) DEL change is queued. Both happen under the hash bucket list_lock * of the entry to prevent concurrent batadv_tt_local_purge_pending_clients() - * from removing the entry. + * from removing the entry and batadv_tt_local_transition_new() from clearing + * BATADV_TT_CLIENT_NEW after it was checked. * * Return: true if the entry has to be kept in the local table until the next * ttvn increment, false if it can be purged immediately. @@ -1492,19 +1515,16 @@ batadv_tt_local_mark_removed(struct batadv_priv *bat_priv, if (roaming) common->flags |= BATADV_TT_CLIENT_ROAM; - if (!(common->flags & BATADV_TT_CLIENT_NEW)) { - common->flags |= BATADV_TT_CLIENT_PENDING; - pending = true; - } - } + if (common->flags & BATADV_TT_CLIENT_NEW) + break; - if (pending) { flags = BATADV_TT_CLIENT_DEL; if (roaming) flags |= BATADV_TT_CLIENT_ROAM; - batadv_tt_local_set_pending_event(bat_priv, tt_local_entry, - flags, message); + batadv_tt_local_set_pending(bat_priv, tt_local_entry, flags, + message); + pending = true; } spin_unlock_bh(list_lock); @@ -1601,37 +1621,25 @@ static void batadv_tt_local_purge_list(struct batadv_priv *bat_priv, hlist_for_each_entry_safe(tt_common_entry, node_tmp, head, hash_entry) { - bool cont = false; - tt_local_entry = container_of(tt_common_entry, struct batadv_tt_local_entry, common); scoped_guard(spinlock_bh, &tt_local_entry->common.flags_lock) { - if (tt_local_entry->common.flags & BATADV_TT_CLIENT_NOPURGE) { - cont = true; + if (tt_local_entry->common.flags & BATADV_TT_CLIENT_NOPURGE) break; - } /* entry already marked for deletion */ - if (tt_local_entry->common.flags & BATADV_TT_CLIENT_PENDING) { - cont = true; + if (tt_local_entry->common.flags & BATADV_TT_CLIENT_PENDING) break; - } - if (!batadv_has_timed_out(tt_local_entry->last_seen, timeout)) { - cont = true; + if (!batadv_has_timed_out(tt_local_entry->last_seen, timeout)) break; - } - tt_local_entry->common.flags |= BATADV_TT_CLIENT_PENDING; + batadv_tt_local_set_pending(bat_priv, tt_local_entry, + BATADV_TT_CLIENT_DEL, + "timed out"); } - - if (cont) - continue; - - batadv_tt_local_set_pending_event(bat_priv, tt_local_entry, - BATADV_TT_CLIENT_DEL, "timed out"); } } From 85fb027f611b200c75b456404def8665216bc2db Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 6 Aug 2026 14:35:19 +0200 Subject: [PATCH 8/9] batman-adv: tt: reject VLAN/TT entries before reaching size limit The translation table is announcing its current state via the TT TVLV in each OGM(2). If another originator detects a desync with its own copy of this state information, it will request a full table sync. Each originator must therefore be able to send a reply with its full table without hitting the size limit of this response. The translation table code already tries to limit the amount of local TT entries. But the code itself fails to achieve this task because: * the size check uses shared, unlocked information * the size check isn't done for VLANs When enough VLANs + TT entries are created in parallel, then the TT will need more room then allowed for a full translation table reply. Other originators will then send regularly requests for a table sync but never get a reply. To avoid this problem for new VLAN and local TT entries, each new entry must reserve "room" before it is actually allocated. Only when enough "room" is available, this reservation is granted. This makes it possible to keep the reservation handling locked and therefore safe for multiple parallel contexts. For now, over-reservation caused by a reduction of the MTU is not taken into account. Signed-off-by: Sven Eckelmann --- net/batman-adv/main.c | 1 + net/batman-adv/mesh-interface.c | 8 ++ net/batman-adv/translation-table.c | 194 ++++++++++++++++++++++++----- net/batman-adv/translation-table.h | 3 + net/batman-adv/types.h | 19 +++ 5 files changed, 194 insertions(+), 31 deletions(-) diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index d89d44706269..6c54a8d4be3e 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -198,6 +198,7 @@ int batadv_mesh_init(struct net_device *mesh_iface) spin_lock_init(&bat_priv->tt.roam_list_lock); spin_lock_init(&bat_priv->tt.last_changeset_lock); spin_lock_init(&bat_priv->tt.commit_lock); + spin_lock_init(&bat_priv->tt.reserve_lock); spin_lock_init(&bat_priv->gw.list_lock); #ifdef CONFIG_BATMAN_ADV_MCAST spin_lock_init(&bat_priv->mcast.mla_lock); diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index 63af21954cf9..1ce707aaf48a 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -561,6 +561,8 @@ void batadv_meshif_vlan_release(struct kref *ref) hlist_del_rcu(&vlan->list); spin_unlock_bh(&vlan->bat_priv->meshif_vlan_list_lock); + batadv_tt_local_unreserve_vlan(vlan->bat_priv); + kfree_rcu(vlan, rcu); } @@ -614,9 +616,15 @@ int batadv_meshif_create_vlan(struct batadv_priv *bat_priv, unsigned short vid) return -EEXIST; } + if (!batadv_tt_local_reserve_vlan(bat_priv, vid)) { + spin_unlock_bh(&bat_priv->meshif_vlan_list_lock); + return -EMSGSIZE; + } + vlan = kzalloc_obj(*vlan, GFP_ATOMIC); if (!vlan) { spin_unlock_bh(&bat_priv->meshif_vlan_list_lock); + batadv_tt_local_unreserve_vlan(bat_priv); return -ENOMEM; } diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 481dc6afaaba..3349376a9087 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -234,6 +234,162 @@ batadv_tt_global_hash_find(struct batadv_priv *bat_priv, const u8 *addr, return tt_global_entry; } +/** + * batadv_tt_len() - compute length in bytes of given number of tt changes + * @changes_num: number of tt changes + * + * Return: computed length in bytes. + */ +static int batadv_tt_len(int changes_num) +{ + return changes_num * sizeof(struct batadv_tvlv_tt_change); +} + +/** + * batadv_tt_local_transmit_size() - calculate the size of a full table response + * for a given number of VLANs and local TT entries + * @num_vlan: number of announced VLANs + * @num_entries: number of announced local TT entries + * + * Return: local translation table size in bytes. + */ +static int batadv_tt_local_transmit_size(u16 num_vlan, u16 num_entries) +{ + int hdr_size; + + /* header size of tvlv encapsulated tt response payload */ + hdr_size = sizeof(struct batadv_unicast_tvlv_packet); + hdr_size += sizeof(struct batadv_tvlv_hdr); + hdr_size += sizeof(struct batadv_tvlv_tt_data); + hdr_size += num_vlan * sizeof(struct batadv_tvlv_tt_vlan_data); + + return hdr_size + batadv_tt_len(num_entries); +} + +/** + * batadv_tt_local_reserve() - reserve room in the transmittable local table + * @bat_priv: the bat priv with all the mesh interface information + * @num_vlan: number of VLANs to reserve + * @num_entries: number of local TT entries to reserve + * @table_size: stores the resulting worst case table size in bytes + * + * Add the requested number of VLANs and local TT entries to the reservation + * counters and check whether the local translation table would then still fit + * in a single full table response. The reservation is dropped again when it + * would not. + * + * The reservation has to happen before the related object is allocated. This + * way two parallel allocations cannot both observe enough room for themselves + * and end up with a local table which can no longer be transmitted. + * + * A granted reservation must be returned via batadv_tt_local_unreserve() when + * the related object is released or was never created. + * + * Return: true when the reservation was granted, false otherwise. + */ +static bool batadv_tt_local_reserve(struct batadv_priv *bat_priv, u16 num_vlan, + u16 num_entries, int *table_size) +{ + int packet_size_max = READ_ONCE(bat_priv->packet_size_max); + + scoped_guard(spinlock_bh, &bat_priv->tt.reserve_lock) { + bat_priv->tt.reserved_vlans += num_vlan; + bat_priv->tt.reserved_entries += num_entries; + + *table_size = batadv_tt_local_transmit_size(bat_priv->tt.reserved_vlans, + bat_priv->tt.reserved_entries); + if (*table_size <= packet_size_max) + return true; + + bat_priv->tt.reserved_vlans -= num_vlan; + bat_priv->tt.reserved_entries -= num_entries; + } + + return false; +} + +/** + * batadv_tt_local_unreserve() - return room in the transmittable local table + * @bat_priv: the bat priv with all the mesh interface information + * @num_vlan: number of VLANs to return + * @num_entries: number of local TT entries to return + */ +static void batadv_tt_local_unreserve(struct batadv_priv *bat_priv, + u16 num_vlan, u16 num_entries) +{ + scoped_guard(spinlock_bh, &bat_priv->tt.reserve_lock) { + bat_priv->tt.reserved_vlans -= num_vlan; + bat_priv->tt.reserved_entries -= num_entries; + } +} + +/** + * batadv_tt_local_reserve_entry() - reserve room for a new local TT entry + * @bat_priv: the bat priv with all the mesh interface information + * @addr: the mac address of the client to add + * + * Return: true when the reservation was granted, false otherwise. + */ +static bool batadv_tt_local_reserve_entry(struct batadv_priv *bat_priv, + const u8 *addr) +{ + int table_size; + + if (batadv_tt_local_reserve(bat_priv, 0, 1, &table_size)) + return true; + + net_ratelimited_function(batadv_info, bat_priv->mesh_iface, + "Local translation table size (%i) exceeds maximum packet size (%i); Ignoring new local tt entry: %pM\n", + table_size, + READ_ONCE(bat_priv->packet_size_max), addr); + + return false; +} + +/** + * batadv_tt_local_unreserve_entry() - return the room of a local TT entry + * @bat_priv: the bat priv with all the mesh interface information + */ +static void batadv_tt_local_unreserve_entry(struct batadv_priv *bat_priv) +{ + batadv_tt_local_unreserve(bat_priv, 0, 1); +} + +/** + * batadv_tt_local_reserve_vlan() - reserve room for a new VLAN + * @bat_priv: the bat priv with all the mesh interface information + * @vid: the VLAN identifier + * + * Each VLAN adds a per VLAN header to the full table response and therefore + * has to be reserved before batadv_meshif_create_vlan() allocates it. + * + * Return: true when the reservation was granted, false otherwise. + */ +bool batadv_tt_local_reserve_vlan(struct batadv_priv *bat_priv, + unsigned short vid) +{ + int table_size; + + if (batadv_tt_local_reserve(bat_priv, 1, 0, &table_size)) + return true; + + net_ratelimited_function(batadv_info, bat_priv->mesh_iface, + "Local translation table size (%i) exceeds maximum packet size (%i); Ignoring new VLAN: %d\n", + table_size, READ_ONCE(bat_priv->packet_size_max), + batadv_print_vid(vid)); + + return false; +} + +/** + * batadv_tt_local_unreserve_vlan() - return the room of a VLAN + * @bat_priv: the bat priv with all the mesh interface information + */ +void batadv_tt_local_unreserve_vlan(struct batadv_priv *bat_priv) +{ + batadv_tt_local_unreserve(bat_priv, 1, 0); +} + /** * batadv_tt_local_entry_release() - release tt_local_entry from lists and queue * for free after rcu grace period @@ -246,6 +402,7 @@ static void batadv_tt_local_entry_release(struct kref *ref) tt_local_entry = container_of(ref, struct batadv_tt_local_entry, common.refcount); + batadv_tt_local_unreserve_entry(tt_local_entry->vlan->bat_priv); batadv_meshif_vlan_put(tt_local_entry->vlan); kfree_rcu(tt_local_entry, common.rcu); @@ -550,17 +707,6 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv, __batadv_tt_local_event(bat_priv, common, flags); } -/** - * batadv_tt_len() - compute length in bytes of given number of tt changes - * @changes_num: number of tt changes - * - * Return: computed length in bytes. - */ -static int batadv_tt_len(int changes_num) -{ - return changes_num * sizeof(struct batadv_tvlv_tt_change); -} - /** * batadv_tt_entries() - compute the number of entries fitting in tt_len bytes * @tt_len: available space @@ -584,7 +730,6 @@ static int batadv_tt_local_table_transmit_size(struct batadv_priv *bat_priv) struct batadv_meshif_vlan *vlan; u16 tt_local_entries = 0; u16 num_vlan = 0; - int hdr_size; rcu_read_lock(); hlist_for_each_entry_rcu(vlan, &bat_priv->meshif_vlan_list, list) { @@ -593,13 +738,7 @@ static int batadv_tt_local_table_transmit_size(struct batadv_priv *bat_priv) } rcu_read_unlock(); - /* header size of tvlv encapsulated tt response payload */ - hdr_size = sizeof(struct batadv_unicast_tvlv_packet); - hdr_size += sizeof(struct batadv_tvlv_hdr); - hdr_size += sizeof(struct batadv_tvlv_tt_data); - hdr_size += num_vlan * sizeof(struct batadv_tvlv_tt_vlan_data); - - return hdr_size + batadv_tt_len(tt_local_entries); + return batadv_tt_local_transmit_size(num_vlan, tt_local_entries); } /** @@ -803,23 +942,15 @@ batadv_tt_local_create(struct net_device *mesh_iface, const u8 *addr, struct batadv_priv *bat_priv = netdev_priv(mesh_iface); struct batadv_tt_local_entry *tt_local; struct batadv_meshif_vlan *vlan; - int packet_size_max; - int table_size; - /* Ignore the client if we cannot send it in a full table response. */ - table_size = batadv_tt_local_table_transmit_size(bat_priv); - table_size += batadv_tt_len(1); - packet_size_max = READ_ONCE(bat_priv->packet_size_max); - if (table_size > packet_size_max) { - net_ratelimited_function(batadv_info, mesh_iface, - "Local translation table size (%i) exceeds maximum packet size (%i); Ignoring new local tt entry: %pM\n", - table_size, packet_size_max, addr); + if (!batadv_tt_local_reserve_entry(bat_priv, addr)) return NULL; - } tt_local = kmem_cache_alloc(batadv_tl_cache, GFP_ATOMIC); - if (!tt_local) + if (!tt_local) { + batadv_tt_local_unreserve_entry(bat_priv); return NULL; + } /* increase the refcounter of the related vlan */ vlan = batadv_meshif_vlan_get(bat_priv, vid); @@ -828,6 +959,7 @@ batadv_tt_local_create(struct net_device *mesh_iface, const u8 *addr, "adding TT local entry %pM to non-existent VLAN %d\n", addr, batadv_print_vid(vid)); kmem_cache_free(batadv_tl_cache, tt_local); + batadv_tt_local_unreserve_entry(bat_priv); return NULL; } diff --git a/net/batman-adv/translation-table.h b/net/batman-adv/translation-table.h index 618d9dbca5ea..ca01d20ee2d7 100644 --- a/net/batman-adv/translation-table.h +++ b/net/batman-adv/translation-table.h @@ -16,6 +16,9 @@ #include int batadv_tt_init(struct batadv_priv *bat_priv); +bool batadv_tt_local_reserve_vlan(struct batadv_priv *bat_priv, + unsigned short vid); +void batadv_tt_local_unreserve_vlan(struct batadv_priv *bat_priv); bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, unsigned short vid, int ifindex, u32 mark); u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index 67872927cfb5..c19caa84e692 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -1055,6 +1055,25 @@ struct batadv_priv_tt { */ spinlock_t commit_lock; + /** + * @reserved_entries: number of local TT entries which are currently + * allocated or about to be allocated. Together with @reserved_vlans it + * describes the worst case size of a full table response. + */ + u16 reserved_entries; + + /** + * @reserved_vlans: number of mesh interface VLANs which are currently + * allocated or about to be allocated. Together with @reserved_entries + * it describes the worst case size of a full table response. + */ + u16 reserved_vlans; + + /** + * @reserve_lock: lock protecting @reserved_entries & @reserved_vlans + */ + spinlock_t reserve_lock; + /** @work: work queue callback item for translation table purging */ struct delayed_work work; }; From d1807b20d548171d40c5d21a7cf22830ea8f348e Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Sun, 20 Sep 2026 10:27:21 +0800 Subject: [PATCH 9/9] batman-adv: use assign_bit() where applicable Convert open-coded if/else with set_bit/clear_bit the assign_bit API. Signed-off-by: Peng Fan Signed-off-by: Sven Eckelmann --- net/batman-adv/distributed-arp-table.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c index a4be5b2a87d7..74050bffedbc 100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@ -863,10 +863,8 @@ static void batadv_dat_tvlv_ogm_handler_v1(struct batadv_priv *bat_priv, u8 flags, void *tvlv_value, u16 tvlv_value_len) { - if (flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND) - clear_bit(BATADV_ORIG_CAPA_HAS_DAT, &orig->capabilities); - else - set_bit(BATADV_ORIG_CAPA_HAS_DAT, &orig->capabilities); + assign_bit(BATADV_ORIG_CAPA_HAS_DAT, &orig->capabilities, + !(flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND)); } /**