From c6c8f1d693ac349d61aa9db7c420f682523555c7 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 6 Aug 2026 17:15:03 +0200 Subject: [PATCH 01/15] batman-adv: dat: fix printing of unknown 4addr subtype If an unknown subtype of an unicast 4addr packets was detected in batadv_dbg_arp(), it is printing the (known) packet_type instead of the subtype. This must be switched to the subtype to have any kind of use for the reader of the debug message. Reported-by: Sashiko Signed-off-by: Sven Eckelmann --- net/batman-adv/distributed-arp-table.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c index 011cfdc47fab..270f8d6e2954 100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@ -548,7 +548,7 @@ static void batadv_dbg_arp(struct batadv_priv *bat_priv, struct sk_buff *skb, break; default: batadv_dbg(BATADV_DBG_DAT, bat_priv, "* type: Unknown (%u)!\n", - unicast_4addr_packet->u.packet_type); + unicast_4addr_packet->subtype); } break; case BATADV_BCAST: From b963ee1dc32ce5329818e167b679b843b340d6ae Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 6 Aug 2026 18:33:24 +0200 Subject: [PATCH 02/15] batman-adv: drop direction in _batadv_is_ap_isolated kernel-doc The _batadv_is_ap_isolated() is called by * batadv_transtable_search() - tt_local_entry is the source - tt_global_entry is the destination * batadv_is_ap_isolated - tt_local_entry is the destination - tt_global_entry is the source It is therefore incorrect to document the tt_local_entry as sending client and tt_global_entry as the destination client. Reported-by: Sashiko 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 68f79853032e..88aeefa97db6 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -2437,8 +2437,8 @@ static void batadv_tt_global_table_free(struct batadv_priv *bat_priv) /** * _batadv_is_ap_isolated() - check whether two clients are AP-isolated from * each other - * @tt_local_entry: local TT entry of the sending client - * @tt_global_entry: global TT entry of the destination client + * @tt_local_entry: local TT entry of one local client + * @tt_global_entry: global TT entry of the remote client * * Return: true if traffic between the two clients should be dropped because * either both are WiFi clients or both carry the ISOLATION flag; false From 77f653e6f7223634111366a1f5b6b0340770105e Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 6 Aug 2026 20:54:53 +0200 Subject: [PATCH 03/15] batman-adv: bat_v: fix bonding candidate selection batadv_v_neigh_is_sob() implements the metric (throughput) similarity check as: neigh2 (the reference) has a better throughput than 3/4 of the neigh1 (candidate) throughput But this results in a selection of bonding candidates in batadv_find_router() which have a really low throughput. But the goal must be to select candidates which have a good throughput. The neigh1/neigh2 operands must be swapped in the comparison to use the reference as such and select only neighbor+interface combinations with good throughput. Reported-by: Sashiko Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_v.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c index 0c27447cf688..2d3045733db1 100644 --- a/net/batman-adv/bat_v.c +++ b/net/batman-adv/bat_v.c @@ -519,8 +519,8 @@ static int batadv_v_neigh_cmp(struct batadv_neigh_node *neigh1, * @neigh2: second neighbour to compare * @if_outgoing2: outgoing interface to use for @neigh2 * - * Return: true if the throughput of @neigh2 is at least 3/4 of the - * @neigh1 throughput + * Return: true if the throughput of @neigh1 is at least 3/4 of the + * @neigh2 throughput */ static bool batadv_v_neigh_is_sob(struct batadv_neigh_node *neigh1, struct batadv_hard_iface *if_outgoing1, @@ -540,10 +540,10 @@ static bool batadv_v_neigh_is_sob(struct batadv_neigh_node *neigh1, if (!ifinfo2) goto err_ifinfo2; - threshold = ifinfo1->bat_v.throughput / 4; - threshold = ifinfo1->bat_v.throughput - threshold; + threshold = ifinfo2->bat_v.throughput / 4; + threshold = ifinfo2->bat_v.throughput - threshold; - ret = ifinfo2->bat_v.throughput > threshold; + ret = ifinfo1->bat_v.throughput > threshold; batadv_neigh_ifinfo_put(ifinfo2); err_ifinfo2: From 7356a9232d9c84e57a71b362370b2397ef58fb26 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 6 Aug 2026 20:54:54 +0200 Subject: [PATCH 04/15] batman-adv: clarify cut-off in batadv_v_neigh_is_sob kernel-doc The neigh1 throughput must be more than 3/4 of the neigh2 throughput. Just 3/4 is not enough to return true. Reported-by: Sashiko Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_v.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c index 2d3045733db1..0d0a226c3fd0 100644 --- a/net/batman-adv/bat_v.c +++ b/net/batman-adv/bat_v.c @@ -519,7 +519,7 @@ static int batadv_v_neigh_cmp(struct batadv_neigh_node *neigh1, * @neigh2: second neighbour to compare * @if_outgoing2: outgoing interface to use for @neigh2 * - * Return: true if the throughput of @neigh1 is at least 3/4 of the + * Return: true if the throughput of @neigh1 is more than 3/4 of the * @neigh2 throughput */ static bool batadv_v_neigh_is_sob(struct batadv_neigh_node *neigh1, From 776c26254d2423e61a704af2d9ab28d39969dfdc Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 6 Aug 2026 20:54:55 +0200 Subject: [PATCH 05/15] batman-adv: use more descriptive var names for is_similar_or_better neigh1 and neigh2 is not really helpful when looking at a code which needs to judge the metric similarity between a candidate and a reference. This already caused an implementation error in the B.A.T.M.A.N. V bonding code. Simply using "reference" and "candidate" for the neighbors is a lot more descriptive and makes it easier to understand the code. Signed-off-by: Sven Eckelmann --- net/batman-adv/bat_iv_ogm.c | 25 +++++++++++----------- net/batman-adv/bat_v.c | 42 ++++++++++++++++++------------------- net/batman-adv/types.h | 12 +++++------ 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c index 53fbdbbe8f4f..3fe09b4c3046 100644 --- a/net/batman-adv/bat_iv_ogm.c +++ b/net/batman-adv/bat_iv_ogm.c @@ -2424,25 +2424,26 @@ static int batadv_iv_ogm_neigh_cmp(struct batadv_neigh_node *neigh1, /** * batadv_iv_ogm_neigh_is_sob() - check if neigh1 is similarly good or better * than neigh2 from the metric prospective - * @neigh1: the first neighbor object of the comparison - * @if_outgoing1: outgoing interface for the first neighbor - * @neigh2: the second neighbor object of the comparison - * @if_outgoing2: outgoing interface for the second neighbor + * @candidate: the first neighbor object of the comparison + * @if_outgoing_cand: outgoing interface for the @candidate neighbor + * @reference: the second neighbor object of the comparison + * @if_outgoing_ref: outgoing interface for the @reference neighbor * - * Return: true if the metric via neigh1 is equally good or better than - * the metric via neigh2, false otherwise. + * Return: true if the metric via @candidate is equally good or better than + * the metric via @reference, false otherwise. */ static bool -batadv_iv_ogm_neigh_is_sob(struct batadv_neigh_node *neigh1, - struct batadv_hard_iface *if_outgoing1, - struct batadv_neigh_node *neigh2, - struct batadv_hard_iface *if_outgoing2) +batadv_iv_ogm_neigh_is_sob(struct batadv_neigh_node *candidate, + struct batadv_hard_iface *if_outgoing_cand, + struct batadv_neigh_node *reference, + struct batadv_hard_iface *if_outgoing_ref) { bool ret; int diff; - ret = batadv_iv_ogm_neigh_diff(neigh1, if_outgoing1, neigh2, - if_outgoing2, &diff); + ret = batadv_iv_ogm_neigh_diff(candidate, if_outgoing_cand, + reference, if_outgoing_ref, + &diff); if (!ret) return false; diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c index 0d0a226c3fd0..596889b3522d 100644 --- a/net/batman-adv/bat_v.c +++ b/net/batman-adv/bat_v.c @@ -514,40 +514,40 @@ static int batadv_v_neigh_cmp(struct batadv_neigh_node *neigh1, /** * batadv_v_neigh_is_sob() - check whether two B.A.T.M.A.N. V neighbours have * a similar or better throughput - * @neigh1: first neighbour to compare - * @if_outgoing1: outgoing interface to use for @neigh1 - * @neigh2: second neighbour to compare - * @if_outgoing2: outgoing interface to use for @neigh2 + * @candidate: the first neighbor object of the comparison + * @if_outgoing_cand: outgoing interface for the @candidate neighbor + * @reference: the second neighbor object of the comparison + * @if_outgoing_ref: outgoing interface for the @reference neighbor * - * Return: true if the throughput of @neigh1 is more than 3/4 of the - * @neigh2 throughput + * Return: true if the throughput of @candidate is more than 3/4 of the + * @reference throughput */ -static bool batadv_v_neigh_is_sob(struct batadv_neigh_node *neigh1, - struct batadv_hard_iface *if_outgoing1, - struct batadv_neigh_node *neigh2, - struct batadv_hard_iface *if_outgoing2) +static bool batadv_v_neigh_is_sob(struct batadv_neigh_node *candidate, + struct batadv_hard_iface *if_outgoing_cand, + struct batadv_neigh_node *reference, + struct batadv_hard_iface *if_outgoing_ref) { - struct batadv_neigh_ifinfo *ifinfo1; - struct batadv_neigh_ifinfo *ifinfo2; + struct batadv_neigh_ifinfo *ifinfo_cand; + struct batadv_neigh_ifinfo *ifinfo_ref; bool ret = false; u32 threshold; - ifinfo1 = batadv_neigh_ifinfo_get(neigh1, if_outgoing1); - if (!ifinfo1) + ifinfo_cand = batadv_neigh_ifinfo_get(candidate, if_outgoing_cand); + if (!ifinfo_cand) goto err_ifinfo1; - ifinfo2 = batadv_neigh_ifinfo_get(neigh2, if_outgoing2); - if (!ifinfo2) + ifinfo_ref = batadv_neigh_ifinfo_get(reference, if_outgoing_ref); + if (!ifinfo_ref) goto err_ifinfo2; - threshold = ifinfo2->bat_v.throughput / 4; - threshold = ifinfo2->bat_v.throughput - threshold; + threshold = ifinfo_ref->bat_v.throughput / 4; + threshold = ifinfo_ref->bat_v.throughput - threshold; - ret = ifinfo1->bat_v.throughput > threshold; + ret = ifinfo_cand->bat_v.throughput > threshold; - batadv_neigh_ifinfo_put(ifinfo2); + batadv_neigh_ifinfo_put(ifinfo_ref); err_ifinfo2: - batadv_neigh_ifinfo_put(ifinfo1); + batadv_neigh_ifinfo_put(ifinfo_cand); err_ifinfo1: return ret; } diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index 9bdc5a3e799e..99667f632d4d 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -2087,14 +2087,14 @@ struct batadv_algo_neigh_ops { struct batadv_hard_iface *if_outgoing2); /** - * @is_similar_or_better: check if neigh1 is equally similar or better - * than neigh2 for their respective outgoing interface from the metric + * @is_similar_or_better: check if @candidate is equally similar or better + * than @reference for their respective outgoing interface from the metric * prospective */ - bool (*is_similar_or_better)(struct batadv_neigh_node *neigh1, - struct batadv_hard_iface *if_outgoing1, - struct batadv_neigh_node *neigh2, - struct batadv_hard_iface *if_outgoing2); + bool (*is_similar_or_better)(struct batadv_neigh_node *candidate, + struct batadv_hard_iface *if_outgoing_cand, + struct batadv_neigh_node *reference, + struct batadv_hard_iface *if_outgoing_ref); /** @dump: dump neighbors to a netlink socket (optional) */ void (*dump)(struct sk_buff *msg, struct netlink_callback *cb, From a5f7058bee1ec6d0df7a0dc20279775ee116f3fb Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sat, 22 Aug 2026 15:14:03 +0200 Subject: [PATCH 06/15] batman-adv: ensure u16 aligned mac address arrays on stack An u8 array on the stack has a natural alignment of only 1 byte. An u8[ETH_ALEN] array on the stack must therefore be assumed to only be 1 byte aligned. But many etherdevices.h functions are requiring a 2 byte (u16) alignment. Annote all u8[ETH_ALEN] arrays on the stack as __aligned(2) to make sure that the compiler takes care of aligning it correctly. Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260821094813.201800-1-sw%40simonwunderlich.de?part=5 Signed-off-by: Sven Eckelmann --- net/batman-adv/bridge_loop_avoidance.c | 6 ++++-- net/batman-adv/distributed-arp-table.c | 8 ++++---- net/batman-adv/mesh-interface.c | 12 ++++++++---- net/batman-adv/multicast.c | 6 +++--- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c index f635da4b8ca6..ad6ab4a50658 100644 --- a/net/batman-adv/bridge_loop_avoidance.c +++ b/net/batman-adv/bridge_loop_avoidance.c @@ -663,7 +663,7 @@ static void batadv_bla_send_request(struct batadv_bla_backbone_gw *backbone_gw) static void batadv_bla_send_announce(struct batadv_priv *bat_priv, struct batadv_bla_backbone_gw *backbone_gw) { - u8 mac[ETH_ALEN]; + u8 mac[ETH_ALEN] __aligned(2); __be16 crc; memcpy(mac, batadv_announce_mac, 4); @@ -1582,7 +1582,9 @@ static struct lock_class_key batadv_backbone_hash_lock_class_key; */ int batadv_bla_init(struct batadv_priv *bat_priv) { - u8 claim_dest[ETH_ALEN] = {0xff, 0x43, 0x05, 0x00, 0x00, 0x00}; + static const u8 claim_dest[ETH_ALEN] __aligned(2) = { + 0xff, 0x43, 0x05, 0x00, 0x00, 0x00 + }; struct batadv_hard_iface *primary_if; unsigned long entrytime; u16 crc; diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c index 270f8d6e2954..a4be5b2a87d7 100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@ -940,7 +940,7 @@ batadv_dat_cache_dump_entry(struct sk_buff *msg, u32 portid, struct netlink_callback *cb, struct batadv_dat_entry *dat_entry) { - u8 mac[ETH_ALEN]; + u8 mac[ETH_ALEN] __aligned(2); u64 u64_mac; int msecs; void *hdr; @@ -1236,10 +1236,10 @@ bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, { struct net_device *mesh_iface = bat_priv->mesh_iface; struct batadv_dat_entry *dat_entry = NULL; + u8 mac[ETH_ALEN] __aligned(2); struct sk_buff *skb_new; unsigned short vid; bool ret = false; - u8 mac[ETH_ALEN]; int hdr_size = 0; __be32 ip_dst; __be32 ip_src; @@ -1344,10 +1344,10 @@ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, struct sk_buff *skb, int hdr_size) { struct batadv_dat_entry *dat_entry = NULL; + u8 mac[ETH_ALEN] __aligned(2); struct sk_buff *skb_new; unsigned short vid; bool ret = false; - u8 mac[ETH_ALEN]; __be32 ip_src; __be32 ip_dst; u64 u64_mac; @@ -1474,9 +1474,9 @@ bool batadv_dat_snoop_incoming_arp_reply(struct batadv_priv *bat_priv, struct sk_buff *skb, int hdr_size) { struct batadv_dat_entry *dat_entry = NULL; + u8 mac[ETH_ALEN] __aligned(2); bool dropped = false; unsigned short vid; - u8 mac[ETH_ALEN]; __be32 ip_src; __be32 ip_dst; u64 u64_mac; diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index e202088cf1fd..63af21954cf9 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -141,9 +141,9 @@ static struct net_device_stats *batadv_interface_stats(struct net_device *dev) static int batadv_interface_set_mac_addr(struct net_device *dev, void *p) { struct batadv_priv *bat_priv = netdev_priv(dev); + u8 old_addr[ETH_ALEN] __aligned(2); struct batadv_meshif_vlan *vlan; struct sockaddr *addr = p; - u8 old_addr[ETH_ALEN]; if (!is_valid_ether_addr(addr->sa_data)) return -EADDRNOTAVAIL; @@ -213,13 +213,18 @@ static void batadv_interface_set_rx_mode(struct net_device *dev) static netdev_tx_t batadv_interface_tx(struct sk_buff *skb, struct net_device *mesh_iface) { - static const u8 ectp_addr[ETH_ALEN] = {0xCF, 0x00, 0x00, 0x00, 0x00, 0x00}; - static const u8 stp_addr[ETH_ALEN] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x00}; struct batadv_priv *bat_priv = netdev_priv(mesh_iface); enum batadv_dhcp_recipient dhcp_rcp = BATADV_DHCP_NO; enum batadv_forw_mode forw_mode = BATADV_FORW_BCAST; + static const u8 ectp_addr[ETH_ALEN] __aligned(2) = { + 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const u8 stp_addr[ETH_ALEN] __aligned(2) = { + 0x01, 0x80, 0xC2, 0x00, 0x00, 0x00 + }; struct batadv_hard_iface *primary_if = NULL; struct batadv_bcast_packet *bcast_packet; + u8 chaddr[ETH_ALEN] __aligned(2); int network_offset = ETH_HLEN; unsigned int header_len = 0; unsigned long brd_delay = 0; @@ -229,7 +234,6 @@ static netdev_tx_t batadv_interface_tx(struct sk_buff *skb, struct ethhdr *ethhdr; bool do_bcast = false; u8 *dst_hint = NULL; - u8 chaddr[ETH_ALEN]; unsigned short vid; bool client_added; __be16 proto; diff --git a/net/batman-adv/multicast.c b/net/batman-adv/multicast.c index 82fd527b61c0..22dd28d5e1ab 100644 --- a/net/batman-adv/multicast.c +++ b/net/batman-adv/multicast.c @@ -368,9 +368,9 @@ batadv_mcast_mla_meshif_get_ipv4(struct net_device *dev, struct hlist_head *mcast_list, struct batadv_mcast_mla_flags *flags) { + u8 mcast_addr[ETH_ALEN] __aligned(2); struct batadv_hw_addr *new; struct in_device *in_dev; - u8 mcast_addr[ETH_ALEN]; struct ip_mc_list *pmc; int ret = 0; @@ -435,9 +435,9 @@ batadv_mcast_mla_meshif_get_ipv6(struct net_device *dev, struct hlist_head *mcast_list, struct batadv_mcast_mla_flags *flags) { + u8 mcast_addr[ETH_ALEN] __aligned(2); struct batadv_hw_addr *new; struct inet6_dev *in6_dev; - u8 mcast_addr[ETH_ALEN]; struct ifmcaddr6 *pmc6; int ret = 0; @@ -587,10 +587,10 @@ static int batadv_mcast_mla_bridge_get(struct net_device *dev, struct batadv_mcast_mla_flags *flags) { struct list_head bridge_mcast_list = LIST_HEAD_INIT(bridge_mcast_list); + u8 mcast_addr[ETH_ALEN] __aligned(2); u8 tvlv_flags = flags->tvlv_flags; struct br_ip_list *br_ip_entry; struct batadv_hw_addr *new; - u8 mcast_addr[ETH_ALEN]; struct br_ip_list *tmp; int ret; From d9304a90eca5703ae8301415b417ad63ddd44d7f Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sat, 22 Aug 2026 15:14:04 +0200 Subject: [PATCH 07/15] batman-adv: ensure u16 aligned mac address in structs An u8 array in structures have a natural alignment of only 1 byte. An u8[ETH_ALEN] array must therefore be assumed to only be 1 byte aligned. But many etherdevices.h functions are requiring a 2 byte (u16) alignment. All these addresses were either on the start of a struct or after a struct member which required at least an alignment of 2 byte. Still, annote all u8[ETH_ALEN] arrays on the stack as __aligned(2) to make sure that this assumption isn't broken by other modifications inside the structs. Signed-off-by: Sven Eckelmann --- net/batman-adv/types.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index 99667f632d4d..c42a4aa8f41a 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -410,7 +410,7 @@ struct batadv_orig_bat_iv { */ struct batadv_orig_node { /** @orig: originator ethernet address */ - u8 orig[ETH_ALEN]; + u8 orig[ETH_ALEN] __aligned(2); /** @ifinfo_list: list for routers per outgoing interface */ struct hlist_head ifinfo_list; @@ -634,12 +634,12 @@ struct batadv_hardif_neigh_node { struct hlist_node list; /** @addr: the MAC address of the neighboring interface */ - u8 addr[ETH_ALEN]; + u8 addr[ETH_ALEN] __aligned(2); /** * @orig: the address of the originator this neighbor node belongs to */ - u8 orig[ETH_ALEN]; + u8 orig[ETH_ALEN] __aligned(2); /** @if_incoming: pointer to incoming hard-interface */ struct batadv_hard_iface *if_incoming; @@ -677,7 +677,7 @@ struct batadv_neigh_node { #endif /** @addr: the MAC address of the neighboring interface */ - u8 addr[ETH_ALEN]; + u8 addr[ETH_ALEN] __aligned(2); /** @ifinfo_list: list for routing metrics per outgoing interface */ struct hlist_head ifinfo_list; @@ -777,7 +777,7 @@ struct batadv_neigh_ifinfo { */ struct batadv_bcast_duplist_entry { /** @orig: mac address of orig node originating the broadcast */ - u8 orig[ETH_ALEN]; + u8 orig[ETH_ALEN] __aligned(2); /** @crc: crc32 checksum of broadcast payload */ u32 crc; @@ -1085,7 +1085,7 @@ struct batadv_priv_bla { struct batadv_hashtable *backbone_hash; /** @loopdetect_addr: MAC address used for own loopdetection frames */ - u8 loopdetect_addr[ETH_ALEN]; + u8 loopdetect_addr[ETH_ALEN] __aligned(2); /** * @loopdetect_lasttime: time when the loopdetection frames were sent @@ -1352,7 +1352,7 @@ struct batadv_tp_vars_common { struct batadv_priv *bat_priv; /** @other_end: mac address of remote */ - u8 other_end[ETH_ALEN]; + u8 other_end[ETH_ALEN] __aligned(2); /** @session: TP session identifier */ u8 session[2]; @@ -1765,7 +1765,7 @@ struct batadv_bla_backbone_gw { * @orig: originator address of backbone node (mac address of primary * iface) */ - u8 orig[ETH_ALEN]; + u8 orig[ETH_ALEN] __aligned(2); /** @vid: vlan id this gateway was detected on */ unsigned short vid; @@ -1810,7 +1810,7 @@ struct batadv_bla_backbone_gw { */ struct batadv_bla_claim { /** @addr: mac address of claimed non-mesh client */ - u8 addr[ETH_ALEN]; + u8 addr[ETH_ALEN] __aligned(2); /** @vid: vlan id this client was detected on */ unsigned short vid; @@ -1840,7 +1840,7 @@ struct batadv_bla_claim { */ struct batadv_tt_common_entry { /** @addr: mac address of non-mesh client */ - u8 addr[ETH_ALEN]; + u8 addr[ETH_ALEN] __aligned(2); /** @vid: VLAN identifier */ unsigned short vid; @@ -1942,7 +1942,7 @@ struct batadv_tt_req_node { /** * @addr: mac address of the originator this request was sent to */ - u8 addr[ETH_ALEN]; + u8 addr[ETH_ALEN] __aligned(2); /** @issued_at: timestamp used for purging stale tt requests */ unsigned long issued_at; @@ -1959,7 +1959,7 @@ struct batadv_tt_req_node { */ struct batadv_tt_roam_node { /** @addr: mac address of the client in the roaming phase */ - u8 addr[ETH_ALEN]; + u8 addr[ETH_ALEN] __aligned(2); /** @vid: VLAN identifier */ u16 vid; @@ -2204,7 +2204,7 @@ struct batadv_hw_addr { struct hlist_node list; /** @addr: the MAC address of this list entry */ - unsigned char addr[ETH_ALEN]; + unsigned char addr[ETH_ALEN] __aligned(2); }; /** From bbfb0d2cf03538d3db0580849720bc5fe773d515 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 30 Aug 2026 17:21:38 +0200 Subject: [PATCH 08/15] batman-adv: tt: remove only the entry which was looked up from the hash batadv_hash_remove() searches a bucket with a compare callback and unlinks the first matching entry. batadv_compare_tt() matches any entry for the same MAC address and VLAN, not the object which was passed in, and the callers in batadv_tt_local_remove() and batadv_tt_global_free() are not serialized against the rest of the translation table in any way. So when the looked up entry was already unlinked by another context and a new entry for the same client was added in the meantime, these two functions unlink that new entry instead. Add batadv_compare_tt_entry(), which matches the very object which is searched for, and use it for both removals. Nothing is unlinked when the entry is gone already, batadv_hash_remove() then simply returns NULL and only the reference of the calling context is dropped. As a side effect the returned hlist_node can no longer belong to a different object, so both functions can operate on the entry they were given. Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 38 ++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 88aeefa97db6..66456a2d45e2 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -95,6 +95,26 @@ static bool batadv_compare_tt(const struct hlist_node *node, const void *data2) return (tt1->vid == tt2->vid) && batadv_compare_eth(data1, data2); } +/** + * batadv_compare_tt_entry() - check if a hash node is a specific TT entry + * @node: the list element pointer of the TT entry stored in the bucket + * @data2: pointer to the tt_common_entry which is looked for + * + * Unlike batadv_compare_tt(), this only matches the very object which is + * passed as @data2 and not just any entry for the same TT client. It is meant + * for batadv_hash_remove() callers which must not unlink an entry they did not + * look up themselves. + * + * Return: true if @node belongs to @data2, false otherwise + */ +static bool batadv_compare_tt_entry(const struct hlist_node *node, + const void *data2) +{ + const struct batadv_tt_common_entry *tt = data2; + + return node == &tt->hash_entry; +} + /** * batadv_choose_tt() - return the index of the tt entry in the hash table * @data: pointer to the tt_common_entry object to map @@ -575,7 +595,6 @@ static void batadv_tt_global_free(struct batadv_priv *bat_priv, struct batadv_tt_global_entry *tt_global, const char *message) { - struct batadv_tt_global_entry *tt_removed_entry; struct hlist_node *tt_removed_node; batadv_dbg(BATADV_DBG_TT, bat_priv, @@ -583,18 +602,16 @@ static void batadv_tt_global_free(struct batadv_priv *bat_priv, tt_global->common.addr, batadv_print_vid(tt_global->common.vid), message); + /* remove exactly this object when still present in hash */ tt_removed_node = batadv_hash_remove(bat_priv->tt.global_hash, - batadv_compare_tt, + batadv_compare_tt_entry, batadv_choose_tt, &tt_global->common); if (!tt_removed_node) return; /* drop reference of remove hash entry */ - tt_removed_entry = hlist_entry(tt_removed_node, - struct batadv_tt_global_entry, - common.hash_entry); - batadv_tt_global_entry_put(tt_removed_entry); + batadv_tt_global_entry_put(tt_global); } /** @@ -1292,7 +1309,6 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr, unsigned short vid, const char *message, bool roaming) { - struct batadv_tt_local_entry *tt_removed_entry; struct batadv_tt_local_entry *tt_local_entry; struct hlist_node *tt_removed_node; u16 curr_flags = BATADV_NO_FLAGS; @@ -1325,18 +1341,16 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr, */ 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, + batadv_compare_tt_entry, batadv_choose_tt, &tt_local_entry->common); if (!tt_removed_node) goto out; /* drop reference of remove hash entry */ - tt_removed_entry = hlist_entry(tt_removed_node, - struct batadv_tt_local_entry, - common.hash_entry); - batadv_tt_local_entry_put(tt_removed_entry); + batadv_tt_local_entry_put(tt_local_entry); out: batadv_tt_local_entry_put(tt_local_entry); From fd8d892c685ad08915e6cebf8ec125ccd01c8e6f Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 30 Aug 2026 17:21:39 +0200 Subject: [PATCH 09/15] batman-adv: tt: extract code handling a roam on add The code for handling the roaming of clients is mostly self contained but embedded in the already too large batadv_tt_local_add function. The maintainability can be improved by keeping it in an separate function. Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 71 +++++++++++++++++++----------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 66456a2d45e2..01c74d4a7e52 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -614,6 +614,50 @@ static void batadv_tt_global_free(struct batadv_priv *bat_priv, batadv_tt_global_entry_put(tt_global); } +/** + * batadv_tt_local_add_roam() - handle roamed clients during batadv_tt_local_add() + * @bat_priv: the bat priv with all the mesh interface information + * @tt_global: the global TT entry + * @roamed_back: whether @tt_global roamed back + */ +static void batadv_tt_local_add_roam(struct batadv_priv *bat_priv, + struct batadv_tt_global_entry *tt_global, + bool roamed_back) +{ + struct batadv_tt_orig_list_entry *orig_entry; + struct hlist_head *head; + + if (!tt_global) + return; + + /* Check whether it is a roaming, but don't do anything if the roaming + * process has already been handled + */ + if (tt_global->common.flags & BATADV_TT_CLIENT_ROAM) + return; + + /* These node are probably going to update their tt table */ + head = &tt_global->orig_list; + rcu_read_lock(); + hlist_for_each_entry_rcu(orig_entry, head, list) { + batadv_send_roam_adv(bat_priv, tt_global->common.addr, + tt_global->common.vid, + orig_entry->orig_node); + } + rcu_read_unlock(); + + if (roamed_back) { + batadv_tt_global_free(bat_priv, tt_global, + "Roaming canceled"); + } else { + /* The global entry has to be marked as ROAMING and + * has to be kept for consistency purpose + */ + tt_global->common.flags |= BATADV_TT_CLIENT_ROAM; + tt_global->roam_at = jiffies; + } +} + /** * batadv_tt_local_add() - add a new client to the local table or update an * existing client @@ -632,14 +676,12 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, { struct batadv_priv *bat_priv = netdev_priv(mesh_iface); struct batadv_tt_global_entry *tt_global = NULL; - struct batadv_tt_orig_list_entry *orig_entry; struct batadv_tt_local_entry *tt_local; struct net *net = dev_net(mesh_iface); struct net_device *in_dev = NULL; struct batadv_meshif_vlan *vlan; bool roamed_back = false; bool iif_is_wifi = false; - struct hlist_head *head; int packet_size_max; bool ret = false; u8 remote_flags; @@ -758,30 +800,7 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, batadv_tt_local_event(bat_priv, tt_local, BATADV_NO_FLAGS); check_roaming: - /* Check whether it is a roaming, but don't do anything if the roaming - * process has already been handled - */ - if (tt_global && !(tt_global->common.flags & BATADV_TT_CLIENT_ROAM)) { - /* These node are probably going to update their tt table */ - head = &tt_global->orig_list; - rcu_read_lock(); - hlist_for_each_entry_rcu(orig_entry, head, list) { - batadv_send_roam_adv(bat_priv, tt_global->common.addr, - tt_global->common.vid, - orig_entry->orig_node); - } - rcu_read_unlock(); - if (roamed_back) { - batadv_tt_global_free(bat_priv, tt_global, - "Roaming canceled"); - } else { - /* The global entry has to be marked as ROAMING and - * has to be kept for consistency purpose - */ - tt_global->common.flags |= BATADV_TT_CLIENT_ROAM; - tt_global->roam_at = jiffies; - } - } + batadv_tt_local_add_roam(bat_priv, tt_global, roamed_back); /* store the current remote flags before altering them. This helps * understanding is flags are changing or not From e103fbefa049d82cac3bec350059e872efecc331 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 30 Aug 2026 17:21:40 +0200 Subject: [PATCH 10/15] batman-adv: tt: simplify NEW flag transition code The function batadv_tt_local_set_flags() implements support for various flags and to set/unset them. It is also possible to decide whether this change should increase the TT size or not. But in reality, this function is only used to transition tt local entries from the NEW state and count these entries. Remove the rest of the code to simplify the function. Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 01c74d4a7e52..2f7951e4c320 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -3815,15 +3815,11 @@ void batadv_tt_free(struct batadv_priv *bat_priv) } /** - * batadv_tt_local_set_flags() - set or unset the specified flags on the local - * table and possibly count them in the TT size + * batadv_tt_local_transition_new() - unset the NEW flag on the local + * table and count them in the TT size * @bat_priv: the bat priv with all the mesh interface information - * @flags: the flag to switch - * @enable: whether to set or unset the flag - * @count: whether to increase the TT size by the number of changed entries */ -static void batadv_tt_local_set_flags(struct batadv_priv *bat_priv, u16 flags, - bool enable, bool count) +static void batadv_tt_local_transition_new(struct batadv_priv *bat_priv) { struct batadv_hashtable *hash = bat_priv->tt.local_hash; struct batadv_tt_common_entry *tt_common_entry; @@ -3839,19 +3835,11 @@ static void batadv_tt_local_set_flags(struct batadv_priv *bat_priv, u16 flags, rcu_read_lock(); hlist_for_each_entry_rcu(tt_common_entry, head, hash_entry) { - if (enable) { - if ((tt_common_entry->flags & flags) == flags) - continue; - tt_common_entry->flags |= flags; - } else { - if (!(tt_common_entry->flags & flags)) - continue; - tt_common_entry->flags &= ~flags; - } - - if (!count) + if (!(tt_common_entry->flags & BATADV_TT_CLIENT_NEW)) continue; + tt_common_entry->flags &= ~BATADV_TT_CLIENT_NEW; + batadv_tt_local_size_inc(bat_priv, tt_common_entry->vid); } @@ -3924,7 +3912,7 @@ static void batadv_tt_local_commit_changes_nolock(struct batadv_priv *bat_priv) return; } - batadv_tt_local_set_flags(bat_priv, BATADV_TT_CLIENT_NEW, false, true); + batadv_tt_local_transition_new(bat_priv); batadv_tt_local_purge_pending_clients(bat_priv); batadv_tt_local_update_crc(bat_priv); From 52d8c713f464448a055025dab4ceff6afcc68e02 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 30 Aug 2026 17:21:41 +0200 Subject: [PATCH 11/15] batman-adv: tt: drop unnecessary cleanup goto in helpers To use scoped_guard(), it is not allowed to use goto in the same routine. These goto's should only be used for cleanups but are not necessary of minimal helpers like batadv_is_my_client(), batadv_tt_global_client_is_roaming() and batadv_tt_local_client_is_roaming(). Removing the goto's is actually making these functions more readable. Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 2f7951e4c320..fbe75e184153 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -3544,19 +3544,18 @@ bool batadv_is_my_client(struct batadv_priv *bat_priv, const u8 *addr, unsigned short vid) { struct batadv_tt_local_entry *tt_local_entry; - bool ret = false; + bool ret; tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid); if (!tt_local_entry) - goto out; + return false; + /* Check if the client has been logically deleted (but is kept for * consistency purpose) */ - if ((tt_local_entry->common.flags & BATADV_TT_CLIENT_PENDING) || - (tt_local_entry->common.flags & BATADV_TT_CLIENT_ROAM)) - goto out; - ret = true; -out: + ret = !((tt_local_entry->common.flags & BATADV_TT_CLIENT_PENDING) || + (tt_local_entry->common.flags & BATADV_TT_CLIENT_ROAM)); + batadv_tt_local_entry_put(tt_local_entry); return ret; } @@ -4082,15 +4081,15 @@ bool batadv_tt_global_client_is_roaming(struct batadv_priv *bat_priv, u8 *addr, unsigned short vid) { struct batadv_tt_global_entry *tt_global_entry; - bool ret = false; + bool ret; tt_global_entry = batadv_tt_global_hash_find(bat_priv, addr, vid); if (!tt_global_entry) - goto out; + return false; ret = tt_global_entry->common.flags & BATADV_TT_CLIENT_ROAM; batadv_tt_global_entry_put(tt_global_entry); -out: + return ret; } @@ -4108,15 +4107,15 @@ bool batadv_tt_local_client_is_roaming(struct batadv_priv *bat_priv, u8 *addr, unsigned short vid) { struct batadv_tt_local_entry *tt_local_entry; - bool ret = false; + bool ret; tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid); if (!tt_local_entry) - goto out; + return false; ret = tt_local_entry->common.flags & BATADV_TT_CLIENT_ROAM; batadv_tt_local_entry_put(tt_local_entry); -out: + return ret; } From 976b159b3c12be89459390bdf950534f639a5200 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 30 Aug 2026 17:21:42 +0200 Subject: [PATCH 12/15] batman-adv: tt: use protected flag modifications The flags of translation table entries are modified in various places using RMW operations like: * read flags + add flag + store * read flags + remove flag + store These were done without making sure that no other context is doing a similar operation at the same time. If another context does modify the flags then it could happen that a store of the flag modifications is simply lost. This problem can usually be fixed at a later point when the flags are tried to be adjusted again. To reduce the time the wrong flags are used, it is better to use a TT entry specific spinlock when accessing the u16. Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 351 ++++++++++++++++++++--------- net/batman-adv/types.h | 11 +- 2 files changed, 255 insertions(+), 107 deletions(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index fbe75e184153..163f90962306 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -433,6 +434,22 @@ batadv_tt_orig_list_entry_put(struct batadv_tt_orig_list_entry *orig_entry) kref_put(&orig_entry->refcount, batadv_tt_orig_list_entry_release); } +/** + * batadv_tt_flags_get() - get a snapshot of the flags of a TT entry + * @common: tt local & tt global common data + * + * Return: the flags of the TT entry as observed under its flags_lock. + */ +static u16 batadv_tt_flags_get(struct batadv_tt_common_entry *common) +{ + u16 flags; + + scoped_guard(spinlock_bh, &common->flags_lock) + flags = common->flags; + + return flags; +} + /** * batadv_tt_local_event() - store a local TT event (ADD/DEL) * @bat_priv: the bat priv with all the mesh interface information @@ -445,23 +462,25 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv, { struct batadv_tt_common_entry *common = &tt_local_entry->common; struct batadv_tt_change_node *tt_change_node; - u8 flags = common->flags | event_flags; 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) return; - tt_change_node->change.flags = flags; memset(tt_change_node->change.reserved, 0, sizeof(tt_change_node->change.reserved)); 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; /* check for ADD+DEL, DEL+ADD, ADD+ADD or DEL+DEL events */ @@ -633,8 +652,23 @@ static void batadv_tt_local_add_roam(struct batadv_priv *bat_priv, /* Check whether it is a roaming, but don't do anything if the roaming * process has already been handled */ - if (tt_global->common.flags & BATADV_TT_CLIENT_ROAM) - return; + scoped_guard(spinlock_bh, &tt_global->common.flags_lock) { + if (tt_global->common.flags & BATADV_TT_CLIENT_ROAM) + return; + + if (!roamed_back) { + /* The global entry has to be marked as ROAMING and has to be + * kept for consistency purpose. + * + * batadv_tt_global_to_purge() evaluates roam_at as soon as it + * observes BATADV_TT_CLIENT_ROAM, so the timeout has to be + * stamped before the flag is published. Otherwise the entry can + * be deleted right away as "Roaming timeout". + */ + tt_global->roam_at = jiffies; + tt_global->common.flags |= BATADV_TT_CLIENT_ROAM; + } + } /* These node are probably going to update their tt table */ head = &tt_global->orig_list; @@ -646,16 +680,8 @@ static void batadv_tt_local_add_roam(struct batadv_priv *bat_priv, } rcu_read_unlock(); - if (roamed_back) { - batadv_tt_global_free(bat_priv, tt_global, - "Roaming canceled"); - } else { - /* The global entry has to be marked as ROAMING and - * has to be kept for consistency purpose - */ - tt_global->common.flags |= BATADV_TT_CLIENT_ROAM; - tt_global->roam_at = jiffies; - } + if (roamed_back) + batadv_tt_global_free(bat_priv, tt_global, "Roaming canceled"); } /** @@ -688,6 +714,7 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, int hash_added; int table_size; u32 match_mark; + bool modified; if (ifindex != BATADV_NULL_IFINDEX) in_dev = dev_get_by_index(net, ifindex); @@ -705,6 +732,8 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, if (tt_local) { tt_local->last_seen = jiffies; + + spin_lock_bh(&tt_local->common.flags_lock); if (tt_local->common.flags & BATADV_TT_CLIENT_PENDING) { batadv_dbg(BATADV_DBG_TT, bat_priv, "Re-adding pending client %pM (vid: %d)\n", @@ -715,6 +744,8 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, * flag can be reset like it was never enqueued */ tt_local->common.flags &= ~BATADV_TT_CLIENT_PENDING; + spin_unlock_bh(&tt_local->common.flags_lock); + goto add_event; } @@ -730,6 +761,8 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, tt_local->common.flags &= ~BATADV_TT_CLIENT_ROAM; roamed_back = true; } + spin_unlock_bh(&tt_local->common.flags_lock); + goto check_roaming; } @@ -765,18 +798,21 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, (u8)atomic_read(&bat_priv->tt.vn)); ether_addr_copy(tt_local->common.addr, addr); + tt_local->common.vid = vid; + kref_init(&tt_local->common.refcount); + tt_local->last_seen = jiffies; + tt_local->common.added_at = tt_local->last_seen; + tt_local->vlan = vlan; + spin_lock_init(&tt_local->common.flags_lock); + + spin_lock_bh(&tt_local->common.flags_lock); /* The local entry has to be marked as NEW to avoid to send it in * a full table response going out before the next ttvn increment * (consistency check) */ tt_local->common.flags = BATADV_TT_CLIENT_NEW; - tt_local->common.vid = vid; if (iif_is_wifi) tt_local->common.flags |= BATADV_TT_CLIENT_WIFI; - kref_init(&tt_local->common.refcount); - tt_local->last_seen = jiffies; - tt_local->common.added_at = tt_local->last_seen; - tt_local->vlan = vlan; /* the batman interface mac and multicast addresses should never be * purged @@ -784,6 +820,7 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, if (batadv_compare_eth(addr, mesh_iface->dev_addr) || is_multicast_ether_addr(addr)) tt_local->common.flags |= BATADV_TT_CLIENT_NOPURGE; + spin_unlock_bh(&tt_local->common.flags_lock); kref_get(&tt_local->common.refcount); hash_added = batadv_hash_add(bat_priv->tt.local_hash, batadv_compare_tt, @@ -802,6 +839,7 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, check_roaming: batadv_tt_local_add_roam(bat_priv, tt_global, roamed_back); + spin_lock_bh(&tt_local->common.flags_lock); /* store the current remote flags before altering them. This helps * understanding is flags are changing or not */ @@ -823,10 +861,13 @@ bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr, else tt_local->common.flags &= ~BATADV_TT_CLIENT_ISOLA; + modified = remote_flags ^ (tt_local->common.flags & BATADV_TT_REMOTE_MASK); + spin_unlock_bh(&tt_local->common.flags_lock); + /* if any "dynamic" flag has been modified, resend an ADD event for this * entry so that all the nodes can get the new flags */ - if (remote_flags ^ (tt_local->common.flags & BATADV_TT_REMOTE_MASK)) + if (modified) batadv_tt_local_event(bat_priv, tt_local, BATADV_NO_FLAGS); ret = true; @@ -1151,6 +1192,7 @@ batadv_tt_local_dump_entry(struct sk_buff *msg, u32 portid, struct batadv_meshif_vlan *vlan; unsigned int last_seen_msecs; void *hdr; + u16 flags; u32 crc; local = container_of(common, struct batadv_tt_local_entry, common); @@ -1172,13 +1214,15 @@ batadv_tt_local_dump_entry(struct sk_buff *msg, u32 portid, genl_dump_check_consistent(cb, hdr); + flags = batadv_tt_flags_get(common); + if (nla_put(msg, BATADV_ATTR_TT_ADDRESS, ETH_ALEN, common->addr) || nla_put_u32(msg, BATADV_ATTR_TT_CRC32, crc) || nla_put_u16(msg, BATADV_ATTR_TT_VID, common->vid) || - nla_put_u32(msg, BATADV_ATTR_TT_FLAGS, common->flags)) + nla_put_u32(msg, BATADV_ATTR_TT_FLAGS, flags)) goto nla_put_failure; - if (!(common->flags & BATADV_TT_CLIENT_NOPURGE) && + if (!(flags & BATADV_TT_CLIENT_NOPURGE) && nla_put_u32(msg, BATADV_ATTR_LAST_SEEN_MSECS, last_seen_msecs)) goto nla_put_failure; @@ -1285,29 +1329,23 @@ int batadv_tt_local_dump(struct sk_buff *msg, struct netlink_callback *cb) } /** - * batadv_tt_local_set_pending() - mark a local TT entry as pending removal + * batadv_tt_local_set_pending_event() - trigger events for TT pending removal * @bat_priv: the bat priv with all the mesh interface information * @tt_local_entry: local TT entry to mark * @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 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. + * Schedule the TT change announcement for 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. */ static void -batadv_tt_local_set_pending(struct batadv_priv *bat_priv, - struct batadv_tt_local_entry *tt_local_entry, - u16 flags, const char *message) +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_event(bat_priv, tt_local_entry, flags); - /* The local client has to be marked as "pending to be removed" but has - * to be kept in the table in order to send it in a full table - * response issued before the net ttvn increment (consistency check) - */ - tt_local_entry->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", tt_local_entry->common.addr, @@ -1331,12 +1369,14 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr, struct batadv_tt_local_entry *tt_local_entry; struct hlist_node *tt_removed_node; u16 curr_flags = BATADV_NO_FLAGS; + bool pending = false; u16 flags; tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid); if (!tt_local_entry) goto out; + spin_lock_bh(&tt_local_entry->common.flags_lock); curr_flags = tt_local_entry->common.flags; flags = BATADV_TT_CLIENT_DEL; @@ -1351,10 +1391,17 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr, } if (!(tt_local_entry->common.flags & BATADV_TT_CLIENT_NEW)) { - batadv_tt_local_set_pending(bat_priv, tt_local_entry, flags, - message); + tt_local_entry->common.flags |= BATADV_TT_CLIENT_PENDING; + pending = true; + } + spin_unlock_bh(&tt_local_entry->common.flags_lock); + + if (pending) { + batadv_tt_local_set_pending_event(bat_priv, tt_local_entry, flags, + message); goto out; } + /* if this client has been added right now, it is possible to * immediately purge it */ @@ -1394,21 +1441,37 @@ 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); - if (tt_local_entry->common.flags & BATADV_TT_CLIENT_NOPURGE) + + scoped_guard(spinlock_bh, &tt_local_entry->common.flags_lock) { + if (tt_local_entry->common.flags & BATADV_TT_CLIENT_NOPURGE) { + cont = true; + break; + } + + /* entry already marked for deletion */ + if (tt_local_entry->common.flags & BATADV_TT_CLIENT_PENDING) { + cont = true; + break; + } + + if (!batadv_has_timed_out(tt_local_entry->last_seen, timeout)) { + cont = true; + break; + } + + tt_local_entry->common.flags |= BATADV_TT_CLIENT_PENDING; + } + + if (cont) continue; - /* entry already marked for deletion */ - if (tt_local_entry->common.flags & BATADV_TT_CLIENT_PENDING) - continue; - - if (!batadv_has_timed_out(tt_local_entry->last_seen, timeout)) - continue; - - batadv_tt_local_set_pending(bat_priv, tt_local_entry, - BATADV_TT_CLIENT_DEL, "timed out"); + batadv_tt_local_set_pending_event(bat_priv, tt_local_entry, + BATADV_TT_CLIENT_DEL, "timed out"); } } @@ -1614,8 +1677,10 @@ batadv_tt_global_sync_flags(struct batadv_tt_global_entry *tt_global) flags |= orig_entry->flags; rcu_read_unlock(); - flags |= tt_global->common.flags & (~BATADV_TT_SYNC_MASK); - tt_global->common.flags = flags; + scoped_guard(spinlock_bh, &tt_global->common.flags_lock) { + flags |= tt_global->common.flags & (~BATADV_TT_SYNC_MASK); + tt_global->common.flags = flags; + } } /** @@ -1697,8 +1762,10 @@ static bool batadv_tt_global_add(struct batadv_priv *bat_priv, struct batadv_tt_local_entry *tt_local_entry; struct batadv_tt_common_entry *common; bool ret = false; + u16 global_flags; u16 local_flags; int hash_added; + bool delete; /* ignore global entries from backbone nodes */ if (batadv_bla_is_backbone_gw_orig(bat_priv, orig_node->orig, vid)) @@ -1711,9 +1778,11 @@ static bool batadv_tt_global_add(struct batadv_priv *bat_priv, * for a roaming advertisement instead of manually messing up the global * table */ - if ((flags & BATADV_TT_CLIENT_TEMP) && tt_local_entry && - !(tt_local_entry->common.flags & BATADV_TT_CLIENT_NEW)) - goto out; + if ((flags & BATADV_TT_CLIENT_TEMP) && tt_local_entry) { + local_flags = batadv_tt_flags_get(&tt_local_entry->common); + if (!(local_flags & BATADV_TT_CLIENT_NEW)) + goto out; + } if (!tt_global_entry) { tt_global_entry = kmem_cache_zalloc(batadv_tg_cache, @@ -1724,9 +1793,13 @@ static bool batadv_tt_global_add(struct batadv_priv *bat_priv, common = &tt_global_entry->common; ether_addr_copy(common->addr, tt_addr); common->vid = vid; + spin_lock_init(&common->flags_lock); - if (!is_multicast_ether_addr(common->addr)) + if (!is_multicast_ether_addr(common->addr)) { + spin_lock_bh(&common->flags_lock); common->flags = flags & (~BATADV_TT_SYNC_MASK); + spin_unlock_bh(&common->flags_lock); + } tt_global_entry->roam_at = 0; /* node must store current time in case of roaming. This is @@ -1766,8 +1839,10 @@ static bool batadv_tt_global_add(struct batadv_priv *bat_priv, * originator list and add the new one orig_entry */ if (flags & BATADV_TT_CLIENT_TEMP) { - if (!(common->flags & BATADV_TT_CLIENT_TEMP)) + global_flags = batadv_tt_flags_get(common); + if (!(global_flags & BATADV_TT_CLIENT_TEMP)) goto out; + if (batadv_tt_global_entry_has_orig(tt_global_entry, orig_node, NULL)) goto out_remove; @@ -1775,6 +1850,9 @@ static bool batadv_tt_global_add(struct batadv_priv *bat_priv, goto add_orig_entry; } + delete = false; + + spin_lock_bh(&common->flags_lock); /* if the client was temporary added before receiving the first * OGM announcing it, we have to clear the TEMP flag. Also, * remove the previous temporary orig node and re-add it @@ -1782,7 +1860,7 @@ static bool batadv_tt_global_add(struct batadv_priv *bat_priv, * is a non-temporary entry is preferred. */ if (common->flags & BATADV_TT_CLIENT_TEMP) { - batadv_tt_global_del_orig_list(tt_global_entry); + delete = true; common->flags &= ~BATADV_TT_CLIENT_TEMP; } @@ -1801,10 +1879,14 @@ static bool batadv_tt_global_add(struct batadv_priv *bat_priv, * new one. */ if (common->flags & BATADV_TT_CLIENT_ROAM) { - batadv_tt_global_del_orig_list(tt_global_entry); - common->flags &= ~BATADV_TT_CLIENT_ROAM; + delete = true; tt_global_entry->roam_at = 0; + common->flags &= ~BATADV_TT_CLIENT_ROAM; } + spin_unlock_bh(&common->flags_lock); + + if (delete) + batadv_tt_global_del_orig_list(tt_global_entry); } add_orig_entry: /* add the new orig_entry (if needed) or update it */ @@ -1828,6 +1910,8 @@ static bool batadv_tt_global_add(struct batadv_priv *bat_priv, local_flags = batadv_tt_local_remove(bat_priv, tt_addr, vid, "global tt received", flags & BATADV_TT_CLIENT_ROAM); + + spin_lock_bh(&tt_global_entry->common.flags_lock); tt_global_entry->common.flags |= local_flags & BATADV_TT_CLIENT_WIFI; if (!(flags & BATADV_TT_CLIENT_ROAM)) @@ -1835,6 +1919,7 @@ static bool batadv_tt_global_add(struct batadv_priv *bat_priv, * roaming state anymore. */ tt_global_entry->common.flags &= ~BATADV_TT_CLIENT_ROAM; + spin_unlock_bh(&tt_global_entry->common.flags_lock); out: batadv_tt_global_entry_put(tt_global_entry); @@ -1904,9 +1989,9 @@ batadv_tt_global_dump_subentry(struct sk_buff *msg, u32 portid, u32 seq, struct batadv_tt_orig_list_entry *orig, bool best) { - u16 flags = (common->flags & (~BATADV_TT_SYNC_MASK)) | orig->flags; struct batadv_orig_node_vlan *vlan; u8 last_ttvn; + u16 flags; void *hdr; u32 crc; @@ -1925,6 +2010,7 @@ batadv_tt_global_dump_subentry(struct sk_buff *msg, u32 portid, u32 seq, if (!hdr) return -ENOBUFS; + flags = (batadv_tt_flags_get(common) & (~BATADV_TT_SYNC_MASK)) | orig->flags; last_ttvn = READ_ONCE(orig->orig_node->last_ttvn); if (nla_put(msg, BATADV_ATTR_TT_ADDRESS, ETH_ALEN, common->addr) || @@ -2212,9 +2298,12 @@ batadv_tt_global_del_roaming(struct batadv_priv *bat_priv, rcu_read_unlock(); if (last_entry) { - /* its the last one, mark for roaming. */ - tt_global_entry->common.flags |= BATADV_TT_CLIENT_ROAM; - tt_global_entry->roam_at = jiffies; + scoped_guard(spinlock_bh, &tt_global_entry->common.flags_lock) { + /* its the last one, mark for roaming. */ + tt_global_entry->roam_at = jiffies; + + tt_global_entry->common.flags |= BATADV_TT_CLIENT_ROAM; + } } else { /* there is another entry, we can simply delete this * one and can still use the other one. @@ -2363,16 +2452,18 @@ static bool batadv_tt_global_to_purge(struct batadv_tt_global_entry *tt_global, unsigned long temp_timeout = BATADV_TT_CLIENT_TEMP_TIMEOUT; bool purge = false; - if ((tt_global->common.flags & BATADV_TT_CLIENT_ROAM) && - batadv_has_timed_out(tt_global->roam_at, roam_timeout)) { - purge = true; - *msg = "Roaming timeout\n"; - } + scoped_guard(spinlock_bh, &tt_global->common.flags_lock) { + if ((tt_global->common.flags & BATADV_TT_CLIENT_ROAM) && + batadv_has_timed_out(tt_global->roam_at, roam_timeout)) { + purge = true; + *msg = "Roaming timeout\n"; + } - if ((tt_global->common.flags & BATADV_TT_CLIENT_TEMP) && - batadv_has_timed_out(tt_global->common.added_at, temp_timeout)) { - purge = true; - *msg = "Temporary client timeout\n"; + if ((tt_global->common.flags & BATADV_TT_CLIENT_TEMP) && + batadv_has_timed_out(tt_global->common.added_at, temp_timeout)) { + purge = true; + *msg = "Temporary client timeout\n"; + } } return purge; @@ -2481,13 +2572,19 @@ static bool _batadv_is_ap_isolated(struct batadv_tt_local_entry *tt_local_entry, struct batadv_tt_global_entry *tt_global_entry) { - if (tt_local_entry->common.flags & BATADV_TT_CLIENT_WIFI && - tt_global_entry->common.flags & BATADV_TT_CLIENT_WIFI) + u16 global_flags; + u16 local_flags; + + global_flags = batadv_tt_flags_get(&tt_global_entry->common); + local_flags = batadv_tt_flags_get(&tt_local_entry->common); + + if (local_flags & BATADV_TT_CLIENT_WIFI && + global_flags & BATADV_TT_CLIENT_WIFI) return true; /* check if the two clients are marked as isolated */ - if (tt_local_entry->common.flags & BATADV_TT_CLIENT_ISOLA && - tt_global_entry->common.flags & BATADV_TT_CLIENT_ISOLA) + if (local_flags & BATADV_TT_CLIENT_ISOLA && + global_flags & BATADV_TT_CLIENT_ISOLA) return true; return false; @@ -2516,11 +2613,15 @@ struct batadv_orig_node *batadv_transtable_search(struct batadv_priv *bat_priv, struct batadv_tt_local_entry *tt_local_entry = NULL; struct batadv_tt_orig_list_entry *best_entry; struct batadv_orig_node *orig_node = NULL; + u16 flags; if (src && batadv_vlan_ap_isola_get(bat_priv, vid)) { tt_local_entry = batadv_tt_local_hash_find(bat_priv, src, vid); - if (!tt_local_entry || - (tt_local_entry->common.flags & BATADV_TT_CLIENT_PENDING)) + if (!tt_local_entry) + goto out; + + flags = batadv_tt_flags_get(&tt_local_entry->common); + if (flags & BATADV_TT_CLIENT_PENDING) goto out; } @@ -2595,6 +2696,8 @@ static u32 batadv_tt_global_crc(struct batadv_priv *bat_priv, rcu_read_lock(); hlist_for_each_entry_rcu(tt_common, head, hash_entry) { + u16 tt_flags; + tt_global = container_of(tt_common, struct batadv_tt_global_entry, common); @@ -2604,18 +2707,21 @@ static u32 batadv_tt_global_crc(struct batadv_priv *bat_priv, if (tt_common->vid != vid) continue; + tt_flags = batadv_tt_flags_get(tt_common); + /* Roaming clients are in the global table for * consistency only. They don't have to be * taken into account while computing the * global crc */ - if (tt_common->flags & BATADV_TT_CLIENT_ROAM) + if (tt_flags & BATADV_TT_CLIENT_ROAM) continue; + /* Temporary clients have not been announced yet, so * they have to be skipped while computing the global * crc */ - if (tt_common->flags & BATADV_TT_CLIENT_TEMP) + if (tt_flags & BATADV_TT_CLIENT_TEMP) continue; /* find out if this global entry is announced by this @@ -2675,18 +2781,24 @@ static u32 batadv_tt_local_crc(struct batadv_priv *bat_priv, rcu_read_lock(); hlist_for_each_entry_rcu(tt_common, head, hash_entry) { + u16 tt_flags; + /* compute the CRC only for entries belonging to the * VLAN identified by vid */ if (tt_common->vid != vid) continue; + tt_flags = batadv_tt_flags_get(tt_common); + /* not yet committed clients have not to be taken into * account while computing the CRC */ - if (tt_common->flags & BATADV_TT_CLIENT_NEW) + if (tt_flags & BATADV_TT_CLIENT_NEW) continue; + flags = tt_flags & BATADV_TT_SYNC_MASK; + /* use network order to read the VID: this ensures that * every node reads the bytes in the same order. */ @@ -2696,7 +2808,6 @@ static u32 batadv_tt_local_crc(struct batadv_priv *bat_priv, /* compute the CRC on flags that have to be kept in sync * among nodes */ - flags = tt_common->flags & BATADV_TT_SYNC_MASK; crc_tmp = crc32c(crc_tmp, &flags, sizeof(flags)); crc ^= crc32c(crc_tmp, tt_common->addr, ETH_ALEN); @@ -2854,17 +2965,19 @@ batadv_tt_req_node_new(struct batadv_priv *bat_priv, * * Return: true if the entry is a valid, false otherwise. */ -static bool batadv_tt_local_valid(const void *entry_ptr, +static bool batadv_tt_local_valid(void *entry_ptr, const void *data_ptr, u8 *flags) { - const struct batadv_tt_common_entry *tt_common_entry = entry_ptr; + struct batadv_tt_common_entry *tt_common_entry = entry_ptr; + u16 tt_flags; - if (tt_common_entry->flags & BATADV_TT_CLIENT_NEW) + tt_flags = batadv_tt_flags_get(tt_common_entry); + if (tt_flags & BATADV_TT_CLIENT_NEW) return false; if (flags) - *flags = tt_common_entry->flags; + *flags = tt_flags; return true; } @@ -2881,16 +2994,18 @@ static bool batadv_tt_local_valid(const void *entry_ptr, * * Return: true if the entry is a valid, false otherwise. */ -static bool batadv_tt_global_valid(const void *entry_ptr, +static bool batadv_tt_global_valid(void *entry_ptr, const void *data_ptr, u8 *flags) { - const struct batadv_tt_common_entry *tt_common_entry = entry_ptr; - const struct batadv_tt_global_entry *tt_global_entry; + struct batadv_tt_common_entry *tt_common_entry = entry_ptr; const struct batadv_orig_node *orig_node = data_ptr; + struct batadv_tt_global_entry *tt_global_entry; + u16 tt_flags; - if (tt_common_entry->flags & BATADV_TT_CLIENT_ROAM || - tt_common_entry->flags & BATADV_TT_CLIENT_TEMP) + tt_flags = batadv_tt_flags_get(tt_common_entry); + if (tt_flags & BATADV_TT_CLIENT_ROAM || + tt_flags & BATADV_TT_CLIENT_TEMP) return false; tt_global_entry = container_of(tt_common_entry, @@ -2919,7 +3034,7 @@ static bool batadv_tt_global_valid(const void *entry_ptr, static u16 batadv_tt_tvlv_generate(struct batadv_priv *bat_priv, struct batadv_hashtable *hash, void *tvlv_buff, u16 tt_len, - bool (*valid_cb)(const void *, + bool (*valid_cb)(void *, const void *, u8 *flags), void *cb_data) @@ -3544,17 +3659,20 @@ bool batadv_is_my_client(struct batadv_priv *bat_priv, const u8 *addr, unsigned short vid) { struct batadv_tt_local_entry *tt_local_entry; + u16 tt_flags; bool ret; tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid); if (!tt_local_entry) return false; + tt_flags = batadv_tt_flags_get(&tt_local_entry->common); + /* Check if the client has been logically deleted (but is kept for * consistency purpose) */ - ret = !((tt_local_entry->common.flags & BATADV_TT_CLIENT_PENDING) || - (tt_local_entry->common.flags & BATADV_TT_CLIENT_ROAM)); + ret = !((tt_flags & BATADV_TT_CLIENT_PENDING) || + (tt_flags & BATADV_TT_CLIENT_ROAM)); batadv_tt_local_entry_put(tt_local_entry); return ret; @@ -3834,10 +3952,19 @@ static void batadv_tt_local_transition_new(struct batadv_priv *bat_priv) rcu_read_lock(); hlist_for_each_entry_rcu(tt_common_entry, head, hash_entry) { - if (!(tt_common_entry->flags & BATADV_TT_CLIENT_NEW)) - continue; + bool cont = false; - tt_common_entry->flags &= ~BATADV_TT_CLIENT_NEW; + scoped_guard(spinlock_bh, &tt_common_entry->flags_lock) { + if (!(tt_common_entry->flags & BATADV_TT_CLIENT_NEW)) { + cont = true; + break; + } + + tt_common_entry->flags &= ~BATADV_TT_CLIENT_NEW; + } + + if (cont) + continue; batadv_tt_local_size_inc(bat_priv, tt_common_entry->vid); @@ -3874,16 +4001,26 @@ static void batadv_tt_local_purge_pending_clients(struct batadv_priv *bat_priv) spin_lock_bh(list_lock); hlist_for_each_entry_safe(tt_common, node_tmp, head, hash_entry) { - if (!(tt_common->flags & BATADV_TT_CLIENT_PENDING)) + bool cont = false; + + scoped_guard(spinlock_bh, &tt_common->flags_lock) { + if (!(tt_common->flags & BATADV_TT_CLIENT_PENDING)) { + cont = true; + break; + } + + batadv_dbg(BATADV_DBG_TT, bat_priv, + "Deleting local tt entry (%pM, vid: %d): pending\n", + tt_common->addr, + batadv_print_vid(tt_common->vid)); + + batadv_tt_local_size_dec(bat_priv, tt_common->vid); + hlist_del_rcu(&tt_common->hash_entry); + } + + if (cont) continue; - batadv_dbg(BATADV_DBG_TT, bat_priv, - "Deleting local tt entry (%pM, vid: %d): pending\n", - tt_common->addr, - batadv_print_vid(tt_common->vid)); - - batadv_tt_local_size_dec(bat_priv, tt_common->vid); - hlist_del_rcu(&tt_common->hash_entry); tt_local = container_of(tt_common, struct batadv_tt_local_entry, common); @@ -4087,7 +4224,8 @@ bool batadv_tt_global_client_is_roaming(struct batadv_priv *bat_priv, if (!tt_global_entry) return false; - ret = tt_global_entry->common.flags & BATADV_TT_CLIENT_ROAM; + ret = batadv_tt_flags_get(&tt_global_entry->common) & BATADV_TT_CLIENT_ROAM; + batadv_tt_global_entry_put(tt_global_entry); return ret; @@ -4113,7 +4251,8 @@ bool batadv_tt_local_client_is_roaming(struct batadv_priv *bat_priv, if (!tt_local_entry) return false; - ret = tt_local_entry->common.flags & BATADV_TT_CLIENT_ROAM; + ret = batadv_tt_flags_get(&tt_local_entry->common) & BATADV_TT_CLIENT_ROAM; + batadv_tt_local_entry_put(tt_local_entry); return ret; @@ -4424,7 +4563,7 @@ bool batadv_tt_global_is_isolated(struct batadv_priv *bat_priv, if (!tt) return false; - ret = tt->common.flags & BATADV_TT_CLIENT_ISOLA; + ret = batadv_tt_flags_get(&tt->common) & BATADV_TT_CLIENT_ISOLA; batadv_tt_global_entry_put(tt); diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index c42a4aa8f41a..67872927cfb5 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -1851,12 +1851,21 @@ struct batadv_tt_common_entry { */ struct hlist_node hash_entry; - /** @flags: various state handling flags (see batadv_tt_client_flags) */ + /** + * @flags: various state handling flags (see batadv_tt_client_flags), + * protected by @flags_lock + */ u16 flags; /** @added_at: timestamp used for purging stale tt common entries */ unsigned long added_at; + /** + * @flags_lock: protect modifications of @flags and + * @batadv_tt_global_entry.roam_at + */ + spinlock_t flags_lock; + /** @refcount: number of contexts the object is used */ struct kref refcount; From f59c4c59df104baf473e20f885071a9d713c3861 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 30 Aug 2026 17:21:43 +0200 Subject: [PATCH 13/15] batman-adv: tt: transition NEW local entries only under lock The batadv_tt_local_size_inc() must never be called for an entry which was already removed from the list. Otherwise the removal from the hash cannot correctly determine if the batadv_tt_local_size_dec() needs to be called or not. This assumption is broken by the use of rcu_read_lock() in batadv_tt_local_transition_new() because it might still see entries in the list which were already removed by a different context from the list. If it then increments the size counter, nothing will reduce the counter again. Simply because the removal (responsible for the decrement) already happened. Over the whole time, the actual hash list spinlock must be held when transitioning NEW local entries to avoid list manipulations. Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 163f90962306..a870d9a97e32 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -333,6 +333,10 @@ static void batadv_tt_local_size_mod(struct batadv_priv *bat_priv, * given vid * @bat_priv: the bat priv with all the mesh interface information * @vid: the VLAN identifier + * + * It must only be called when removing the NEW flag of a + * batadv_tt_local_entry while it is still part of the bat_priv->tt.local_hash. + * It must therefore be checked under the specific list_locks[i]. */ static void batadv_tt_local_size_inc(struct batadv_priv *bat_priv, unsigned short vid) @@ -3938,6 +3942,7 @@ void batadv_tt_free(struct batadv_priv *bat_priv) */ static void batadv_tt_local_transition_new(struct batadv_priv *bat_priv) { + spinlock_t *list_lock; /* protects write access to the hash lists */ struct batadv_hashtable *hash = bat_priv->tt.local_hash; struct batadv_tt_common_entry *tt_common_entry; struct hlist_head *head; @@ -3948,10 +3953,10 @@ static void batadv_tt_local_transition_new(struct batadv_priv *bat_priv) for (i = 0; i < hash->size; i++) { head = &hash->table[i]; + list_lock = &hash->list_locks[i]; - rcu_read_lock(); - hlist_for_each_entry_rcu(tt_common_entry, - head, hash_entry) { + spin_lock_bh(list_lock); + hlist_for_each_entry(tt_common_entry, head, hash_entry) { bool cont = false; scoped_guard(spinlock_bh, &tt_common_entry->flags_lock) { @@ -3969,7 +3974,7 @@ static void batadv_tt_local_transition_new(struct batadv_priv *bat_priv) batadv_tt_local_size_inc(bat_priv, tt_common_entry->vid); } - rcu_read_unlock(); + spin_unlock_bh(list_lock); } } From 01ae63ecd1c098521f2544cb1018b8ec22d9bdb1 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 30 Aug 2026 17:21:44 +0200 Subject: [PATCH 14/15] batman-adv: tt: don't uncount never committed clients on pending purge batadv_tt_local_purge_pending_clients() decreases vlan->tt.num_entries for every entry it unlinks, but an entry which still carries BATADV_TT_CLIENT_NEW was never counted by batadv_tt_local_transition_new(). batadv_tt_local_resize_to_mtu() calls batadv_tt_local_purge() and batadv_tt_local_purge_pending_clients() directly, without committing in between, and it halves its timeout down towards zero. Once the timeout is short enough, batadv_tt_local_purge_list() marks even freshly added clients as pending. And each of them decreases a counter it never increased. Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index a870d9a97e32..794692170b61 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -349,6 +349,10 @@ static void batadv_tt_local_size_inc(struct batadv_priv *bat_priv, * given vid * @bat_priv: the bat priv with all the mesh interface information * @vid: the VLAN identifier + * + * It must only be called after a batadv_tt_local_entry without NEW flag + * was removed from bat_priv->tt.local_hash (under the specific + * list_locks[i]). */ static void batadv_tt_local_size_dec(struct batadv_priv *bat_priv, unsigned short vid) @@ -4019,8 +4023,13 @@ static void batadv_tt_local_purge_pending_clients(struct batadv_priv *bat_priv) tt_common->addr, batadv_print_vid(tt_common->vid)); - batadv_tt_local_size_dec(bat_priv, tt_common->vid); hlist_del_rcu(&tt_common->hash_entry); + + /* An entry which still carries BATADV_TT_CLIENT_NEW was + * never counted and must not be uncounted here. + */ + if (!(tt_common->flags & BATADV_TT_CLIENT_NEW)) + batadv_tt_local_size_dec(bat_priv, tt_common->vid); } if (cont) From 799152bcf6cbe4393eb03690925a382cefae303c Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Sun, 30 Aug 2026 17:21:45 +0200 Subject: [PATCH 15/15] batman-adv: tt: decrement count for committed client on local_remove Local TT entries which have the BATADV_TT_CLIENT_NEW no longer set are committed. The batadv_tt_local_size_inc() was called for them and thus the batadv_tt_local_size_dec() has to be called also when batadv_tt_local_remove() is called for them when the BATADV_TT_CLIENT_NEW wasn't consumed in the remove path. Signed-off-by: Sven Eckelmann --- net/batman-adv/translation-table.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 794692170b61..44a8cf89382c 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -352,7 +352,8 @@ static void batadv_tt_local_size_inc(struct batadv_priv *bat_priv, * * It must only be called after a batadv_tt_local_entry without NEW flag * was removed from bat_priv->tt.local_hash (under the specific - * list_locks[i]). + * list_locks[i]) or when it is ensured that it is not accessible + * in this list. */ static void batadv_tt_local_size_dec(struct batadv_priv *bat_priv, unsigned short vid) @@ -1423,6 +1424,13 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr, if (!tt_removed_node) goto out; + /* 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 above. + */ + if (!(batadv_tt_flags_get(&tt_local_entry->common) & BATADV_TT_CLIENT_NEW)) + batadv_tt_local_size_dec(bat_priv, tt_local_entry->common.vid); + /* drop reference of remove hash entry */ batadv_tt_local_entry_put(tt_local_entry);