* [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path
@ 2026-02-06 2:52 Linus Lüssing
2026-02-06 2:52 ` [PATCH net-next v2 01/14] net: bridge: mcast: export ip{4,6}_active state to netlink Linus Lüssing
` (13 more replies)
0 siblings, 14 replies; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang
This patchset introduces new state variables to combine and reduce the
number of checks we would otherwise perform on every multicast packet
in fast/data path. Instead of checking if our querier is enabled or
if another querier timer is pending, plus if the initial query
grace period has elapsed, plus for IPv6 and for our own querier if we
have an IPv6 address yet - and all this for every multicast packet -
we can now simply check one boolean state variable per protocol family
combining all this, the new ip{4,6}_active. Or MCAST_ACTIVE_V4 /
MCAST_ACTIVE_V6 to netlink/userspace.
The second reason for introducing these new, internal multicast active
variables is to later propagate a safety mechanism which was introduced
in b00589af3b04 ("bridge: disable snooping if there is no querier") to
switchdev/DSA, too. That is to notify switchdev/DSA if multicast
snooping can safely be applied without potential packet loss.
An example usage/integration of this with the modified Realtek rtl83xx
switch driver can be found in a draft pull-request at the OpenWrt
project: https://github.com/openwrt/openwrt/pull/18780
This was tested at least on an ZyXEL GS1900-24HP v1 switch.
iproute2 patch:
https://patchwork.kernel.org/project/netdevbpf/patch/20260206023704.4839-1-linus.luessing@c0d3.blue/
Regards, Linus
---
# Changelog v2
Summary of notable changes:
With the newly added selftests things are moved around a bit,
to be able to early on verify every commit that the general
and netlink ABI behaviour did not change.
The locking for br_multicast_open() and br_multicast_stop()
is now more symmetric.
The newly added tests revealed that in the previous pull-request
the checks were insufficient/incorrect when using VLANs, this
got fixed.
An inactive assertion / kernel splat was fixed.
* all: rebased to current net-next/main (a90f6dcefca6)
* [PATCH net-next v2 1/14] net: bridge: mcast: export ip{4,6}_active state to netlink
* removed the ip{4,6}_active state variables for now,
using/mimicking the current checks used in fast-path
to define the intended result for the netlink/userspace ABI
* br_vlan_global_opts_fill(): removing "const" from
"struct net_bridge_vlan *v_opts" (for now), to allow
dereferencing it
br->multicast_ctx.ip{4,6}_active to start with,
* [PATCH net-next v2 2/14] net: bridge: mcast: track active state, adding tests
* NEW: test that the new netlink interface has the intended results
* [PATCH net-next v2 3/14] net: bridge: mcast: avoid sleeping on bridge-down
* NEW: to be able
* [PATCH net-next v2 4/14] net: bridge: mcast: track active state, IGMP/MLD querier appearance
* adding br_multicast_notify_active() with a simple br_info()
to start with to track the evolving ip{4,6}_active behaviour
* br_ip{4,6}_multicast_query_delay_expired(): don't enable
ip{4,6}_active if we are about to get disabled by
adding/checking br_multicast_stopping()
* adding protocol specific ...delay_timer.function resets
in (new) br_multicast_reset_timer_cbs(), instead of
changing timer_setup()s in br_multicast_ctx_init()
* [PATCH net-next v2 5/14] net: bridge: mcast: track active state, foreign IGMP/MLD querier disappearance
* unchanged
* [PATCH net-next v2 6/14] net: bridge: mcast: track active state, IPv6 address availability
* unchanged
* [PATCH net-next v2 7/14] net: bridge: mcast: track active state, own MLD querier disappearance
* unchanged
* [PATCH net-next v2 8/14] net: bridge: mcast: track active state, if snooping is enabled
* renamed title: "... mcast: active state ..."
-> "... mcast: track activestate ..."
* adding VLAN aware check for BROPT_MCAST_VLAN_SNOOPING_ENABLED
* removing misplaced, second br_multicast_update_active(brmctx)
in br_multicast_toggle_one_vlan()
* [PATCH net-next v2 9/14] net: bridge: mcast: track active state, VLAN snooping
* NEW
* [PATCH net-next v2 10/14] net: bridge: mcast: track active state, bridge up/down
* adding mcast-spin-lock around br_multicast_open() in br_dev_open()a
* adding mcast-spin-lock around br_multicast_update_active()
in __br_multicast_stop()
* removing (now already locked) mcast-spin-lock addition
in br_multicast_toggle_one_vlan()
* removing (now already locked) mcast-spin-lock addition
in br_multicast_toggle_vlan_snooping()
* [PATCH net-next v2 11/14] net: bridge: mcast: track active state, prepare for outside lock reads
* NEW
* [PATCH net-next v2 12/14] net: bridge: mcast: use combined active state in netlink
* NEW (partially from: "net: bridge: mcast: export ip{4,6}_active state to netlink")
* [PATCH net-next v2 13/14] net: bridge: mcast: use combined active state in fast/data path
* using READ_ONCE() on ip{4,6}_active states
* [PATCH net-next v2 14/14] net: bridge: mcast: add inactive state assertions
* with the moved lock, bail out early if we are about to
get disabled (via br_multicast_stopping()) and just entered
br_ip{4,6}_multicast_querier_expired()
* removed br_multicast_assert_inactive() in
br_multicast_toggle_one_vlan(), as it can toggle to enabled
# Changelog (v1)
Changelog to / follow-up of: [PATCH net-next 0/5] net: bridge: propagate safe mcast snooping to switchdev + DSA
-> https://lkml.org/lkml/2025/5/22/1413
* removed the switchdev/DSA changes for now
* splitting "[PATCH net-next 1/5] net: bridge: mcast: explicitly track active state"
into:
* net: bridge: mcast: track active state, IGMP/MLD querier appearance
* net: bridge: mcast: track active state, foreign IGMP/MLD querier disappearance
* net: bridge: mcast: track active state, IPv6 address availability
* net: bridge: mcast: track active state, own MLD querier disappearance
* net: bridge: mcast: use combined active state in fast/data path
* net: bridge: mcast: track active state, bridge up/down
* rebased to current net-next/main:
* from_timer() -> timer_container_of()
* net: bridge: mcast: export ip{4,6}_active state to netlink:
* changing NLA_U8 to NLA_REJECT to make it read-only
* moved br_multicast_update_active() call from br_ip{4,6}_multicast_query_expired()
(own querier timer callback) to br_ip{4,6}_multicast_querier_expired()
(other querier timer callback)
* even though both should have worked as br_multicast_querier_expired()
would call br_multicast_start_querier()->...->br_multicast_query_expired(),
even if the own querier is disabled, but let's use the more direct way
* simplified br_multicast_update_active():
* no return value for now, don't track if the active state has changed,
these aren't necessary (yet)
* removed __br_multicast_update_active() variant as was used to force
an inactive state in __br_multicast_stop(), instead using an
netif_running(brmctx->br->dev) check in br_multicast_update_active()
* replaced br_ip{4,6}_multicast_check_active() with simpler
br_ip{4,6}_multicast_update_active() and
br_ip{4,6}_multicast_querier_exists()
* fixing build errors with CONFIG_IPV6 unset
* simplified br_multicast_toggle_enabled()
* no return value for now
* fixes "old used uninitialized" issue
* removed const from __br_multicast_querier_exists()'s "bool is_ipv6"
* replaced "struct ethhdr *eth" in br_multicast_{snooping,querier}_active()
with direct ethernet protocol integer attributes
* added a few comments in br_multicast_update_active() calling functions
Linus Lüssing (14):
net: bridge: mcast: export ip{4,6}_active state to netlink
net: bridge: mcast: track active state, adding tests
net: bridge: mcast: avoid sleeping on bridge-down
net: bridge: mcast: track active state, IGMP/MLD querier appearance
net: bridge: mcast: track active state, foreign IGMP/MLD querier
disappearance
net: bridge: mcast: track active state, IPv6 address availability
net: bridge: mcast: track active state, own MLD querier disappearance
net: bridge: mcast: track active state, if snooping is enabled
net: bridge: mcast: track active state, VLAN snooping
net: bridge: mcast: track active state, bridge up/down
net: bridge: mcast: track active state, prepare for outside lock reads
net: bridge: mcast: use combined active state in netlink
net: bridge: mcast: use combined active state in fast/data path
net: bridge: mcast: add inactive state assertions
include/uapi/linux/if_bridge.h | 2 +
include/uapi/linux/if_link.h | 14 +
net/bridge/br_device.c | 6 +-
net/bridge/br_input.c | 2 +-
net/bridge/br_multicast.c | 351 +++++++--
net/bridge/br_netlink.c | 10 +-
net/bridge/br_private.h | 45 +-
net/bridge/br_vlan.c | 5 +
net/bridge/br_vlan_options.c | 10 +-
net/core/rtnetlink.c | 2 +-
.../net/forwarding/bridge_mdb_active.sh | 682 ++++++++++++++++++
11 files changed, 1048 insertions(+), 81 deletions(-)
create mode 100755 tools/testing/selftests/net/forwarding/bridge_mdb_active.sh
--
2.51.0
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH net-next v2 01/14] net: bridge: mcast: export ip{4,6}_active state to netlink
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-08 16:00 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 02/14] net: bridge: mcast: track active state, adding tests Linus Lüssing
` (12 subsequent siblings)
13 siblings, 1 reply; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
Export the new ip{4,6}_active variables to netlink, to be able to
check from userspace that they are updated as intended.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
include/uapi/linux/if_bridge.h | 2 ++
include/uapi/linux/if_link.h | 12 ++++++++++++
net/bridge/br_netlink.c | 16 +++++++++++++++-
net/bridge/br_private.h | 2 +-
net/bridge/br_vlan_options.c | 22 ++++++++++++++++++++--
net/core/rtnetlink.c | 2 +-
6 files changed, 51 insertions(+), 5 deletions(-)
diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
index e52f8207ab27..910103b1ef03 100644
--- a/include/uapi/linux/if_bridge.h
+++ b/include/uapi/linux/if_bridge.h
@@ -584,6 +584,8 @@ enum {
BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS,
BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_STATE,
BRIDGE_VLANDB_GOPTS_MSTI,
+ BRIDGE_VLANDB_GOPTS_MCAST_ACTIVE_V4,
+ BRIDGE_VLANDB_GOPTS_MCAST_ACTIVE_V6,
__BRIDGE_VLANDB_GOPTS_MAX
};
#define BRIDGE_VLANDB_GOPTS_MAX (__BRIDGE_VLANDB_GOPTS_MAX - 1)
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index e9b5f79e1ee1..3cb040aaa8dc 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -744,6 +744,16 @@ enum in6_addr_gen_mode {
* @IFLA_BR_FDB_MAX_LEARNED
* Set the number of max dynamically learned FDB entries for the current
* bridge.
+ *
+ * @IFLA_BR_MCAST_ACTIVE_V4
+ * Bridge IPv4 mcast active state, read only.
+ *
+ * 1 if an IGMP querier is present, 0 otherwise.
+ *
+ * @IFLA_BR_MCAST_ACTIVE_V6
+ * Bridge IPv6 mcast active state, read only.
+ *
+ * 1 if an MLD querier is present, 0 otherwise.
*/
enum {
IFLA_BR_UNSPEC,
@@ -796,6 +806,8 @@ enum {
IFLA_BR_MCAST_QUERIER_STATE,
IFLA_BR_FDB_N_LEARNED,
IFLA_BR_FDB_MAX_LEARNED,
+ IFLA_BR_MCAST_ACTIVE_V4,
+ IFLA_BR_MCAST_ACTIVE_V6,
__IFLA_BR_MAX,
};
diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
index 0264730938f4..1b5ab1fb4558 100644
--- a/net/bridge/br_netlink.c
+++ b/net/bridge/br_netlink.c
@@ -1264,7 +1264,9 @@ static const struct nla_policy br_policy[IFLA_BR_MAX + 1] = {
[IFLA_BR_VLAN_STATS_ENABLED] = { .type = NLA_U8 },
[IFLA_BR_MCAST_STATS_ENABLED] = { .type = NLA_U8 },
[IFLA_BR_MCAST_IGMP_VERSION] = { .type = NLA_U8 },
+ [IFLA_BR_MCAST_ACTIVE_V4] = { .type = NLA_REJECT },
[IFLA_BR_MCAST_MLD_VERSION] = { .type = NLA_U8 },
+ [IFLA_BR_MCAST_ACTIVE_V6] = { .type = NLA_REJECT },
[IFLA_BR_VLAN_STATS_PER_PORT] = { .type = NLA_U8 },
[IFLA_BR_MULTI_BOOLOPT] =
NLA_POLICY_EXACT_LEN(sizeof(struct br_boolopt_multi)),
@@ -1625,7 +1627,9 @@ static size_t br_get_size(const struct net_device *brdev)
nla_total_size_64bit(sizeof(u64)) + /* IFLA_BR_MCAST_QUERY_RESPONSE_INTVL */
nla_total_size_64bit(sizeof(u64)) + /* IFLA_BR_MCAST_STARTUP_QUERY_INTVL */
nla_total_size(sizeof(u8)) + /* IFLA_BR_MCAST_IGMP_VERSION */
+ nla_total_size(sizeof(u8)) + /* IFLA_BR_MCAST_ACTIVE_V4 */
nla_total_size(sizeof(u8)) + /* IFLA_BR_MCAST_MLD_VERSION */
+ nla_total_size(sizeof(u8)) + /* IFLA_BR_MCAST_ACTIVE_V6 */
br_multicast_querier_state_size() + /* IFLA_BR_MCAST_QUERIER_STATE */
#endif
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
@@ -1646,6 +1650,8 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
u32 ageing_time = jiffies_to_clock_t(br->ageing_time);
u32 stp_enabled = br->stp_enabled;
u16 priority = (br->bridge_id.prio[0] << 8) | br->bridge_id.prio[1];
+ struct ethhdr eth6 = { .h_proto = htons(ETH_P_IPV6) };
+ struct ethhdr eth4 = { .h_proto = htons(ETH_P_IP) };
u8 vlan_enabled = br_vlan_enabled(br->dev);
struct br_boolopt_multi bm;
u64 clockval;
@@ -1717,12 +1723,20 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
br->multicast_ctx.multicast_startup_query_count) ||
nla_put_u8(skb, IFLA_BR_MCAST_IGMP_VERSION,
br->multicast_ctx.multicast_igmp_version) ||
+ nla_put_u8(skb, IFLA_BR_MCAST_ACTIVE_V4,
+ netif_running(brdev) && br_opt_get(br, BROPT_MULTICAST_ENABLED) &&
+ !br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) &&
+ br_multicast_querier_exists(&br->multicast_ctx, ð4, NULL)) ||
br_multicast_dump_querier_state(skb, &br->multicast_ctx,
IFLA_BR_MCAST_QUERIER_STATE))
return -EMSGSIZE;
#if IS_ENABLED(CONFIG_IPV6)
if (nla_put_u8(skb, IFLA_BR_MCAST_MLD_VERSION,
- br->multicast_ctx.multicast_mld_version))
+ br->multicast_ctx.multicast_mld_version) ||
+ nla_put_u8(skb, IFLA_BR_MCAST_ACTIVE_V6,
+ netif_running(brdev) && br_opt_get(br, BROPT_MULTICAST_ENABLED) &&
+ !br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) &&
+ br_multicast_querier_exists(&br->multicast_ctx, ð6, NULL)))
return -EMSGSIZE;
#endif
clockval = jiffies_to_clock_t(br->multicast_ctx.multicast_last_member_interval);
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index b9b2981c4841..4ab6a1f58116 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -1892,7 +1892,7 @@ int br_vlan_rtm_process_global_options(struct net_device *dev,
bool br_vlan_global_opts_can_enter_range(const struct net_bridge_vlan *v_curr,
const struct net_bridge_vlan *r_end);
bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
- const struct net_bridge_vlan *v_opts);
+ struct net_bridge_vlan *v_opts);
/* vlan state manipulation helpers using *_ONCE to annotate lock-free access,
* while br_vlan_set_state() may access data protected by multicast_lock.
diff --git a/net/bridge/br_vlan_options.c b/net/bridge/br_vlan_options.c
index 8fa89b04ee94..6425fa509b95 100644
--- a/net/bridge/br_vlan_options.c
+++ b/net/bridge/br_vlan_options.c
@@ -347,8 +347,10 @@ bool br_vlan_global_opts_can_enter_range(const struct net_bridge_vlan *v_curr,
}
bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
- const struct net_bridge_vlan *v_opts)
+ struct net_bridge_vlan *v_opts)
{
+ struct ethhdr eth6 = { .h_proto = htons(ETH_P_IPV6) };
+ struct ethhdr eth4 = { .h_proto = htons(ETH_P_IP) };
struct nlattr *nest2 __maybe_unused;
u64 clockval __maybe_unused;
struct nlattr *nest;
@@ -369,6 +371,12 @@ bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
!!(v_opts->priv_flags & BR_VLFLAG_GLOBAL_MCAST_ENABLED)) ||
nla_put_u8(skb, BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION,
v_opts->br_mcast_ctx.multicast_igmp_version) ||
+ nla_put_u8(skb, BRIDGE_VLANDB_GOPTS_MCAST_ACTIVE_V4,
+ netif_running(v_opts->br->dev) &&
+ br_opt_get(v_opts->br, BROPT_MULTICAST_ENABLED) &&
+ br_opt_get(v_opts->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) &&
+ !br_multicast_ctx_vlan_global_disabled(&v_opts->br_mcast_ctx) &&
+ br_multicast_querier_exists(&v_opts->br_mcast_ctx, ð4, NULL)) ||
nla_put_u32(skb, BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT,
v_opts->br_mcast_ctx.multicast_last_member_count) ||
nla_put_u32(skb, BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT,
@@ -423,7 +431,13 @@ bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
#if IS_ENABLED(CONFIG_IPV6)
if (nla_put_u8(skb, BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION,
- v_opts->br_mcast_ctx.multicast_mld_version))
+ v_opts->br_mcast_ctx.multicast_mld_version) ||
+ nla_put_u8(skb, BRIDGE_VLANDB_GOPTS_MCAST_ACTIVE_V6,
+ netif_running(v_opts->br->dev) &&
+ br_opt_get(v_opts->br, BROPT_MULTICAST_ENABLED) &&
+ br_opt_get(v_opts->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) &&
+ !br_multicast_ctx_vlan_global_disabled(&v_opts->br_mcast_ctx) &&
+ br_multicast_querier_exists(&v_opts->br_mcast_ctx, ð6, NULL)))
goto out_err;
#endif
#endif
@@ -448,7 +462,9 @@ static size_t rtnl_vlan_global_opts_nlmsg_size(const struct net_bridge_vlan *v)
#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
+ nla_total_size(sizeof(u8)) /* BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING */
+ nla_total_size(sizeof(u8)) /* BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION */
+ + nla_total_size(sizeof(u8)) /* BRIDGE_VLANDB_GOPTS_MCAST_ACTIVE_V4 */
+ nla_total_size(sizeof(u8)) /* BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION */
+ + nla_total_size(sizeof(u8)) /* BRIDGE_VLANDB_GOPTS_MCAST_ACTIVE_V6 */
+ nla_total_size(sizeof(u32)) /* BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT */
+ nla_total_size(sizeof(u32)) /* BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT */
+ nla_total_size(sizeof(u64)) /* BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL */
@@ -630,9 +646,11 @@ static const struct nla_policy br_vlan_db_gpol[BRIDGE_VLANDB_GOPTS_MAX + 1] = {
[BRIDGE_VLANDB_GOPTS_RANGE] = { .type = NLA_U16 },
[BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING] = { .type = NLA_U8 },
[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION] = { .type = NLA_U8 },
+ [BRIDGE_VLANDB_GOPTS_MCAST_ACTIVE_V6] = { .type = NLA_REJECT },
[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL] = { .type = NLA_U64 },
[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER] = { .type = NLA_U8 },
[BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION] = { .type = NLA_U8 },
+ [BRIDGE_VLANDB_GOPTS_MCAST_ACTIVE_V4] = { .type = NLA_REJECT },
[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT] = { .type = NLA_U32 },
[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT] = { .type = NLA_U32 },
[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL] = { .type = NLA_U64 },
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index b1ed55141d8a..7b29162e1f30 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -62,7 +62,7 @@
#include "dev.h"
-#define RTNL_MAX_TYPE 50
+#define RTNL_MAX_TYPE 52
#define RTNL_SLAVE_MAX_TYPE 44
struct rtnl_link {
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v2 02/14] net: bridge: mcast: track active state, adding tests
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
2026-02-06 2:52 ` [PATCH net-next v2 01/14] net: bridge: mcast: export ip{4,6}_active state to netlink Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-07 4:58 ` Jakub Kicinski
2026-02-08 16:00 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 03/14] net: bridge: mcast: avoid sleeping on bridge-down Linus Lüssing
` (11 subsequent siblings)
13 siblings, 2 replies; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
Before making any significant changes to the internals of the Linux
bridge add some tests regarding the multicast activity. This is
also to verify that we have the semantics of the new
*_MCAST_ACTIVE_{V4,V6} netlink attributes as expected.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
.../net/forwarding/bridge_mdb_active.sh | 682 ++++++++++++++++++
1 file changed, 682 insertions(+)
create mode 100755 tools/testing/selftests/net/forwarding/bridge_mdb_active.sh
diff --git a/tools/testing/selftests/net/forwarding/bridge_mdb_active.sh b/tools/testing/selftests/net/forwarding/bridge_mdb_active.sh
new file mode 100755
index 000000000000..5b6e14d88bc2
--- /dev/null
+++ b/tools/testing/selftests/net/forwarding/bridge_mdb_active.sh
@@ -0,0 +1,682 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+# +-------+ +---------+
+# | brq0 | | br0 |
+# | + $h1 | | + $swp1 |
+# +----|--+ +----|----+
+# | |
+# \--------------/
+#
+#
+# This script checks if we have the expected mcast_active_v{4,6} state
+# on br0 in a variety of scenarios. This state determines if ultimately
+# multicast snooping is applied to multicast data packets
+# (multicast snooping active) or if they are (by default) flooded instead
+# (multicast snooping inactive).
+#
+# Notably, multicast snooping can be enabled but still be inactive if not all
+# requirements to safely apply multicast snooping to multicast data packets
+# are met.
+#
+# Depending on the test case an IGMP/MLD querier might be on brq0, on br0
+# or neither.
+
+
+ALL_TESTS="
+ test_inactive
+ test_active_other_querier
+ test_active_own_querier
+ test_inactive_brdown
+ test_inactive_nov6
+ test_inactive_snooping_off
+ test_inactive_querier_off
+ test_inactive_other_querier_norespdelay
+ test_inactive_own_querier_norespdelay
+ test_inactive
+ test_vlan_inactive
+ test_vlan_active_other_querier
+ test_vlan_active_own_querier
+ test_vlan_inactive_brdown
+ test_vlan_inactive_nov6
+ test_vlan_inactive_snooping_off
+ test_vlan_inactive_vlans_snooping_off
+ test_vlan_inactive_vlan_snooping_off
+ test_vlan_inactive_other_querier_norespdelay
+ test_vlan_inactive_own_querier_norespdelay
+"
+
+NUM_NETIFS=1
+MCAST_MAX_RESP_IVAL_SEC=1
+MCAST_VLAN_ID=42
+source lib.sh
+
+switch_create()
+{
+ ip link add dev br0 type bridge\
+ vlan_filtering 0 \
+ mcast_query_response_interval $((${MCAST_MAX_RESP_IVAL_SEC}*100))\
+ mcast_snooping 0 \
+ mcast_vlan_snooping 0
+ ip link add dev brq0 type bridge\
+ vlan_filtering 0 \
+ mcast_query_response_interval $((${MCAST_MAX_RESP_IVAL_SEC}*100))\
+ mcast_snooping 0 \
+ mcast_vlan_snooping 0
+
+ echo 1 > /proc/sys/net/ipv6/conf/br0/disable_ipv6
+ echo 1 > /proc/sys/net/ipv6/conf/brq0/disable_ipv6
+
+ ip link set dev $swp1 master br0
+ ip link set dev $h1 master brq0
+
+ ip link set dev $h1 up
+ ip link set dev $swp1 up
+}
+
+switch_destroy()
+{
+ ip link set dev $swp1 down
+ ip link set dev $h1 down
+
+ ip link del dev brq0
+ ip link del dev br0
+}
+
+setup_prepare()
+{
+ h1=${NETIFS[p1]}
+ swp1=${NETIFS[p2]}
+
+ switch_create
+}
+
+cleanup()
+{
+ pre_cleanup
+ switch_destroy
+}
+
+mcast_active_check()
+{
+ local af="$1"
+ local state="$2"
+
+ ip -d -j link show dev br0\
+ | jq -e ".[] | select(.linkinfo.info_data.mcast_active_$af == $state)"\
+ &> /dev/null
+
+ check_err $? "Mcast active check failed"
+}
+
+mcast_vlan_active_check()
+{
+ local af="$1"
+ local state="$2"
+ local vid="${MCAST_VLAN_ID}"
+ local ret
+
+ bridge -j vlan global show dev br0\
+ | jq -e ".[].vlans.[] | select(.vlan == $vid and .mcast_active_$af == 1)"\
+ &> /dev/null
+ ret="$?"
+
+ if [ $ret -eq 0 -a $state -eq 0 ] || [ $ret -ne 0 -a $state -eq 1 ]; then
+ check_err 1 "Mcast VLAN active check failed"
+ fi
+}
+
+mcast_assert_active_v4()
+{
+ mcast_active_check "v4" "1"
+}
+
+mcast_assert_active_v6()
+{
+ mcast_active_check "v6" "1"
+}
+
+mcast_assert_inactive_v4()
+{
+ mcast_active_check "v4" "0"
+}
+
+mcast_assert_inactive_v6()
+{
+ mcast_active_check "v6" "0"
+}
+
+mcast_vlan_assert_active_v4()
+{
+ mcast_vlan_active_check "v4" "1"
+}
+
+mcast_vlan_assert_active_v6()
+{
+ mcast_vlan_active_check "v6" "1"
+}
+
+mcast_vlan_assert_inactive_v4()
+{
+ mcast_vlan_active_check "v4" "0"
+}
+
+mcast_vlan_assert_inactive_v6()
+{
+ mcast_vlan_active_check "v6" "0"
+}
+
+
+test_inactive_nolog()
+{
+ ip link set dev br0 down
+ ip link set dev brq0 down
+ ip link set dev br0 type bridge mcast_snooping 0
+ ip link set dev brq0 type bridge mcast_snooping 0
+ ip link set dev br0 type bridge mcast_querier 0
+ ip link set dev brq0 type bridge mcast_querier 0
+ ip link set dev br0 type bridge mcast_vlan_snooping 0
+ ip link set dev br0 type bridge vlan_filtering 0
+
+ echo 1 > /proc/sys/net/ipv6/conf/br0/disable_ipv6
+ echo 1 > /proc/sys/net/ipv6/conf/brq0/disable_ipv6
+
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+}
+
+test_inactive()
+{
+ RET=0
+
+ test_inactive_nolog
+ log_test "Mcast inactive test"
+}
+
+wait_lladdr_dad() {
+ local check_tentative
+
+ check_tentative="map(select(.scope == \"link\" and ((.tentative == true) | not))) | .[]"
+
+ ip -6 -j a s dev "$1"\
+ | jq -e ".[].addr_info | ${check_tentative}" &> /dev/null
+}
+
+test_active_setup_bridge()
+{
+ [ -n "$1" ] && echo 0 > /proc/sys/net/ipv6/conf/br0/disable_ipv6
+ [ -n "$2" ] && echo 0 > /proc/sys/net/ipv6/conf/brq0/disable_ipv6
+
+ [ -n "$3" ] && ip link set dev br0 up
+ [ -n "$4" ] && ip link set dev brq0 up
+ [ -n "$5" ] && slowwait 3 wait_lladdr_dad br0
+ [ -n "$6" ] && slowwait 3 wait_lladdr_dad brq0
+}
+
+test_active_setup_config()
+{
+ [ -n "$1" ] && ip link set dev br0 type bridge mcast_snooping 1
+ [ -n "$2" ] && ip link set dev brq0 type bridge mcast_snooping 1
+ [ -n "$3" ] && ip link set dev br0 type bridge mcast_querier 1
+ [ -n "$4" ] && ip link set dev brq0 type bridge mcast_querier 1
+}
+
+test_active_setup_wait()
+{
+ sleep $((${MCAST_MAX_RESP_IVAL_SEC} * 2))
+}
+
+test_active_setup_reset_own_querier()
+{
+ ip link set dev br0 type bridge mcast_querier 0
+ ip link set dev br0 type bridge mcast_querier 1
+
+ test_active_setup_wait
+}
+
+test_vlan_active_setup_config()
+{
+ [ -n "$1" ] && ip link set dev br0 type bridge vlan_filtering 1
+ [ -n "$2" ] && ip link set dev brq0 type bridge vlan_filtering 1
+ [ -n "$3" ] && ip link set dev br0 type bridge mcast_vlan_snooping 1
+ [ -n "$4" ] && ip link set dev brq0 type bridge mcast_vlan_snooping 1
+}
+
+test_vlan_active_setup_add_vlan()
+{
+ bridge vlan add vid ${MCAST_VLAN_ID} dev $swp1
+ bridge vlan add vid ${MCAST_VLAN_ID} dev $h1
+ bridge vlan global set vid ${MCAST_VLAN_ID} dev br0\
+ mcast_query_response_interval $((${MCAST_MAX_RESP_IVAL_SEC}*100))
+ bridge vlan global set vid ${MCAST_VLAN_ID} dev brq0\
+ mcast_query_response_interval $((${MCAST_MAX_RESP_IVAL_SEC}*100))
+ bridge vlan global set vid ${MCAST_VLAN_ID} dev br0 mcast_snooping 0
+ bridge vlan global set vid ${MCAST_VLAN_ID} dev brq0 mcast_snooping 0
+ bridge vlan global set vid ${MCAST_VLAN_ID} dev br0 mcast_querier 0
+ bridge vlan global set vid ${MCAST_VLAN_ID} dev brq0 mcast_querier 0
+}
+
+test_vlan_active_setup_config_vlan()
+{
+ [ -n "$1" ] && bridge vlan global set vid ${MCAST_VLAN_ID} dev br0 mcast_snooping 1
+ [ -n "$2" ] && bridge vlan global set vid ${MCAST_VLAN_ID} dev brq0 mcast_snooping 1
+ [ -n "$3" ] && bridge vlan global set vid ${MCAST_VLAN_ID} dev br0 mcast_querier 1
+ [ -n "$4" ] && bridge vlan global set vid ${MCAST_VLAN_ID} dev brq0 mcast_querier 1
+}
+
+test_vlan_teardown()
+{
+ bridge vlan del vid ${MCAST_VLAN_ID} dev $swp1
+ bridge vlan del vid ${MCAST_VLAN_ID} dev $h1
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+ mcast_vlan_assert_inactive_v4
+ mcast_vlan_assert_inactive_v6
+}
+
+test_vlan_active_setup_reset_own_querier()
+{
+ bridge vlan global set vid ${MCAST_VLAN_ID} dev br0 mcast_querier 0
+ bridge vlan global set vid ${MCAST_VLAN_ID} dev br0 mcast_querier 1
+
+ test_active_setup_wait
+}
+
+test_active_other_querier_nolog()
+{
+ test_active_setup_bridge "1" "2" "3" "4" "5" "6"
+ test_active_setup_config "1" "2" "" "4"
+ test_active_setup_wait
+
+ mcast_assert_active_v4
+ mcast_assert_active_v6
+}
+
+test_active_other_querier()
+{
+ RET=0
+
+ test_active_other_querier_nolog
+ test_inactive_nolog
+ log_test "Mcast active with other querier test"
+}
+
+test_active_own_querier_nolog()
+{
+ test_active_setup_bridge "1" "2" "3" "4" "5" "6"
+ test_active_setup_config "1" "2" "3" ""
+ test_active_setup_wait
+
+ mcast_assert_active_v4
+ mcast_assert_active_v6
+}
+
+test_active_own_querier()
+{
+ RET=0
+
+ test_active_own_querier_nolog
+ test_inactive_nolog
+ log_test "Mcast active with own querier test"
+}
+
+test_active_final()
+{
+ mcast_assert_active_v4
+ mcast_assert_active_v6
+
+ test_inactive_nolog
+}
+
+test_inactive_brdown()
+{
+ RET=0
+
+ test_active_setup_bridge "1" "2" "" "4" "" "6"
+ test_active_setup_config "1" "2" "3" ""
+ test_active_setup_wait
+
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+
+ test_active_setup_bridge "" "" "3" "" "" ""
+ mcast_assert_active_v4
+ mcast_assert_inactive_v6
+
+ test_active_setup_bridge "" "" "" "" "5" ""
+ test_active_setup_reset_own_querier
+ test_active_final
+
+ log_test "Mcast inactive, bridge down test"
+}
+
+test_inactive_nov6()
+{
+ RET=0
+
+ test_active_setup_bridge "" "2" "3" "4" "5" "6"
+ test_active_setup_config "1" "2" "3" ""
+ test_active_setup_wait
+
+ mcast_assert_active_v4
+ mcast_assert_inactive_v6
+
+ test_active_setup_bridge "1" "" "" "" "5" ""
+ test_active_setup_reset_own_querier
+ test_active_final
+
+ log_test "Mcast inactive, own querier, no IPv6 address test"
+}
+
+test_inactive_snooping_off()
+{
+ RET=0
+
+ test_active_setup_bridge "1" "2" "3" "4" "5" "6"
+ test_active_setup_config "" "2" "3" ""
+ test_active_setup_wait
+
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+
+ test_active_setup_config "1" "" "" ""
+ test_active_setup_reset_own_querier
+ test_active_final
+
+ log_test "Mcast inactive, snooping disabled test"
+}
+
+test_inactive_querier_off()
+{
+ RET=0
+
+ test_active_setup_bridge "1" "2" "3" "4" "5" "6"
+ test_active_setup_config "1" "2" "" ""
+ test_active_setup_wait
+
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+
+ test_active_setup_config "" "" "3" ""
+ test_active_setup_wait
+ test_active_final
+
+ log_test "Mcast inactive, no querier test"
+}
+
+test_inactive_other_querier_norespdelay()
+{
+ RET=0
+
+ test_active_setup_bridge "1" "2" "3" "4" "5" "6"
+ test_active_setup_config "1" "2" "3" ""
+ #test_active_setup_wait
+
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+
+ test_active_setup_wait
+ test_active_final
+
+ log_test "Mcast inactive, other querier, no response delay test"
+}
+
+test_inactive_own_querier_norespdelay()
+{
+ RET=0
+
+ test_active_setup_bridge "1" "2" "3" "4" "5" "6"
+ test_active_setup_config "1" "2" "" "4"
+ #test_active_setup_wait
+
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+
+ test_active_setup_wait
+ test_active_final
+
+ log_test "Mcast inactive, own querier, no response delay test"
+}
+
+test_vlan_inactive()
+{
+ RET=0
+
+ test_inactive_nolog
+ mcast_vlan_assert_inactive_v4
+ mcast_vlan_assert_inactive_v6
+
+ ip link set dev br0 type bridge vlan_filtering 1
+ ip link set dev br0 type bridge mcast_vlan_snooping 1
+ mcast_vlan_assert_inactive_v4
+ mcast_vlan_assert_inactive_v6
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+
+ ip link set dev br0 type bridge mcast_vlan_snooping 0
+ ip link set dev br0 type bridge vlan_filtering 0
+ test_active_own_querier_nolog
+ ip link set dev br0 type bridge vlan_filtering 1
+ mcast_assert_active_v4
+ mcast_assert_active_v6
+
+ ip link set dev br0 type bridge mcast_vlan_snooping 1
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+
+ test_inactive_nolog
+ log_test "Mcast VLAN inactive test"
+}
+
+test_vlan_active_final()
+{
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+ mcast_vlan_assert_active_v4
+ mcast_vlan_assert_active_v6
+
+ test_vlan_teardown
+ test_inactive_nolog
+}
+
+test_vlan_active_other_querier()
+{
+ RET=0
+
+ test_active_setup_bridge "1" "2" "3" "4" "5" "6"
+ test_active_setup_config "1" "2" "" ""
+ test_vlan_active_setup_config "1" "2" "3" "4"
+ test_vlan_active_setup_add_vlan
+ test_vlan_active_setup_config_vlan "1" "2" "" "4"
+ test_active_setup_wait
+ test_vlan_active_final
+
+ log_test "Mcast VLAN active, other querier test"
+}
+
+test_vlan_active_own_querier()
+{
+ RET=0
+
+ test_active_setup_bridge "1" "2" "3" "4" "5" "6"
+ test_active_setup_config "1" "2" "" ""
+ test_vlan_active_setup_config "1" "2" "3" "4"
+ test_vlan_active_setup_add_vlan
+ test_vlan_active_setup_config_vlan "1" "2" "3" ""
+ test_active_setup_wait
+ test_vlan_active_final
+
+ log_test "Mcast VLAN active, own querier test"
+}
+
+test_vlan_inactive_brdown()
+{
+ RET=0
+
+ test_active_setup_bridge "1" "2" "" "4" "" "6"
+ test_active_setup_config "1" "2" "" ""
+ test_vlan_active_setup_config "1" "2" "3" "4"
+ test_vlan_active_setup_add_vlan
+ test_vlan_active_setup_config_vlan "1" "2" "3" ""
+ test_active_setup_wait
+
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+ mcast_vlan_assert_inactive_v4
+ mcast_vlan_assert_inactive_v6
+
+ test_active_setup_bridge "" "" "3" "" "" ""
+ mcast_vlan_assert_active_v4
+ mcast_assert_inactive_v6
+
+ test_active_setup_bridge "" "" "" "" "5" ""
+ test_vlan_active_setup_reset_own_querier
+ test_vlan_active_final
+
+ log_test "Mcast VLAN inactive, bridge down test"
+}
+
+test_vlan_inactive_nov6()
+{
+ RET=0
+
+ test_active_setup_bridge "" "2" "3" "4" "5" "6"
+ test_active_setup_config "1" "2" "" ""
+ test_vlan_active_setup_config "1" "2" "3" "4"
+ test_vlan_active_setup_add_vlan
+ test_vlan_active_setup_config_vlan "1" "2" "3" ""
+ test_active_setup_wait
+
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+ mcast_vlan_assert_active_v4
+ mcast_vlan_assert_inactive_v6
+
+ test_active_setup_bridge "1" "" "" "" "5" ""
+ test_vlan_active_setup_reset_own_querier
+ test_vlan_active_final
+
+ log_test "Mcast VLAN inactive, own querier, no IPv6 address test"
+}
+
+test_vlan_inactive_snooping_off()
+{
+ RET=0
+
+ test_active_setup_bridge "1" "2" "3" "4" "5" "6"
+ test_active_setup_config "" "2" "" ""
+ test_vlan_active_setup_config "1" "2" "3" "4"
+ test_vlan_active_setup_add_vlan
+ test_vlan_active_setup_config_vlan "1" "2" "3" ""
+ test_active_setup_wait
+
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+ mcast_vlan_assert_inactive_v4
+ mcast_vlan_assert_inactive_v6
+
+ test_active_setup_config "1" "" "" ""
+ test_vlan_active_setup_reset_own_querier
+ test_vlan_active_final
+
+ log_test "Mcast VLAN inactive, snooping disabled test"
+}
+
+test_vlan_inactive_vlans_snooping_off()
+{
+ RET=0
+
+ test_active_setup_bridge "1" "2" "3" "4" "5" "6"
+ test_active_setup_config "1" "2" "" ""
+ test_vlan_active_setup_config "1" "2" "" "4"
+ test_vlan_active_setup_add_vlan
+ test_vlan_active_setup_config_vlan "1" "2" "3" ""
+ test_active_setup_wait
+
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+ mcast_vlan_assert_inactive_v4
+ mcast_vlan_assert_inactive_v6
+
+ test_vlan_active_setup_config "" "" "3" ""
+ test_vlan_active_setup_reset_own_querier
+ test_vlan_active_final
+
+ log_test "Mcast VLAN inactive, snooping for VLANs disabled test"
+}
+
+test_vlan_inactive_vlan_snooping_off()
+{
+ RET=0
+
+ test_active_setup_bridge "1" "2" "3" "4" "5" "6"
+ test_active_setup_config "1" "2" "" ""
+ test_vlan_active_setup_config "1" "2" "3" "4"
+ test_vlan_active_setup_add_vlan
+ test_vlan_active_setup_config_vlan "" "2" "3" ""
+ test_active_setup_wait
+
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+ mcast_vlan_assert_inactive_v4
+ mcast_vlan_assert_inactive_v6
+
+ test_vlan_active_setup_config_vlan "1" "" "" ""
+ test_vlan_active_setup_reset_own_querier
+ test_vlan_active_final
+
+ log_test "Mcast VLAN inactive, snooping for this VLAN disabled test"
+}
+
+test_vlan_inactive_other_querier_norespdelay()
+{
+ RET=0
+
+ test_active_setup_bridge "1" "2" "3" "4" "5" "6"
+ test_active_setup_config "1" "2" "" ""
+ test_vlan_active_setup_config "1" "2" "3" "4"
+ test_vlan_active_setup_add_vlan
+ test_vlan_active_setup_config_vlan "1" "2" "" "4"
+ #test_active_setup_wait
+
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+ mcast_vlan_assert_inactive_v4
+ mcast_vlan_assert_inactive_v6
+
+ test_active_setup_wait
+ test_vlan_active_final
+
+ log_test "Mcast VLAN inactive, other querier, no response delay test"
+}
+
+test_vlan_inactive_own_querier_norespdelay()
+{
+ RET=0
+
+ test_active_setup_bridge "1" "2" "3" "4" "5" "6"
+ test_active_setup_config "1" "2" "" ""
+ test_vlan_active_setup_config "1" "2" "3" "4"
+ test_vlan_active_setup_add_vlan
+ test_vlan_active_setup_config_vlan "1" "2" "3" ""
+ #test_active_setup_wait
+
+ mcast_assert_inactive_v4
+ mcast_assert_inactive_v6
+ mcast_vlan_assert_inactive_v4
+ mcast_vlan_assert_inactive_v6
+
+ test_active_setup_wait
+ test_vlan_active_final
+
+ log_test "Mcast VLAN inactive, own querier, no response delay test"
+}
+
+trap cleanup EXIT
+
+setup_prepare
+setup_wait
+
+tests_run
+
+exit $EXIT_STATUS
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v2 03/14] net: bridge: mcast: avoid sleeping on bridge-down
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
2026-02-06 2:52 ` [PATCH net-next v2 01/14] net: bridge: mcast: export ip{4,6}_active state to netlink Linus Lüssing
2026-02-06 2:52 ` [PATCH net-next v2 02/14] net: bridge: mcast: track active state, adding tests Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-08 11:41 ` Ido Schimmel
2026-02-08 16:01 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 04/14] net: bridge: mcast: track active state, IGMP/MLD querier appearance Linus Lüssing
` (10 subsequent siblings)
13 siblings, 2 replies; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
We later want to use the multicast lock when setting the bridge
interface up or down, to be able to atomically both check all conditions
to toggle the multicast active state and to subsequently toggle it.
While most variables we check / contexts we check from are serialized
(toggled variables through netlink/sysfs) the timer_pending() check is
not and might run in parallel.
However so far we are not allowed to spinlock __br_multicast_stop() as
its call to timer_delete_sync() might sleep. Therefore replacing the
sleeping variant with the non-sleeping one. It is sufficient to only
wait for any timer callback to finish when we are freeing the multicast
context.
Using the timer_shutdown() instead of the timer_delete() variant also
allows us to detect that we are stopping from within the according timer
callbacks, to retain the promise of the previous timer_delete_sync()
calls that no multicast state is changed after these
timer_{delete,shutdown}*() calls. And more importantly that we are not
inadvertently rearming timers.
This new check also makes the netif_running() check redundant/obsolete
in these contexts.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
net/bridge/br_device.c | 4 ++
net/bridge/br_multicast.c | 108 ++++++++++++++++++++++++++------------
net/bridge/br_private.h | 5 ++
net/bridge/br_vlan.c | 5 ++
4 files changed, 87 insertions(+), 35 deletions(-)
diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index a818fdc22da9..d9d1227d5708 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -168,7 +168,9 @@ static int br_dev_open(struct net_device *dev)
netdev_update_features(dev);
netif_start_queue(dev);
br_stp_enable_bridge(br);
+ spin_lock_bh(&br->multicast_lock);
br_multicast_open(br);
+ spin_unlock_bh(&br->multicast_lock);
if (br_opt_get(br, BROPT_MULTICAST_ENABLED))
br_multicast_join_snoopers(br);
@@ -191,7 +193,9 @@ static int br_dev_stop(struct net_device *dev)
struct net_bridge *br = netdev_priv(dev);
br_stp_disable_bridge(br);
+ spin_lock_bh(&br->multicast_lock);
br_multicast_stop(br);
+ spin_unlock_bh(&br->multicast_lock);
if (br_opt_get(br, BROPT_MULTICAST_ENABLED))
br_multicast_leave_snoopers(br);
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index dccae08b4f4c..f5a368dd20a3 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -1665,6 +1665,14 @@ static void br_multicast_router_expired(struct net_bridge_mcast_port *pmctx,
spin_unlock(&br->multicast_lock);
}
+static bool br_multicast_stopping(struct net_bridge *br,
+ struct timer_list *timer)
+{
+ lockdep_assert_held_once(&br->multicast_lock);
+
+ return !timer->function;
+}
+
static void br_ip4_multicast_router_expired(struct timer_list *t)
{
struct net_bridge_mcast_port *pmctx = timer_container_of(pmctx, t,
@@ -1700,7 +1708,8 @@ static void br_multicast_local_router_expired(struct net_bridge_mcast *brmctx,
struct timer_list *timer)
{
spin_lock(&brmctx->br->multicast_lock);
- if (brmctx->multicast_router == MDB_RTR_TYPE_DISABLED ||
+ if (br_multicast_stopping(brmctx->br, timer) ||
+ brmctx->multicast_router == MDB_RTR_TYPE_DISABLED ||
brmctx->multicast_router == MDB_RTR_TYPE_PERM ||
br_ip4_multicast_is_router(brmctx) ||
br_ip6_multicast_is_router(brmctx))
@@ -1730,10 +1739,11 @@ static void br_ip6_multicast_local_router_expired(struct timer_list *t)
#endif
static void br_multicast_querier_expired(struct net_bridge_mcast *brmctx,
- struct bridge_mcast_own_query *query)
+ struct bridge_mcast_own_query *query,
+ struct timer_list *timer)
{
spin_lock(&brmctx->br->multicast_lock);
- if (!netif_running(brmctx->br->dev) ||
+ if (br_multicast_stopping(brmctx->br, timer) ||
br_multicast_ctx_vlan_global_disabled(brmctx) ||
!br_opt_get(brmctx->br, BROPT_MULTICAST_ENABLED))
goto out;
@@ -1749,7 +1759,7 @@ static void br_ip4_multicast_querier_expired(struct timer_list *t)
struct net_bridge_mcast *brmctx = timer_container_of(brmctx, t,
ip4_other_query.timer);
- br_multicast_querier_expired(brmctx, &brmctx->ip4_own_query);
+ br_multicast_querier_expired(brmctx, &brmctx->ip4_own_query, t);
}
#if IS_ENABLED(CONFIG_IPV6)
@@ -1758,7 +1768,7 @@ static void br_ip6_multicast_querier_expired(struct timer_list *t)
struct net_bridge_mcast *brmctx = timer_container_of(brmctx, t,
ip6_other_query.timer);
- br_multicast_querier_expired(brmctx, &brmctx->ip6_own_query);
+ br_multicast_querier_expired(brmctx, &brmctx->ip6_own_query, t);
}
#endif
@@ -4049,10 +4059,12 @@ int br_multicast_rcv(struct net_bridge_mcast **brmctx,
}
static void br_multicast_query_expired(struct net_bridge_mcast *brmctx,
- struct bridge_mcast_own_query *query)
+ struct bridge_mcast_own_query *query,
+ struct timer_list *timer)
{
spin_lock(&brmctx->br->multicast_lock);
- if (br_multicast_ctx_vlan_disabled(brmctx))
+ if (br_multicast_stopping(brmctx->br, timer) ||
+ br_multicast_ctx_vlan_disabled(brmctx))
goto out;
if (query->startup_sent < brmctx->multicast_startup_query_count)
@@ -4068,7 +4080,7 @@ static void br_ip4_multicast_query_expired(struct timer_list *t)
struct net_bridge_mcast *brmctx = timer_container_of(brmctx, t,
ip4_own_query.timer);
- br_multicast_query_expired(brmctx, &brmctx->ip4_own_query);
+ br_multicast_query_expired(brmctx, &brmctx->ip4_own_query, t);
}
#if IS_ENABLED(CONFIG_IPV6)
@@ -4077,7 +4089,7 @@ static void br_ip6_multicast_query_expired(struct timer_list *t)
struct net_bridge_mcast *brmctx = timer_container_of(brmctx, t,
ip6_own_query.timer);
- br_multicast_query_expired(brmctx, &brmctx->ip6_own_query);
+ br_multicast_query_expired(brmctx, &brmctx->ip6_own_query, t);
}
#endif
@@ -4120,29 +4132,30 @@ void br_multicast_ctx_init(struct net_bridge *br,
seqcount_spinlock_init(&brmctx->ip6_querier.seq, &br->multicast_lock);
#endif
- timer_setup(&brmctx->ip4_mc_router_timer,
- br_ip4_multicast_local_router_expired, 0);
- timer_setup(&brmctx->ip4_other_query.timer,
- br_ip4_multicast_querier_expired, 0);
- timer_setup(&brmctx->ip4_other_query.delay_timer,
- br_multicast_query_delay_expired, 0);
- timer_setup(&brmctx->ip4_own_query.timer,
- br_ip4_multicast_query_expired, 0);
+ timer_setup(&brmctx->ip4_mc_router_timer, NULL, 0);
+ timer_setup(&brmctx->ip4_other_query.timer, NULL, 0);
+ timer_setup(&brmctx->ip4_other_query.delay_timer, NULL, 0);
+ timer_setup(&brmctx->ip4_own_query.timer, NULL, 0);
#if IS_ENABLED(CONFIG_IPV6)
- timer_setup(&brmctx->ip6_mc_router_timer,
- br_ip6_multicast_local_router_expired, 0);
- timer_setup(&brmctx->ip6_other_query.timer,
- br_ip6_multicast_querier_expired, 0);
- timer_setup(&brmctx->ip6_other_query.delay_timer,
- br_multicast_query_delay_expired, 0);
- timer_setup(&brmctx->ip6_own_query.timer,
- br_ip6_multicast_query_expired, 0);
+ timer_setup(&brmctx->ip6_mc_router_timer, NULL, 0);
+ timer_setup(&brmctx->ip6_other_query.timer, NULL, 0);
+ timer_setup(&brmctx->ip6_other_query.delay_timer, NULL, 0);
+ timer_setup(&brmctx->ip6_own_query.timer, NULL, 0);
#endif
}
void br_multicast_ctx_deinit(struct net_bridge_mcast *brmctx)
{
- __br_multicast_stop(brmctx);
+ timer_shutdown_sync(&brmctx->ip4_mc_router_timer);
+ timer_shutdown_sync(&brmctx->ip4_other_query.timer);
+ timer_shutdown_sync(&brmctx->ip4_other_query.delay_timer);
+ timer_shutdown_sync(&brmctx->ip4_own_query.timer);
+#if IS_ENABLED(CONFIG_IPV6)
+ timer_shutdown_sync(&brmctx->ip6_mc_router_timer);
+ timer_shutdown_sync(&brmctx->ip6_other_query.timer);
+ timer_shutdown_sync(&brmctx->ip6_other_query.delay_timer);
+ timer_shutdown_sync(&brmctx->ip6_own_query.timer);
+#endif
}
void br_multicast_init(struct net_bridge *br)
@@ -4222,9 +4235,27 @@ void br_multicast_leave_snoopers(struct net_bridge *br)
br_ip6_multicast_leave_snoopers(br);
}
+void br_multicast_reset_timer_cbs(struct net_bridge_mcast *brmctx)
+{
+ lockdep_assert_held_once(&brmctx->br->multicast_lock);
+
+ brmctx->ip4_mc_router_timer.function = br_ip4_multicast_local_router_expired;
+ brmctx->ip4_other_query.timer.function = br_ip4_multicast_querier_expired;
+ brmctx->ip4_other_query.delay_timer.function = br_multicast_query_delay_expired;
+ brmctx->ip4_own_query.timer.function = br_ip4_multicast_query_expired;
+#if IS_ENABLED(CONFIG_IPV6)
+ brmctx->ip6_mc_router_timer.function = br_ip6_multicast_local_router_expired;
+ brmctx->ip6_other_query.timer.function = br_ip6_multicast_querier_expired;
+ brmctx->ip6_other_query.delay_timer.function = br_multicast_query_delay_expired;
+ brmctx->ip6_own_query.timer.function = br_ip6_multicast_query_expired;
+#endif
+}
+
static void __br_multicast_open_query(struct net_bridge *br,
struct bridge_mcast_own_query *query)
{
+ lockdep_assert_held_once(&br->multicast_lock);
+
query->startup_sent = 0;
if (!br_opt_get(br, BROPT_MULTICAST_ENABLED))
@@ -4235,6 +4266,8 @@ static void __br_multicast_open_query(struct net_bridge *br,
static void __br_multicast_open(struct net_bridge_mcast *brmctx)
{
+ br_multicast_reset_timer_cbs(brmctx);
+
__br_multicast_open_query(brmctx->br, &brmctx->ip4_own_query);
#if IS_ENABLED(CONFIG_IPV6)
__br_multicast_open_query(brmctx->br, &brmctx->ip6_own_query);
@@ -4267,15 +4300,17 @@ void br_multicast_open(struct net_bridge *br)
static void __br_multicast_stop(struct net_bridge_mcast *brmctx)
{
- timer_delete_sync(&brmctx->ip4_mc_router_timer);
- timer_delete_sync(&brmctx->ip4_other_query.timer);
- timer_delete_sync(&brmctx->ip4_other_query.delay_timer);
- timer_delete_sync(&brmctx->ip4_own_query.timer);
+ lockdep_assert_held_once(&brmctx->br->multicast_lock);
+
+ timer_shutdown(&brmctx->ip4_mc_router_timer);
+ timer_shutdown(&brmctx->ip4_other_query.timer);
+ timer_shutdown(&brmctx->ip4_other_query.delay_timer);
+ timer_shutdown(&brmctx->ip4_own_query.timer);
#if IS_ENABLED(CONFIG_IPV6)
- timer_delete_sync(&brmctx->ip6_mc_router_timer);
- timer_delete_sync(&brmctx->ip6_other_query.timer);
- timer_delete_sync(&brmctx->ip6_other_query.delay_timer);
- timer_delete_sync(&brmctx->ip6_own_query.timer);
+ timer_shutdown(&brmctx->ip6_mc_router_timer);
+ timer_shutdown(&brmctx->ip6_other_query.timer);
+ timer_shutdown(&brmctx->ip6_other_query.delay_timer);
+ timer_shutdown(&brmctx->ip6_own_query.timer);
#endif
}
@@ -4326,12 +4361,12 @@ void br_multicast_toggle_one_vlan(struct net_bridge_vlan *vlan, bool on)
spin_lock_bh(&br->multicast_lock);
vlan->priv_flags ^= BR_VLFLAG_MCAST_ENABLED;
- spin_unlock_bh(&br->multicast_lock);
if (on)
__br_multicast_open(&vlan->br_mcast_ctx);
else
__br_multicast_stop(&vlan->br_mcast_ctx);
+ spin_unlock_bh(&br->multicast_lock);
} else {
struct net_bridge_mcast *brmctx;
@@ -4389,6 +4424,7 @@ int br_multicast_toggle_vlan_snooping(struct net_bridge *br, bool on,
if (!vg)
return 0;
+ spin_lock_bh(&br->multicast_lock);
br_opt_toggle(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED, on);
/* disable/enable non-vlan mcast contexts based on vlan snooping */
@@ -4396,6 +4432,8 @@ int br_multicast_toggle_vlan_snooping(struct net_bridge *br, bool on,
__br_multicast_stop(&br->multicast_ctx);
else
__br_multicast_open(&br->multicast_ctx);
+ spin_unlock_bh(&br->multicast_lock);
+
list_for_each_entry(p, &br->port_list, list) {
if (on)
br_multicast_disable_port_ctx(&p->multicast_ctx);
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 4ab6a1f58116..a181a27aa559 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -976,6 +976,7 @@ void br_multicast_disable_port(struct net_bridge_port *port);
void br_multicast_init(struct net_bridge *br);
void br_multicast_join_snoopers(struct net_bridge *br);
void br_multicast_leave_snoopers(struct net_bridge *br);
+void br_multicast_reset_timer_cbs(struct net_bridge_mcast *brmctx);
void br_multicast_open(struct net_bridge *br);
void br_multicast_stop(struct net_bridge *br);
void br_multicast_dev_del(struct net_bridge *br);
@@ -1416,6 +1417,10 @@ static inline void br_multicast_leave_snoopers(struct net_bridge *br)
{
}
+static inline void br_multicast_reset_timer_cbs(struct net_bridge_mcast *brmctx)
+{
+}
+
static inline void br_multicast_open(struct net_bridge *br)
{
}
diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
index ce72b837ff8e..7b46aa517c28 100644
--- a/net/bridge/br_vlan.c
+++ b/net/bridge/br_vlan.c
@@ -325,7 +325,12 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
if (err && err != -EOPNOTSUPP)
goto out;
}
+
br_multicast_ctx_init(br, v, &v->br_mcast_ctx);
+
+ spin_lock_bh(&br->multicast_lock);
+ br_multicast_reset_timer_cbs(&v->br_mcast_ctx);
+ spin_unlock_bh(&br->multicast_lock);
v->priv_flags |= BR_VLFLAG_GLOBAL_MCAST_ENABLED;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v2 04/14] net: bridge: mcast: track active state, IGMP/MLD querier appearance
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
` (2 preceding siblings ...)
2026-02-06 2:52 ` [PATCH net-next v2 03/14] net: bridge: mcast: avoid sleeping on bridge-down Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-08 16:07 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 05/14] net: bridge: mcast: track active state, foreign IGMP/MLD querier disappearance Linus Lüssing
` (9 subsequent siblings)
13 siblings, 1 reply; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
This is the first step to track in dedicated, per protocol family
variables if we can actively and safely use multicast snooping.
To later use these in the fast/data path instead of performing
all these checks for every packet and to later notify DSA/switchdev
about this state.
This toggles these new variables to true after a Maximum Response Delay
(default: 10 seconds) if a new IGMP or MLD querier has appeared. This
can be triggered either through receiving an IGMP/MLD query from another
host or by a user enabling our own IGMP/MLD querier.
This is the first of several requirements, similar to what
br_multicast_querier_exists() already checks so far, to be able to
reliably receive IGMP/MLD reports, which in turn are needed to build
a complete multicast database.
No functional change for the fast/data path yet.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
net/bridge/br_multicast.c | 109 ++++++++++++++++++++++++++++++++++++--
net/bridge/br_private.h | 2 +
2 files changed, 108 insertions(+), 3 deletions(-)
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index f5a368dd20a3..d5c623dce7eb 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -1069,6 +1069,81 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm
return skb;
}
+static bool br_ip4_multicast_querier_exists(struct net_bridge_mcast *brmctx)
+{
+ return __br_multicast_querier_exists(brmctx, &brmctx->ip4_other_query, false);
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+static bool br_ip6_multicast_querier_exists(struct net_bridge_mcast *brmctx)
+{
+ return __br_multicast_querier_exists(brmctx, &brmctx->ip6_other_query, true);
+}
+#endif
+
+static void br_ip4_multicast_update_active(struct net_bridge_mcast *brmctx,
+ bool force_inactive)
+{
+ if (force_inactive)
+ brmctx->ip4_active = false;
+ else
+ brmctx->ip4_active = br_ip4_multicast_querier_exists(brmctx);
+}
+
+static void br_ip6_multicast_update_active(struct net_bridge_mcast *brmctx,
+ bool force_inactive)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+ if (force_inactive)
+ brmctx->ip6_active = false;
+ else
+ brmctx->ip6_active = br_ip6_multicast_querier_exists(brmctx);
+#endif
+}
+
+static void br_multicast_notify_active(struct net_bridge_mcast *brmctx,
+ bool ip4_active_old, bool ip6_active_old)
+{
+ if (brmctx->ip4_active == ip4_active_old &&
+ brmctx->ip6_active == ip6_active_old)
+ return;
+
+ br_info(brmctx->br, "mc_active changed, vid: %i: v4: %i->%i, v6: %i->%i\n",
+ brmctx->vlan ? brmctx->vlan->vid : -1,
+ ip4_active_old, brmctx->ip4_active,
+ ip6_active_old, brmctx->ip6_active);
+}
+
+/**
+ * br_multicast_update_active() - update mcast active state
+ * @brmctx: the bridge multicast context to check
+ *
+ * This (potentially) updates the IPv4/IPv6 multicast active state. And by
+ * that enables or disables snooping of multicast payload traffic in fast
+ * path.
+ *
+ * The multicast active state is set, per protocol family, if:
+ *
+ * - an IGMP/MLD querier is present
+ *
+ * And is unset otherwise.
+ *
+ * This function should be called by anything that changes one of the
+ * above prerequisites.
+ */
+static void br_multicast_update_active(struct net_bridge_mcast *brmctx)
+{
+ bool ip4_active_old = brmctx->ip4_active, ip6_active_old = brmctx->ip6_active;
+ bool force_inactive = false;
+
+ lockdep_assert_held_once(&brmctx->br->multicast_lock);
+
+ br_ip4_multicast_update_active(brmctx, force_inactive);
+ br_ip6_multicast_update_active(brmctx, force_inactive);
+
+ br_multicast_notify_active(brmctx, ip4_active_old, ip6_active_old);
+}
+
#if IS_ENABLED(CONFIG_IPV6)
static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brmctx,
struct net_bridge_mcast_port *pmctx,
@@ -1772,10 +1847,36 @@ static void br_ip6_multicast_querier_expired(struct timer_list *t)
}
#endif
-static void br_multicast_query_delay_expired(struct timer_list *t)
+static void br_ip4_multicast_query_delay_expired(struct timer_list *t)
{
+ struct net_bridge_mcast *brmctx = timer_container_of(brmctx, t,
+ ip4_other_query.delay_timer);
+
+ spin_lock(&brmctx->br->multicast_lock);
+ if (!br_multicast_stopping(brmctx->br, t))
+ /* an own or other IGMP querier appeared some seconds ago and all
+ * reports should have arrived by now, maybe set multicast state to active
+ */
+ br_multicast_update_active(brmctx);
+ spin_unlock(&brmctx->br->multicast_lock);
}
+#if IS_ENABLED(CONFIG_IPV6)
+static void br_ip6_multicast_query_delay_expired(struct timer_list *t)
+{
+ struct net_bridge_mcast *brmctx = timer_container_of(brmctx, t,
+ ip6_other_query.delay_timer);
+
+ spin_lock(&brmctx->br->multicast_lock);
+ if (!br_multicast_stopping(brmctx->br, t))
+ /* an own or other MLD querier appeared some seconds ago and all
+ * reports should have arrived, maybe set multicast state to active
+ */
+ br_multicast_update_active(brmctx);
+ spin_unlock(&brmctx->br->multicast_lock);
+}
+#endif
+
static void br_multicast_select_own_querier(struct net_bridge_mcast *brmctx,
struct br_ip *ip,
struct sk_buff *skb)
@@ -4124,11 +4225,13 @@ void br_multicast_ctx_init(struct net_bridge *br,
brmctx->multicast_membership_interval = 260 * HZ;
brmctx->ip4_querier.port_ifidx = 0;
+ brmctx->ip4_active = 0;
seqcount_spinlock_init(&brmctx->ip4_querier.seq, &br->multicast_lock);
brmctx->multicast_igmp_version = 2;
#if IS_ENABLED(CONFIG_IPV6)
brmctx->multicast_mld_version = 1;
brmctx->ip6_querier.port_ifidx = 0;
+ brmctx->ip6_active = 0;
seqcount_spinlock_init(&brmctx->ip6_querier.seq, &br->multicast_lock);
#endif
@@ -4241,12 +4344,12 @@ void br_multicast_reset_timer_cbs(struct net_bridge_mcast *brmctx)
brmctx->ip4_mc_router_timer.function = br_ip4_multicast_local_router_expired;
brmctx->ip4_other_query.timer.function = br_ip4_multicast_querier_expired;
- brmctx->ip4_other_query.delay_timer.function = br_multicast_query_delay_expired;
+ brmctx->ip4_other_query.delay_timer.function = br_ip4_multicast_query_delay_expired;
brmctx->ip4_own_query.timer.function = br_ip4_multicast_query_expired;
#if IS_ENABLED(CONFIG_IPV6)
brmctx->ip6_mc_router_timer.function = br_ip6_multicast_local_router_expired;
brmctx->ip6_other_query.timer.function = br_ip6_multicast_querier_expired;
- brmctx->ip6_other_query.delay_timer.function = br_multicast_query_delay_expired;
+ brmctx->ip6_other_query.delay_timer.function = br_ip6_multicast_query_delay_expired;
brmctx->ip6_own_query.timer.function = br_ip6_multicast_query_expired;
#endif
}
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index a181a27aa559..4cc59abdb8e1 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -160,12 +160,14 @@ struct net_bridge_mcast {
struct bridge_mcast_other_query ip4_other_query;
struct bridge_mcast_own_query ip4_own_query;
struct bridge_mcast_querier ip4_querier;
+ bool ip4_active;
#if IS_ENABLED(CONFIG_IPV6)
struct hlist_head ip6_mc_router_list;
struct timer_list ip6_mc_router_timer;
struct bridge_mcast_other_query ip6_other_query;
struct bridge_mcast_own_query ip6_own_query;
struct bridge_mcast_querier ip6_querier;
+ bool ip6_active;
#endif /* IS_ENABLED(CONFIG_IPV6) */
#endif /* CONFIG_BRIDGE_IGMP_SNOOPING */
};
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v2 05/14] net: bridge: mcast: track active state, foreign IGMP/MLD querier disappearance
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
` (3 preceding siblings ...)
2026-02-06 2:52 ` [PATCH net-next v2 04/14] net: bridge: mcast: track active state, IGMP/MLD querier appearance Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-07 4:56 ` [net-next,v2,05/14] " Jakub Kicinski
2026-02-08 16:08 ` [PATCH net-next v2 05/14] " Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 06/14] net: bridge: mcast: track active state, IPv6 address availability Linus Lüssing
` (8 subsequent siblings)
13 siblings, 2 replies; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
This change ensures that the new multicast active state variable is unset
again after a foreign IGMP/MLD querier has disappeared (default: 255
seconds). If no new, other IGMP/MLD querier took over then we can't
reliably receive IGMP/MLD reports anymore and in turn can't ensure the
completeness of our MDB anymore either.
No functional change for the fast/data path yet.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
net/bridge/br_multicast.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index d5c623dce7eb..0fc29875db9c 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -1826,6 +1826,10 @@ static void br_multicast_querier_expired(struct net_bridge_mcast *brmctx,
br_multicast_start_querier(brmctx, query);
out:
+ /* another IGMP/MLD querier disappeared, set multicast state to inactive
+ * if our own querier is disabled, too
+ */
+ br_multicast_update_active(brmctx);
spin_unlock(&brmctx->br->multicast_lock);
}
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v2 06/14] net: bridge: mcast: track active state, IPv6 address availability
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
` (4 preceding siblings ...)
2026-02-06 2:52 ` [PATCH net-next v2 05/14] net: bridge: mcast: track active state, foreign IGMP/MLD querier disappearance Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-08 16:08 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 07/14] net: bridge: mcast: track active state, own MLD querier disappearance Linus Lüssing
` (7 subsequent siblings)
13 siblings, 1 reply; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
If we are the only potential MLD querier but don't have an IPv6
link-local address configured on our bridge interface then we can't
create a valid MLD query and in turn can't reliably receive MLD reports
and can't build a complete MDB. Hence disable the new multicast active
state variable then. Or reenable it if an IPv6 link-local address
became available.
No functional change for the fast/data path yet.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
net/bridge/br_multicast.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 0fc29875db9c..a1cde2ba2a3e 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -1125,6 +1125,7 @@ static void br_multicast_notify_active(struct net_bridge_mcast *brmctx,
* The multicast active state is set, per protocol family, if:
*
* - an IGMP/MLD querier is present
+ * - for own IPv6 MLD querier: an IPv6 address is configured on the bridge
*
* And is unset otherwise.
*
@@ -1222,10 +1223,12 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm
&ip6h->daddr, 0, &ip6h->saddr)) {
kfree_skb(skb);
br_opt_toggle(brmctx->br, BROPT_HAS_IPV6_ADDR, false);
+ br_multicast_update_active(brmctx);
return NULL;
}
br_opt_toggle(brmctx->br, BROPT_HAS_IPV6_ADDR, true);
+ br_multicast_update_active(brmctx);
ipv6_eth_mc_map(&ip6h->daddr, eth->h_dest);
hopopt = (u8 *)(ip6h + 1);
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v2 07/14] net: bridge: mcast: track active state, own MLD querier disappearance
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
` (5 preceding siblings ...)
2026-02-06 2:52 ` [PATCH net-next v2 06/14] net: bridge: mcast: track active state, IPv6 address availability Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-08 16:09 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 08/14] net: bridge: mcast: track active state, if snooping is enabled Linus Lüssing
` (6 subsequent siblings)
13 siblings, 1 reply; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
This change ensures that the new multicast active state variable is
immediately unset if our internal IGMP/MLD querier was elected and
now disabled.
If no IGMP/MLD querier exists on the link then we can't reliably receive
IGMP/MLD reports and in turn can't ensure the completeness of our MDB
anymore either.
No functional change for the fast/data path yet. This is the last
necessary check before using the new multicast active state variable
in the fast/data path, too.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
net/bridge/br_multicast.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index a1cde2ba2a3e..2710c21daef5 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -4914,6 +4914,7 @@ int br_multicast_set_querier(struct net_bridge_mcast *brmctx, unsigned long val)
#endif
unlock:
+ br_multicast_update_active(brmctx);
spin_unlock_bh(&brmctx->br->multicast_lock);
return 0;
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v2 08/14] net: bridge: mcast: track active state, if snooping is enabled
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
` (6 preceding siblings ...)
2026-02-06 2:52 ` [PATCH net-next v2 07/14] net: bridge: mcast: track active state, own MLD querier disappearance Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-08 16:09 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 09/14] net: bridge: mcast: track active state, VLAN snooping Linus Lüssing
` (5 subsequent siblings)
13 siblings, 1 reply; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
To be able to use the upcoming SWITCHDEV_ATTR_ID_BRIDGE_MC_ACTIVE
as a potential replacement for SWITCHDEV_ATTR_ID_BRIDGE_MC_DISABLED
also check and toggle the active state if multicast snooping is enabled
or disabled. So that MC_ACTIVE not only checks the querier state, but
also if multicast snooping is enabled in general.
No functional change for the fast/data path yet.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
include/uapi/linux/if_link.h | 6 ++++--
net/bridge/br_multicast.c | 16 ++++++++++++++--
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index 3cb040aaa8dc..d68eeff21b73 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -748,12 +748,14 @@ enum in6_addr_gen_mode {
* @IFLA_BR_MCAST_ACTIVE_V4
* Bridge IPv4 mcast active state, read only.
*
- * 1 if an IGMP querier is present, 0 otherwise.
+ * 1 if *IFLA_BR_MCAST_SNOOPING* is enabled and an IGMP querier is present,
+ * 0 otherwise.
*
* @IFLA_BR_MCAST_ACTIVE_V6
* Bridge IPv6 mcast active state, read only.
*
- * 1 if an MLD querier is present, 0 otherwise.
+ * 1 if *IFLA_BR_MCAST_SNOOPING* is enabled and an MLD querier is present,
+ * 0 otherwise.
*/
enum {
IFLA_BR_UNSPEC,
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 2710c21daef5..4bdc3838c3dc 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -1124,6 +1124,7 @@ static void br_multicast_notify_active(struct net_bridge_mcast *brmctx,
*
* The multicast active state is set, per protocol family, if:
*
+ * - multicast snooping is enabled
* - an IGMP/MLD querier is present
* - for own IPv6 MLD querier: an IPv6 address is configured on the bridge
*
@@ -1139,6 +1140,9 @@ static void br_multicast_update_active(struct net_bridge_mcast *brmctx)
lockdep_assert_held_once(&brmctx->br->multicast_lock);
+ if (!br_opt_get(brmctx->br, BROPT_MULTICAST_ENABLED))
+ force_inactive = true;
+
br_ip4_multicast_update_active(brmctx, force_inactive);
br_ip6_multicast_update_active(brmctx, force_inactive);
@@ -1353,6 +1357,12 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge_mcast *brmctx,
return NULL;
}
+static void br_multicast_toggle_enabled(struct net_bridge *br, bool on)
+{
+ br_opt_toggle(br, BROPT_MULTICAST_ENABLED, on);
+ br_multicast_update_active(&br->multicast_ctx);
+}
+
struct net_bridge_mdb_entry *br_multicast_new_group(struct net_bridge *br,
struct br_ip *group)
{
@@ -1366,7 +1376,7 @@ struct net_bridge_mdb_entry *br_multicast_new_group(struct net_bridge *br,
if (atomic_read(&br->mdb_hash_tbl.nelems) >= br->hash_max) {
trace_br_mdb_full(br->dev, group);
br_mc_disabled_update(br->dev, false, NULL);
- br_opt_toggle(br, BROPT_MULTICAST_ENABLED, false);
+ br_multicast_toggle_enabled(br, false);
return ERR_PTR(-E2BIG);
}
@@ -4471,6 +4481,7 @@ void br_multicast_toggle_one_vlan(struct net_bridge_vlan *vlan, bool on)
spin_lock_bh(&br->multicast_lock);
vlan->priv_flags ^= BR_VLFLAG_MCAST_ENABLED;
+ br_multicast_update_active(&vlan->br_mcast_ctx);
if (on)
__br_multicast_open(&vlan->br_mcast_ctx);
@@ -4822,7 +4833,8 @@ int br_multicast_toggle(struct net_bridge *br, unsigned long val,
if (err)
goto unlock;
- br_opt_toggle(br, BROPT_MULTICAST_ENABLED, !!val);
+ br_multicast_toggle_enabled(br, !!val);
+
if (!br_opt_get(br, BROPT_MULTICAST_ENABLED)) {
change_snoopers = true;
br_multicast_del_grps(br);
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v2 09/14] net: bridge: mcast: track active state, VLAN snooping
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
` (7 preceding siblings ...)
2026-02-06 2:52 ` [PATCH net-next v2 08/14] net: bridge: mcast: track active state, if snooping is enabled Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-08 16:10 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 10/14] net: bridge: mcast: track active state, bridge up/down Linus Lüssing
` (4 subsequent siblings)
13 siblings, 1 reply; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
If VLAN aware multicast snooping is enabled then we need to perform a
few extra checks to figure out if multicast snooping is actually enabled
for a specific VLAN, as there is then an additional per VLAN multicast
snooping toggle.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
net/bridge/br_multicast.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 4bdc3838c3dc..2a2f54009e0f 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -1143,6 +1143,26 @@ static void br_multicast_update_active(struct net_bridge_mcast *brmctx)
if (!br_opt_get(brmctx->br, BROPT_MULTICAST_ENABLED))
force_inactive = true;
+ if (br_opt_get(brmctx->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED)) {
+ /* with per-vlan snooping enabled there is an extra per-vlan
+ * toggle to enable/disable snooping which we must check
+ */
+ if (br_multicast_ctx_vlan_global_disabled(brmctx))
+ force_inactive = true;
+
+ /* with per-vlan snooping enabled the non-vlan multicast
+ * snooping context is inactive
+ */
+ if (!br_multicast_ctx_is_vlan(brmctx))
+ force_inactive = true;
+ } else {
+ /* with per-vlan snooping disabled a vlan multicast
+ * snooping context is inactive
+ */
+ if (br_multicast_ctx_is_vlan(brmctx))
+ force_inactive = true;
+ }
+
br_ip4_multicast_update_active(brmctx, force_inactive);
br_ip6_multicast_update_active(brmctx, force_inactive);
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v2 10/14] net: bridge: mcast: track active state, bridge up/down
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
` (8 preceding siblings ...)
2026-02-06 2:52 ` [PATCH net-next v2 09/14] net: bridge: mcast: track active state, VLAN snooping Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-08 16:10 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 11/14] net: bridge: mcast: track active state, prepare for outside lock reads Linus Lüssing
` (3 subsequent siblings)
13 siblings, 1 reply; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
This is mainly for switchdev and DSA later: To ensure that we switch
to inactive before destroying a bridge interface. A switchdev/DSA driver
might have allocated resources after we switched to an enabled multicast
active state. This gives switchdev/DSA drivers a chance to free these
resources again when we destroy the bridge (later).
Putting it into the ndo_stop / bridge interface down part instead of the
ndo_uninit / bridge destroy part though for a better semantic match. If
the bridge interface is down / stopped then it is also inactive.
No functional change for the fast/data path.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
include/uapi/linux/if_link.h | 8 ++++----
net/bridge/br_multicast.c | 10 ++++++++++
2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index d68eeff21b73..4ce9ddee94e8 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -748,14 +748,14 @@ enum in6_addr_gen_mode {
* @IFLA_BR_MCAST_ACTIVE_V4
* Bridge IPv4 mcast active state, read only.
*
- * 1 if *IFLA_BR_MCAST_SNOOPING* is enabled and an IGMP querier is present,
- * 0 otherwise.
+ * 1 if *IFLA_BR_MCAST_SNOOPING* is enabled, an IGMP querier is present
+ * and the bridge interface is up, 0 otherwise.
*
* @IFLA_BR_MCAST_ACTIVE_V6
* Bridge IPv6 mcast active state, read only.
*
- * 1 if *IFLA_BR_MCAST_SNOOPING* is enabled and an MLD querier is present,
- * 0 otherwise.
+ * 1 if *IFLA_BR_MCAST_SNOOPING* is enabled, an MLD querier is present
+ * and the bridge interface is up, 0 otherwise.
*/
enum {
IFLA_BR_UNSPEC,
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 2a2f54009e0f..7a32c6bed111 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -1124,6 +1124,7 @@ static void br_multicast_notify_active(struct net_bridge_mcast *brmctx,
*
* The multicast active state is set, per protocol family, if:
*
+ * - the bridge interface is up
* - multicast snooping is enabled
* - an IGMP/MLD querier is present
* - for own IPv6 MLD querier: an IPv6 address is configured on the bridge
@@ -1140,6 +1141,9 @@ static void br_multicast_update_active(struct net_bridge_mcast *brmctx)
lockdep_assert_held_once(&brmctx->br->multicast_lock);
+ if (!netif_running(brmctx->br->dev))
+ force_inactive = true;
+
if (!br_opt_get(brmctx->br, BROPT_MULTICAST_ENABLED))
force_inactive = true;
@@ -4412,6 +4416,9 @@ static void __br_multicast_open(struct net_bridge_mcast *brmctx)
#if IS_ENABLED(CONFIG_IPV6)
__br_multicast_open_query(brmctx->br, &brmctx->ip6_own_query);
#endif
+
+ /* bridge interface is up, maybe set multicast state to active */
+ br_multicast_update_active(brmctx);
}
void br_multicast_open(struct net_bridge *br)
@@ -4452,6 +4459,9 @@ static void __br_multicast_stop(struct net_bridge_mcast *brmctx)
timer_shutdown(&brmctx->ip6_other_query.delay_timer);
timer_shutdown(&brmctx->ip6_own_query.timer);
#endif
+
+ /* bridge interface is down, set multicast state to inactive */
+ br_multicast_update_active(brmctx);
}
void br_multicast_update_vlan_mcast_ctx(struct net_bridge_vlan *v, u8 state)
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v2 11/14] net: bridge: mcast: track active state, prepare for outside lock reads
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
` (9 preceding siblings ...)
2026-02-06 2:52 ` [PATCH net-next v2 10/14] net: bridge: mcast: track active state, bridge up/down Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-08 16:11 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 12/14] net: bridge: mcast: use combined active state in netlink Linus Lüssing
` (2 subsequent siblings)
13 siblings, 1 reply; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
We are updating ip{4,6}_active and check all variables their state relies
on while holding the bridge multicast spinlock. However we are going to
read it without this lock in a follow-up commit on fast/data path, too.
As these variables are only booleans this shouldn't be a problem,
ip{4,6}_active will be loaded and stored atomically. And those
read sides should converge eventually. But to allow tooling to verify
this use the READ_ONCE() and WRITE_ONCE macros.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
net/bridge/br_multicast.c | 25 ++++++++++++++-----------
1 file changed, 14 insertions(+), 11 deletions(-)
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 7a32c6bed111..48552720bcea 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -1085,9 +1085,9 @@ static void br_ip4_multicast_update_active(struct net_bridge_mcast *brmctx,
bool force_inactive)
{
if (force_inactive)
- brmctx->ip4_active = false;
+ WRITE_ONCE(brmctx->ip4_active, false);
else
- brmctx->ip4_active = br_ip4_multicast_querier_exists(brmctx);
+ WRITE_ONCE(brmctx->ip4_active, br_ip4_multicast_querier_exists(brmctx));
}
static void br_ip6_multicast_update_active(struct net_bridge_mcast *brmctx,
@@ -1095,23 +1095,25 @@ static void br_ip6_multicast_update_active(struct net_bridge_mcast *brmctx,
{
#if IS_ENABLED(CONFIG_IPV6)
if (force_inactive)
- brmctx->ip6_active = false;
+ WRITE_ONCE(brmctx->ip6_active, false);
else
- brmctx->ip6_active = br_ip6_multicast_querier_exists(brmctx);
+ WRITE_ONCE(brmctx->ip6_active, br_ip6_multicast_querier_exists(brmctx));
#endif
}
static void br_multicast_notify_active(struct net_bridge_mcast *brmctx,
bool ip4_active_old, bool ip6_active_old)
{
- if (brmctx->ip4_active == ip4_active_old &&
- brmctx->ip6_active == ip6_active_old)
+ int ip4_active = READ_ONCE(brmctx->ip4_active);
+ int ip6_active = READ_ONCE(brmctx->ip6_active);
+
+ if (ip4_active == ip4_active_old &&
+ ip6_active == ip6_active_old)
return;
br_info(brmctx->br, "mc_active changed, vid: %i: v4: %i->%i, v6: %i->%i\n",
brmctx->vlan ? brmctx->vlan->vid : -1,
- ip4_active_old, brmctx->ip4_active,
- ip6_active_old, brmctx->ip6_active);
+ ip4_active_old, ip4_active, ip6_active_old, ip6_active);
}
/**
@@ -1136,7 +1138,8 @@ static void br_multicast_notify_active(struct net_bridge_mcast *brmctx,
*/
static void br_multicast_update_active(struct net_bridge_mcast *brmctx)
{
- bool ip4_active_old = brmctx->ip4_active, ip6_active_old = brmctx->ip6_active;
+ bool ip4_active_old = READ_ONCE(brmctx->ip4_active);
+ bool ip6_active_old = READ_ONCE(brmctx->ip6_active);
bool force_inactive = false;
lockdep_assert_held_once(&brmctx->br->multicast_lock);
@@ -4266,13 +4269,13 @@ void br_multicast_ctx_init(struct net_bridge *br,
brmctx->multicast_membership_interval = 260 * HZ;
brmctx->ip4_querier.port_ifidx = 0;
- brmctx->ip4_active = 0;
+ WRITE_ONCE(brmctx->ip4_active, 0);
seqcount_spinlock_init(&brmctx->ip4_querier.seq, &br->multicast_lock);
brmctx->multicast_igmp_version = 2;
#if IS_ENABLED(CONFIG_IPV6)
brmctx->multicast_mld_version = 1;
brmctx->ip6_querier.port_ifidx = 0;
- brmctx->ip6_active = 0;
+ WRITE_ONCE(brmctx->ip6_active, 0);
seqcount_spinlock_init(&brmctx->ip6_querier.seq, &br->multicast_lock);
#endif
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v2 12/14] net: bridge: mcast: use combined active state in netlink
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
` (10 preceding siblings ...)
2026-02-06 2:52 ` [PATCH net-next v2 11/14] net: bridge: mcast: track active state, prepare for outside lock reads Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-08 16:11 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 13/14] net: bridge: mcast: use combined active state in fast/data path Linus Lüssing
2026-02-06 2:52 ` [PATCH net-next v2 14/14] net: bridge: mcast: add inactive state assertions Linus Lüssing
13 siblings, 1 reply; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
Use the new multicast ip{4,6}_active variables for the netlink output.
The result for the user should be the same. But this allows us to check
that from userspace tests, to ensure that ip{4,6}_active behave as intended
for the upcoming fast/data path changes.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
net/bridge/br_netlink.c | 10 ++--------
net/bridge/br_private.h | 2 +-
net/bridge/br_vlan_options.c | 16 +++-------------
3 files changed, 6 insertions(+), 22 deletions(-)
diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
index 1b5ab1fb4558..e79fddf747f8 100644
--- a/net/bridge/br_netlink.c
+++ b/net/bridge/br_netlink.c
@@ -1650,8 +1650,6 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
u32 ageing_time = jiffies_to_clock_t(br->ageing_time);
u32 stp_enabled = br->stp_enabled;
u16 priority = (br->bridge_id.prio[0] << 8) | br->bridge_id.prio[1];
- struct ethhdr eth6 = { .h_proto = htons(ETH_P_IPV6) };
- struct ethhdr eth4 = { .h_proto = htons(ETH_P_IP) };
u8 vlan_enabled = br_vlan_enabled(br->dev);
struct br_boolopt_multi bm;
u64 clockval;
@@ -1724,9 +1722,7 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
nla_put_u8(skb, IFLA_BR_MCAST_IGMP_VERSION,
br->multicast_ctx.multicast_igmp_version) ||
nla_put_u8(skb, IFLA_BR_MCAST_ACTIVE_V4,
- netif_running(brdev) && br_opt_get(br, BROPT_MULTICAST_ENABLED) &&
- !br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) &&
- br_multicast_querier_exists(&br->multicast_ctx, ð4, NULL)) ||
+ READ_ONCE(br->multicast_ctx.ip4_active)) ||
br_multicast_dump_querier_state(skb, &br->multicast_ctx,
IFLA_BR_MCAST_QUERIER_STATE))
return -EMSGSIZE;
@@ -1734,9 +1730,7 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
if (nla_put_u8(skb, IFLA_BR_MCAST_MLD_VERSION,
br->multicast_ctx.multicast_mld_version) ||
nla_put_u8(skb, IFLA_BR_MCAST_ACTIVE_V6,
- netif_running(brdev) && br_opt_get(br, BROPT_MULTICAST_ENABLED) &&
- !br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) &&
- br_multicast_querier_exists(&br->multicast_ctx, ð6, NULL)))
+ READ_ONCE(br->multicast_ctx.ip6_active)))
return -EMSGSIZE;
#endif
clockval = jiffies_to_clock_t(br->multicast_ctx.multicast_last_member_interval);
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 4cc59abdb8e1..8150ecc2c919 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -1899,7 +1899,7 @@ int br_vlan_rtm_process_global_options(struct net_device *dev,
bool br_vlan_global_opts_can_enter_range(const struct net_bridge_vlan *v_curr,
const struct net_bridge_vlan *r_end);
bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
- struct net_bridge_vlan *v_opts);
+ const struct net_bridge_vlan *v_opts);
/* vlan state manipulation helpers using *_ONCE to annotate lock-free access,
* while br_vlan_set_state() may access data protected by multicast_lock.
diff --git a/net/bridge/br_vlan_options.c b/net/bridge/br_vlan_options.c
index 6425fa509b95..dcb214b5507d 100644
--- a/net/bridge/br_vlan_options.c
+++ b/net/bridge/br_vlan_options.c
@@ -347,10 +347,8 @@ bool br_vlan_global_opts_can_enter_range(const struct net_bridge_vlan *v_curr,
}
bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
- struct net_bridge_vlan *v_opts)
+ const struct net_bridge_vlan *v_opts)
{
- struct ethhdr eth6 = { .h_proto = htons(ETH_P_IPV6) };
- struct ethhdr eth4 = { .h_proto = htons(ETH_P_IP) };
struct nlattr *nest2 __maybe_unused;
u64 clockval __maybe_unused;
struct nlattr *nest;
@@ -372,11 +370,7 @@ bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
nla_put_u8(skb, BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION,
v_opts->br_mcast_ctx.multicast_igmp_version) ||
nla_put_u8(skb, BRIDGE_VLANDB_GOPTS_MCAST_ACTIVE_V4,
- netif_running(v_opts->br->dev) &&
- br_opt_get(v_opts->br, BROPT_MULTICAST_ENABLED) &&
- br_opt_get(v_opts->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) &&
- !br_multicast_ctx_vlan_global_disabled(&v_opts->br_mcast_ctx) &&
- br_multicast_querier_exists(&v_opts->br_mcast_ctx, ð4, NULL)) ||
+ READ_ONCE(v_opts->br_mcast_ctx.ip4_active)) ||
nla_put_u32(skb, BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT,
v_opts->br_mcast_ctx.multicast_last_member_count) ||
nla_put_u32(skb, BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT,
@@ -433,11 +427,7 @@ bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
if (nla_put_u8(skb, BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION,
v_opts->br_mcast_ctx.multicast_mld_version) ||
nla_put_u8(skb, BRIDGE_VLANDB_GOPTS_MCAST_ACTIVE_V6,
- netif_running(v_opts->br->dev) &&
- br_opt_get(v_opts->br, BROPT_MULTICAST_ENABLED) &&
- br_opt_get(v_opts->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) &&
- !br_multicast_ctx_vlan_global_disabled(&v_opts->br_mcast_ctx) &&
- br_multicast_querier_exists(&v_opts->br_mcast_ctx, ð6, NULL)))
+ READ_ONCE(v_opts->br_mcast_ctx.ip6_active)))
goto out_err;
#endif
#endif
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v2 13/14] net: bridge: mcast: use combined active state in fast/data path
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
` (11 preceding siblings ...)
2026-02-06 2:52 ` [PATCH net-next v2 12/14] net: bridge: mcast: use combined active state in netlink Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-08 16:12 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 14/14] net: bridge: mcast: add inactive state assertions Linus Lüssing
13 siblings, 1 reply; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
As the multicast active state variable is now always up to date and
functionally equivalent to our manual, extensive checks in fast path
we can just use this state variable in fast path, too. This allows to
save some CPU cycles for every multicast packet in the fast/data path.
Next to using brmctx->ip4_active / brmctx->ip6_active in fast path this
mostly just moves some code around to not expose it via br_private.h
anymore. While at it now also passing the ethernet protocol number
directly, instead of a pointer into the ethernet header.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
net/bridge/br_device.c | 2 +-
net/bridge/br_input.c | 2 +-
net/bridge/br_multicast.c | 40 ++++++++++++++++++++++++++++++++++-----
net/bridge/br_private.h | 38 ++++++++-----------------------------
4 files changed, 45 insertions(+), 37 deletions(-)
diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index d9d1227d5708..ab7b54eb646e 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -102,7 +102,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
mdst = br_mdb_entry_skb_get(brmctx, skb, vid);
if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
- br_multicast_querier_exists(brmctx, eth_hdr(skb), mdst))
+ br_multicast_snooping_active(brmctx, eth_hdr(skb)->h_proto, mdst))
br_multicast_flood(mdst, skb, brmctx, false, true);
else
br_flood(br, skb, BR_PKT_MULTICAST, false, true, vid);
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index 1405f1061a54..c3dd8781a1b5 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -187,7 +187,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
case BR_PKT_MULTICAST:
mdst = br_mdb_entry_skb_get(brmctx, skb, vid);
if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
- br_multicast_querier_exists(brmctx, eth_hdr(skb), mdst)) {
+ br_multicast_snooping_active(brmctx, eth_hdr(skb)->h_proto, mdst)) {
if ((mdst && mdst->host_joined) ||
br_multicast_is_router(brmctx, skb) ||
br->dev->flags & IFF_ALLMULTI) {
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 48552720bcea..8ad1b8fec3c5 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -1069,6 +1069,26 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm
return skb;
}
+static bool
+__br_multicast_querier_exists(struct net_bridge_mcast *brmctx,
+ struct bridge_mcast_other_query *querier,
+ bool is_ipv6)
+{
+ bool own_querier_enabled;
+
+ if (brmctx->multicast_querier) {
+ if (is_ipv6 && !br_opt_get(brmctx->br, BROPT_HAS_IPV6_ADDR))
+ own_querier_enabled = false;
+ else
+ own_querier_enabled = true;
+ } else {
+ own_querier_enabled = false;
+ }
+
+ return !timer_pending(&querier->delay_timer) &&
+ (own_querier_enabled || timer_pending(&querier->timer));
+}
+
static bool br_ip4_multicast_querier_exists(struct net_bridge_mcast *brmctx)
{
return __br_multicast_querier_exists(brmctx, &brmctx->ip4_other_query, false);
@@ -1081,6 +1101,20 @@ static bool br_ip6_multicast_querier_exists(struct net_bridge_mcast *brmctx)
}
#endif
+static bool br_multicast_querier_exists(struct net_bridge_mcast *brmctx, int proto)
+{
+ switch (proto) {
+ case (ETH_P_IP):
+ return br_ip4_multicast_querier_exists(brmctx);
+#if IS_ENABLED(CONFIG_IPV6)
+ case (ETH_P_IPV6):
+ return br_ip6_multicast_querier_exists(brmctx);
+#endif
+ default:
+ return false;
+ }
+}
+
static void br_ip4_multicast_update_active(struct net_bridge_mcast *brmctx,
bool force_inactive)
{
@@ -5123,7 +5157,6 @@ bool br_multicast_has_querier_anywhere(struct net_device *dev, int proto)
{
struct net_bridge *br;
struct net_bridge_port *port;
- struct ethhdr eth;
bool ret = false;
rcu_read_lock();
@@ -5136,10 +5169,7 @@ bool br_multicast_has_querier_anywhere(struct net_device *dev, int proto)
br = port->br;
- memset(ð, 0, sizeof(eth));
- eth.h_proto = htons(proto);
-
- ret = br_multicast_querier_exists(&br->multicast_ctx, ð, NULL);
+ ret = br_multicast_querier_exists(&br->multicast_ctx, proto);
unlock:
rcu_read_unlock();
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 8150ecc2c919..75684f8079f2 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -1159,37 +1159,15 @@ br_multicast_is_router(struct net_bridge_mcast *brmctx, struct sk_buff *skb)
}
static inline bool
-__br_multicast_querier_exists(struct net_bridge_mcast *brmctx,
- struct bridge_mcast_other_query *querier,
- const bool is_ipv6)
+br_multicast_snooping_active(struct net_bridge_mcast *brmctx, __be16 eth_proto,
+ const struct net_bridge_mdb_entry *mdb)
{
- bool own_querier_enabled;
-
- if (brmctx->multicast_querier) {
- if (is_ipv6 && !br_opt_get(brmctx->br, BROPT_HAS_IPV6_ADDR))
- own_querier_enabled = false;
- else
- own_querier_enabled = true;
- } else {
- own_querier_enabled = false;
- }
-
- return !timer_pending(&querier->delay_timer) &&
- (own_querier_enabled || timer_pending(&querier->timer));
-}
-
-static inline bool br_multicast_querier_exists(struct net_bridge_mcast *brmctx,
- struct ethhdr *eth,
- const struct net_bridge_mdb_entry *mdb)
-{
- switch (eth->h_proto) {
+ switch (eth_proto) {
case (htons(ETH_P_IP)):
- return __br_multicast_querier_exists(brmctx,
- &brmctx->ip4_other_query, false);
+ return READ_ONCE(brmctx->ip4_active);
#if IS_ENABLED(CONFIG_IPV6)
case (htons(ETH_P_IPV6)):
- return __br_multicast_querier_exists(brmctx,
- &brmctx->ip6_other_query, true);
+ return READ_ONCE(brmctx->ip6_active);
#endif
default:
return !!mdb && br_group_is_l2(&mdb->addr);
@@ -1448,9 +1426,9 @@ static inline bool br_multicast_is_router(struct net_bridge_mcast *brmctx,
return false;
}
-static inline bool br_multicast_querier_exists(struct net_bridge_mcast *brmctx,
- struct ethhdr *eth,
- const struct net_bridge_mdb_entry *mdb)
+static inline bool
+br_multicast_snooping_active(struct net_bridge_mcast *brmctx, __be16 eth_proto,
+ const struct net_bridge_mdb_entry *mdb)
{
return false;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v2 14/14] net: bridge: mcast: add inactive state assertions
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
` (12 preceding siblings ...)
2026-02-06 2:52 ` [PATCH net-next v2 13/14] net: bridge: mcast: use combined active state in fast/data path Linus Lüssing
@ 2026-02-06 2:52 ` Linus Lüssing
2026-02-08 16:13 ` Ido Schimmel
13 siblings, 1 reply; 35+ messages in thread
From: Linus Lüssing @ 2026-02-06 2:52 UTC (permalink / raw)
To: bridge
Cc: netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Jakub Kicinski,
Eric Dumazet, David S . Miller, Kuniyuki Iwashima,
Stanislav Fomichev, Xiao Liang, Linus Lüssing
To avoid packetloss and as it is very hard from a user's perspective to
debug multicast snooping related issues it is even more crucial to properly
switch from an active to an inactive multicast snooping state than the
other way around.
Therefore adding a few kernel warnings if any of our assertions to be in
an inactive state would fail.
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
net/bridge/br_multicast.c | 43 +++++++++++++++++++++++++++++++++++----
1 file changed, 39 insertions(+), 4 deletions(-)
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 8ad1b8fec3c5..5c6c00776dee 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -1418,10 +1418,29 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge_mcast *brmctx,
return NULL;
}
+static void br_ip4_multicast_assert_inactive(struct net_bridge_mcast *brmctx)
+{
+ WARN_ON(br_multicast_snooping_active(brmctx, htons(ETH_P_IP), NULL));
+}
+
+static void br_ip6_multicast_assert_inactive(struct net_bridge_mcast *brmctx)
+{
+ WARN_ON(br_multicast_snooping_active(brmctx, htons(ETH_P_IPV6), NULL));
+}
+
+static void br_multicast_assert_inactive(struct net_bridge_mcast *brmctx)
+{
+ br_ip4_multicast_assert_inactive(brmctx);
+ br_ip6_multicast_assert_inactive(brmctx);
+}
+
static void br_multicast_toggle_enabled(struct net_bridge *br, bool on)
{
br_opt_toggle(br, BROPT_MULTICAST_ENABLED, on);
br_multicast_update_active(&br->multicast_ctx);
+
+ if (!on)
+ br_multicast_assert_inactive(&br->multicast_ctx);
}
struct net_bridge_mdb_entry *br_multicast_new_group(struct net_bridge *br,
@@ -1891,9 +1910,7 @@ static void br_multicast_querier_expired(struct net_bridge_mcast *brmctx,
struct bridge_mcast_own_query *query,
struct timer_list *timer)
{
- spin_lock(&brmctx->br->multicast_lock);
- if (br_multicast_stopping(brmctx->br, timer) ||
- br_multicast_ctx_vlan_global_disabled(brmctx) ||
+ if (br_multicast_ctx_vlan_global_disabled(brmctx) ||
!br_opt_get(brmctx->br, BROPT_MULTICAST_ENABLED))
goto out;
@@ -1904,7 +1921,6 @@ static void br_multicast_querier_expired(struct net_bridge_mcast *brmctx,
* if our own querier is disabled, too
*/
br_multicast_update_active(brmctx);
- spin_unlock(&brmctx->br->multicast_lock);
}
static void br_ip4_multicast_querier_expired(struct timer_list *t)
@@ -1912,7 +1928,16 @@ static void br_ip4_multicast_querier_expired(struct timer_list *t)
struct net_bridge_mcast *brmctx = timer_container_of(brmctx, t,
ip4_other_query.timer);
+ spin_lock(&brmctx->br->multicast_lock);
+ if (br_multicast_stopping(brmctx->br, t))
+ goto out;
+
br_multicast_querier_expired(brmctx, &brmctx->ip4_own_query, t);
+
+ if (!brmctx->multicast_querier)
+ br_ip4_multicast_assert_inactive(brmctx);
+out:
+ spin_unlock(&brmctx->br->multicast_lock);
}
#if IS_ENABLED(CONFIG_IPV6)
@@ -1921,7 +1946,16 @@ static void br_ip6_multicast_querier_expired(struct timer_list *t)
struct net_bridge_mcast *brmctx = timer_container_of(brmctx, t,
ip6_other_query.timer);
+ spin_lock(&brmctx->br->multicast_lock);
+ if (br_multicast_stopping(brmctx->br, t))
+ goto out;
+
br_multicast_querier_expired(brmctx, &brmctx->ip6_own_query, t);
+
+ if (!brmctx->multicast_querier)
+ br_ip6_multicast_assert_inactive(brmctx);
+out:
+ spin_unlock(&brmctx->br->multicast_lock);
}
#endif
@@ -4499,6 +4533,7 @@ static void __br_multicast_stop(struct net_bridge_mcast *brmctx)
/* bridge interface is down, set multicast state to inactive */
br_multicast_update_active(brmctx);
+ br_multicast_assert_inactive(brmctx);
}
void br_multicast_update_vlan_mcast_ctx(struct net_bridge_vlan *v, u8 state)
--
2.51.0
^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [net-next,v2,05/14] net: bridge: mcast: track active state, foreign IGMP/MLD querier disappearance
2026-02-06 2:52 ` [PATCH net-next v2 05/14] net: bridge: mcast: track active state, foreign IGMP/MLD querier disappearance Linus Lüssing
@ 2026-02-07 4:56 ` Jakub Kicinski
2026-02-11 3:05 ` Linus Lüssing
2026-02-08 16:08 ` [PATCH net-next v2 05/14] " Ido Schimmel
1 sibling, 1 reply; 35+ messages in thread
From: Jakub Kicinski @ 2026-02-07 4:56 UTC (permalink / raw)
To: linus.luessing
Cc: Jakub Kicinski, razor, kuniyu, davem, edumazet, idosch, sdf,
shaw.leon, netdev, bridge, horms, pabeni, andrew+netdev,
linux-kernel
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
net: bridge: mcast: track active state, foreign IGMP/MLD querier disappearance
This change ensures that the new multicast active state variable is unset
again after a foreign IGMP/MLD querier has disappeared (default: 255
seconds). If no new, other IGMP/MLD querier took over then we can't
reliably receive IGMP/MLD reports anymore and in turn can't ensure the
completeness of our MDB anymore either.
> diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
> index d5c623dce7eb..0fc29875db9c 100644
> --- a/net/bridge/br_multicast.c
> +++ b/net/bridge/br_multicast.c
> @@ -1826,6 +1826,10 @@ static void br_multicast_querier_expired(struct net_bridge_mcast *brmctx,
> br_multicast_start_querier(brmctx, query);
>
> out:
> + /* another IGMP/MLD querier disappeared, set multicast state to inactive
> + * if our own querier is disabled, too
> + */
> + br_multicast_update_active(brmctx);
> spin_unlock(&brmctx->br->multicast_lock);
> }
This isn't a bug, but I noticed a couple of unaddressed review comments
from Ido Schimmel's feedback on v1 back in May 2025 that don't appear to
have been incorporated into this v2 series.
In __br_multicast_querier_exists() in net/bridge/br_private.h around line
1164, the 'is_ipv6' bool parameter is marked const. Since this is a
pass-by-value parameter, the const qualifier doesn't provide any benefit.
Should it be removed?
Reference: https://lore.kernel.org/all/aDNPxdmDPYwasUEM@shredder/
Also in br_multicast_querier_exists() in net/bridge/br_private.h around
line 1182, the function takes a struct ethhdr *eth parameter but only uses
eth->h_proto. Would it make more sense to either make the 'eth' parameter
const, or just pass the EtherType (__be16) directly instead of the entire
struct pointer?
Reference: https://lore.kernel.org/all/aDNPxdmDPYwasUEM@shredder/
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 02/14] net: bridge: mcast: track active state, adding tests
2026-02-06 2:52 ` [PATCH net-next v2 02/14] net: bridge: mcast: track active state, adding tests Linus Lüssing
@ 2026-02-07 4:58 ` Jakub Kicinski
2026-02-08 16:00 ` Ido Schimmel
1 sibling, 0 replies; 35+ messages in thread
From: Jakub Kicinski @ 2026-02-07 4:58 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Ido Schimmel,
Andrew Lunn, Simon Horman, Paolo Abeni, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, 6 Feb 2026 03:52:08 +0100 Linus Lüssing wrote:
> Before making any significant changes to the internals of the Linux
> bridge add some tests regarding the multicast activity. This is
> also to verify that we have the semantics of the new
> *_MCAST_ACTIVE_{V4,V6} netlink attributes as expected.
shellcheck has a bunch of complains here, including these two warnings:
+In bridge_mdb_active.sh line 124:
+ if [ $ret -eq 0 -a $state -eq 0 ] || [ $ret -ne 0 -a $state -eq 1 ]; then
+ ^--^ SC2086 (info): Double quote to prevent globbing and word splitting.
+ ^-- SC2166 (warning): Prefer [ p ] && [ q ] as [ p -a q ] is not well defined.
+ ^-- SC2166 (warning): Prefer [ p ] && [ q ] as [ p -a q ] is not well defined.
> .../net/forwarding/bridge_mdb_active.sh | 682 ++++++++++++++++++
Is there a reason this test is not included in the makefile?
--
pw-bot: cr
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 03/14] net: bridge: mcast: avoid sleeping on bridge-down
2026-02-06 2:52 ` [PATCH net-next v2 03/14] net: bridge: mcast: avoid sleeping on bridge-down Linus Lüssing
@ 2026-02-08 11:41 ` Ido Schimmel
2026-02-08 16:01 ` Ido Schimmel
1 sibling, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 11:41 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:09AM +0100, Linus Lüssing wrote:
> diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> index a818fdc22da9..d9d1227d5708 100644
> --- a/net/bridge/br_device.c
> +++ b/net/bridge/br_device.c
> @@ -168,7 +168,9 @@ static int br_dev_open(struct net_device *dev)
> netdev_update_features(dev);
> netif_start_queue(dev);
> br_stp_enable_bridge(br);
> + spin_lock_bh(&br->multicast_lock);
This wouldn't work when CONFIG_BRIDGE_IGMP_SNOOPING is not set
> br_multicast_open(br);
> + spin_unlock_bh(&br->multicast_lock);
>
> if (br_opt_get(br, BROPT_MULTICAST_ENABLED))
> br_multicast_join_snoopers(br);
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 01/14] net: bridge: mcast: export ip{4,6}_active state to netlink
2026-02-06 2:52 ` [PATCH net-next v2 01/14] net: bridge: mcast: export ip{4,6}_active state to netlink Linus Lüssing
@ 2026-02-08 16:00 ` Ido Schimmel
0 siblings, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:00 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:07AM +0100, Linus Lüssing wrote:
> Export the new ip{4,6}_active variables to netlink, to be able to
> check from userspace that they are updated as intended.
>
> Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 02/14] net: bridge: mcast: track active state, adding tests
2026-02-06 2:52 ` [PATCH net-next v2 02/14] net: bridge: mcast: track active state, adding tests Linus Lüssing
2026-02-07 4:58 ` Jakub Kicinski
@ 2026-02-08 16:00 ` Ido Schimmel
2026-02-10 21:06 ` Linus Lüssing
1 sibling, 1 reply; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:00 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:08AM +0100, Linus Lüssing wrote:
> Before making any significant changes to the internals of the Linux
> bridge add some tests regarding the multicast activity. This is
> also to verify that we have the semantics of the new
> *_MCAST_ACTIVE_{V4,V6} netlink attributes as expected.
>
> Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
> ---
> .../net/forwarding/bridge_mdb_active.sh | 682 ++++++++++++++++++
> 1 file changed, 682 insertions(+)
> create mode 100755 tools/testing/selftests/net/forwarding/bridge_mdb_active.sh
>
> diff --git a/tools/testing/selftests/net/forwarding/bridge_mdb_active.sh b/tools/testing/selftests/net/forwarding/bridge_mdb_active.sh
> new file mode 100755
> index 000000000000..5b6e14d88bc2
> --- /dev/null
> +++ b/tools/testing/selftests/net/forwarding/bridge_mdb_active.sh
> @@ -0,0 +1,682 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +
> +# +-------+ +---------+
> +# | brq0 | | br0 |
> +# | + $h1 | | + $swp1 |
> +# +----|--+ +----|----+
> +# | |
> +# \--------------/
> +#
> +#
> +# This script checks if we have the expected mcast_active_v{4,6} state
> +# on br0 in a variety of scenarios. This state determines if ultimately
> +# multicast snooping is applied to multicast data packets
> +# (multicast snooping active) or if they are (by default) flooded instead
> +# (multicast snooping inactive).
> +#
> +# Notably, multicast snooping can be enabled but still be inactive if not all
> +# requirements to safely apply multicast snooping to multicast data packets
> +# are met.
> +#
> +# Depending on the test case an IGMP/MLD querier might be on brq0, on br0
> +# or neither.
> +
> +
> +ALL_TESTS="
> + test_inactive
> + test_active_other_querier
> + test_active_own_querier
> + test_inactive_brdown
> + test_inactive_nov6
> + test_inactive_snooping_off
> + test_inactive_querier_off
> + test_inactive_other_querier_norespdelay
> + test_inactive_own_querier_norespdelay
> + test_inactive
> + test_vlan_inactive
> + test_vlan_active_other_querier
> + test_vlan_active_own_querier
> + test_vlan_inactive_brdown
> + test_vlan_inactive_nov6
> + test_vlan_inactive_snooping_off
> + test_vlan_inactive_vlans_snooping_off
> + test_vlan_inactive_vlan_snooping_off
> + test_vlan_inactive_other_querier_norespdelay
> + test_vlan_inactive_own_querier_norespdelay
> +"
> +
> +NUM_NETIFS=1
Shouldn't this be "2" given below you have "swp1=${NETIFS[p2]}"?
> +MCAST_MAX_RESP_IVAL_SEC=1
> +MCAST_VLAN_ID=42
> +source lib.sh
> +
> +switch_create()
> +{
> + ip link add dev br0 type bridge\
> + vlan_filtering 0 \
> + mcast_query_response_interval $((${MCAST_MAX_RESP_IVAL_SEC}*100))\
> + mcast_snooping 0 \
> + mcast_vlan_snooping 0
> + ip link add dev brq0 type bridge\
> + vlan_filtering 0 \
> + mcast_query_response_interval $((${MCAST_MAX_RESP_IVAL_SEC}*100))\
> + mcast_snooping 0 \
> + mcast_vlan_snooping 0
> +
> + echo 1 > /proc/sys/net/ipv6/conf/br0/disable_ipv6
> + echo 1 > /proc/sys/net/ipv6/conf/brq0/disable_ipv6
> +
> + ip link set dev $swp1 master br0
> + ip link set dev $h1 master brq0
> +
> + ip link set dev $h1 up
> + ip link set dev $swp1 up
> +}
> +
> +switch_destroy()
> +{
> + ip link set dev $swp1 down
> + ip link set dev $h1 down
> +
> + ip link del dev brq0
> + ip link del dev br0
> +}
> +
> +setup_prepare()
> +{
> + h1=${NETIFS[p1]}
> + swp1=${NETIFS[p2]}
> +
> + switch_create
> +}
> +
> +cleanup()
> +{
> + pre_cleanup
> + switch_destroy
> +}
> +
> +mcast_active_check()
> +{
> + local af="$1"
> + local state="$2"
> +
> + ip -d -j link show dev br0\
> + | jq -e ".[] | select(.linkinfo.info_data.mcast_active_$af == $state)"\
> + &> /dev/null
> +
> + check_err $? "Mcast active check failed"
Please include the address family in the error message to make it
clearer what failed. Same in other places
> +}
> +
> +mcast_vlan_active_check()
> +{
> + local af="$1"
> + local state="$2"
> + local vid="${MCAST_VLAN_ID}"
> + local ret
> +
> + bridge -j vlan global show dev br0\
> + | jq -e ".[].vlans.[] | select(.vlan == $vid and .mcast_active_$af == 1)"\
> + &> /dev/null
> + ret="$?"
> +
> + if [ $ret -eq 0 -a $state -eq 0 ] || [ $ret -ne 0 -a $state -eq 1 ]; then
> + check_err 1 "Mcast VLAN active check failed"
> + fi
> +}
> +
> +mcast_assert_active_v4()
> +{
> + mcast_active_check "v4" "1"
> +}
> +
> +mcast_assert_active_v6()
> +{
> + mcast_active_check "v6" "1"
> +}
> +
> +mcast_assert_inactive_v4()
> +{
> + mcast_active_check "v4" "0"
> +}
> +
> +mcast_assert_inactive_v6()
> +{
> + mcast_active_check "v6" "0"
> +}
> +
> +mcast_vlan_assert_active_v4()
> +{
> + mcast_vlan_active_check "v4" "1"
> +}
> +
> +mcast_vlan_assert_active_v6()
> +{
> + mcast_vlan_active_check "v6" "1"
> +}
> +
> +mcast_vlan_assert_inactive_v4()
> +{
> + mcast_vlan_active_check "v4" "0"
> +}
> +
> +mcast_vlan_assert_inactive_v6()
> +{
> + mcast_vlan_active_check "v6" "0"
> +}
> +
> +
Double blank line
> +test_inactive_nolog()
> +{
> + ip link set dev br0 down
> + ip link set dev brq0 down
> + ip link set dev br0 type bridge mcast_snooping 0
> + ip link set dev brq0 type bridge mcast_snooping 0
> + ip link set dev br0 type bridge mcast_querier 0
> + ip link set dev brq0 type bridge mcast_querier 0
> + ip link set dev br0 type bridge mcast_vlan_snooping 0
> + ip link set dev br0 type bridge vlan_filtering 0
> +
> + echo 1 > /proc/sys/net/ipv6/conf/br0/disable_ipv6
> + echo 1 > /proc/sys/net/ipv6/conf/brq0/disable_ipv6
> +
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> +}
> +
> +test_inactive()
> +{
> + RET=0
> +
> + test_inactive_nolog
> + log_test "Mcast inactive test"
> +}
> +
> +wait_lladdr_dad() {
> + local check_tentative
> +
> + check_tentative="map(select(.scope == \"link\" and ((.tentative == true) | not))) | .[]"
> +
> + ip -6 -j a s dev "$1"\
> + | jq -e ".[].addr_info | ${check_tentative}" &> /dev/null
> +}
> +
> +test_active_setup_bridge()
> +{
> + [ -n "$1" ] && echo 0 > /proc/sys/net/ipv6/conf/br0/disable_ipv6
> + [ -n "$2" ] && echo 0 > /proc/sys/net/ipv6/conf/brq0/disable_ipv6
> +
> + [ -n "$3" ] && ip link set dev br0 up
> + [ -n "$4" ] && ip link set dev brq0 up
> + [ -n "$5" ] && slowwait 3 wait_lladdr_dad br0
> + [ -n "$6" ] && slowwait 3 wait_lladdr_dad brq0
> +}
> +
> +test_active_setup_config()
> +{
> + [ -n "$1" ] && ip link set dev br0 type bridge mcast_snooping 1
> + [ -n "$2" ] && ip link set dev brq0 type bridge mcast_snooping 1
> + [ -n "$3" ] && ip link set dev br0 type bridge mcast_querier 1
> + [ -n "$4" ] && ip link set dev brq0 type bridge mcast_querier 1
> +}
> +
> +test_active_setup_wait()
> +{
> + sleep $((${MCAST_MAX_RESP_IVAL_SEC} * 2))
> +}
> +
> +test_active_setup_reset_own_querier()
> +{
> + ip link set dev br0 type bridge mcast_querier 0
> + ip link set dev br0 type bridge mcast_querier 1
> +
> + test_active_setup_wait
> +}
> +
> +test_vlan_active_setup_config()
> +{
> + [ -n "$1" ] && ip link set dev br0 type bridge vlan_filtering 1
> + [ -n "$2" ] && ip link set dev brq0 type bridge vlan_filtering 1
> + [ -n "$3" ] && ip link set dev br0 type bridge mcast_vlan_snooping 1
> + [ -n "$4" ] && ip link set dev brq0 type bridge mcast_vlan_snooping 1
> +}
> +
> +test_vlan_active_setup_add_vlan()
> +{
> + bridge vlan add vid ${MCAST_VLAN_ID} dev $swp1
> + bridge vlan add vid ${MCAST_VLAN_ID} dev $h1
> + bridge vlan global set vid ${MCAST_VLAN_ID} dev br0\
> + mcast_query_response_interval $((${MCAST_MAX_RESP_IVAL_SEC}*100))
> + bridge vlan global set vid ${MCAST_VLAN_ID} dev brq0\
> + mcast_query_response_interval $((${MCAST_MAX_RESP_IVAL_SEC}*100))
> + bridge vlan global set vid ${MCAST_VLAN_ID} dev br0 mcast_snooping 0
> + bridge vlan global set vid ${MCAST_VLAN_ID} dev brq0 mcast_snooping 0
> + bridge vlan global set vid ${MCAST_VLAN_ID} dev br0 mcast_querier 0
> + bridge vlan global set vid ${MCAST_VLAN_ID} dev brq0 mcast_querier 0
> +}
> +
> +test_vlan_active_setup_config_vlan()
> +{
> + [ -n "$1" ] && bridge vlan global set vid ${MCAST_VLAN_ID} dev br0 mcast_snooping 1
> + [ -n "$2" ] && bridge vlan global set vid ${MCAST_VLAN_ID} dev brq0 mcast_snooping 1
> + [ -n "$3" ] && bridge vlan global set vid ${MCAST_VLAN_ID} dev br0 mcast_querier 1
> + [ -n "$4" ] && bridge vlan global set vid ${MCAST_VLAN_ID} dev brq0 mcast_querier 1
> +}
> +
> +test_vlan_teardown()
> +{
> + bridge vlan del vid ${MCAST_VLAN_ID} dev $swp1
> + bridge vlan del vid ${MCAST_VLAN_ID} dev $h1
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> + mcast_vlan_assert_inactive_v4
> + mcast_vlan_assert_inactive_v6
> +}
> +
> +test_vlan_active_setup_reset_own_querier()
> +{
> + bridge vlan global set vid ${MCAST_VLAN_ID} dev br0 mcast_querier 0
> + bridge vlan global set vid ${MCAST_VLAN_ID} dev br0 mcast_querier 1
> +
> + test_active_setup_wait
> +}
> +
> +test_active_other_querier_nolog()
> +{
> + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> + test_active_setup_config "1" "2" "" "4"
> + test_active_setup_wait
> +
> + mcast_assert_active_v4
> + mcast_assert_active_v6
> +}
> +
> +test_active_other_querier()
> +{
> + RET=0
> +
> + test_active_other_querier_nolog
> + test_inactive_nolog
> + log_test "Mcast active with other querier test"
> +}
> +
> +test_active_own_querier_nolog()
> +{
> + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> + test_active_setup_config "1" "2" "3" ""
> + test_active_setup_wait
> +
> + mcast_assert_active_v4
> + mcast_assert_active_v6
> +}
> +
> +test_active_own_querier()
> +{
> + RET=0
> +
> + test_active_own_querier_nolog
> + test_inactive_nolog
> + log_test "Mcast active with own querier test"
> +}
> +
> +test_active_final()
> +{
> + mcast_assert_active_v4
> + mcast_assert_active_v6
> +
> + test_inactive_nolog
> +}
> +
> +test_inactive_brdown()
> +{
> + RET=0
> +
> + test_active_setup_bridge "1" "2" "" "4" "" "6"
> + test_active_setup_config "1" "2" "3" ""
> + test_active_setup_wait
> +
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> +
> + test_active_setup_bridge "" "" "3" "" "" ""
> + mcast_assert_active_v4
> + mcast_assert_inactive_v6
> +
> + test_active_setup_bridge "" "" "" "" "5" ""
> + test_active_setup_reset_own_querier
> + test_active_final
> +
> + log_test "Mcast inactive, bridge down test"
> +}
> +
> +test_inactive_nov6()
> +{
> + RET=0
> +
> + test_active_setup_bridge "" "2" "3" "4" "5" "6"
> + test_active_setup_config "1" "2" "3" ""
> + test_active_setup_wait
> +
> + mcast_assert_active_v4
> + mcast_assert_inactive_v6
> +
> + test_active_setup_bridge "1" "" "" "" "5" ""
> + test_active_setup_reset_own_querier
> + test_active_final
> +
> + log_test "Mcast inactive, own querier, no IPv6 address test"
> +}
> +
> +test_inactive_snooping_off()
> +{
> + RET=0
> +
> + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> + test_active_setup_config "" "2" "3" ""
> + test_active_setup_wait
> +
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> +
> + test_active_setup_config "1" "" "" ""
> + test_active_setup_reset_own_querier
> + test_active_final
> +
> + log_test "Mcast inactive, snooping disabled test"
> +}
> +
> +test_inactive_querier_off()
> +{
> + RET=0
> +
> + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> + test_active_setup_config "1" "2" "" ""
> + test_active_setup_wait
> +
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> +
> + test_active_setup_config "" "" "3" ""
> + test_active_setup_wait
> + test_active_final
> +
> + log_test "Mcast inactive, no querier test"
> +}
> +
> +test_inactive_other_querier_norespdelay()
> +{
> + RET=0
> +
> + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> + test_active_setup_config "1" "2" "3" ""
> + #test_active_setup_wait
Why the comment? There are more instances below
> +
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> +
> + test_active_setup_wait
> + test_active_final
> +
> + log_test "Mcast inactive, other querier, no response delay test"
> +}
> +
> +test_inactive_own_querier_norespdelay()
> +{
> + RET=0
> +
> + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> + test_active_setup_config "1" "2" "" "4"
> + #test_active_setup_wait
> +
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> +
> + test_active_setup_wait
> + test_active_final
> +
> + log_test "Mcast inactive, own querier, no response delay test"
> +}
> +
> +test_vlan_inactive()
> +{
> + RET=0
> +
> + test_inactive_nolog
> + mcast_vlan_assert_inactive_v4
> + mcast_vlan_assert_inactive_v6
> +
> + ip link set dev br0 type bridge vlan_filtering 1
> + ip link set dev br0 type bridge mcast_vlan_snooping 1
> + mcast_vlan_assert_inactive_v4
> + mcast_vlan_assert_inactive_v6
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> +
> + ip link set dev br0 type bridge mcast_vlan_snooping 0
> + ip link set dev br0 type bridge vlan_filtering 0
> + test_active_own_querier_nolog
> + ip link set dev br0 type bridge vlan_filtering 1
> + mcast_assert_active_v4
> + mcast_assert_active_v6
> +
> + ip link set dev br0 type bridge mcast_vlan_snooping 1
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> +
> + test_inactive_nolog
> + log_test "Mcast VLAN inactive test"
> +}
> +
> +test_vlan_active_final()
> +{
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> + mcast_vlan_assert_active_v4
> + mcast_vlan_assert_active_v6
> +
> + test_vlan_teardown
> + test_inactive_nolog
> +}
> +
> +test_vlan_active_other_querier()
> +{
> + RET=0
> +
> + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> + test_active_setup_config "1" "2" "" ""
> + test_vlan_active_setup_config "1" "2" "3" "4"
> + test_vlan_active_setup_add_vlan
> + test_vlan_active_setup_config_vlan "1" "2" "" "4"
> + test_active_setup_wait
> + test_vlan_active_final
> +
> + log_test "Mcast VLAN active, other querier test"
> +}
> +
> +test_vlan_active_own_querier()
> +{
> + RET=0
> +
> + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> + test_active_setup_config "1" "2" "" ""
> + test_vlan_active_setup_config "1" "2" "3" "4"
> + test_vlan_active_setup_add_vlan
> + test_vlan_active_setup_config_vlan "1" "2" "3" ""
> + test_active_setup_wait
> + test_vlan_active_final
> +
> + log_test "Mcast VLAN active, own querier test"
> +}
> +
> +test_vlan_inactive_brdown()
> +{
> + RET=0
> +
> + test_active_setup_bridge "1" "2" "" "4" "" "6"
> + test_active_setup_config "1" "2" "" ""
> + test_vlan_active_setup_config "1" "2" "3" "4"
> + test_vlan_active_setup_add_vlan
> + test_vlan_active_setup_config_vlan "1" "2" "3" ""
> + test_active_setup_wait
> +
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> + mcast_vlan_assert_inactive_v4
> + mcast_vlan_assert_inactive_v6
> +
> + test_active_setup_bridge "" "" "3" "" "" ""
> + mcast_vlan_assert_active_v4
> + mcast_assert_inactive_v6
> +
> + test_active_setup_bridge "" "" "" "" "5" ""
> + test_vlan_active_setup_reset_own_querier
> + test_vlan_active_final
> +
> + log_test "Mcast VLAN inactive, bridge down test"
> +}
> +
> +test_vlan_inactive_nov6()
> +{
> + RET=0
> +
> + test_active_setup_bridge "" "2" "3" "4" "5" "6"
> + test_active_setup_config "1" "2" "" ""
> + test_vlan_active_setup_config "1" "2" "3" "4"
> + test_vlan_active_setup_add_vlan
> + test_vlan_active_setup_config_vlan "1" "2" "3" ""
> + test_active_setup_wait
> +
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> + mcast_vlan_assert_active_v4
> + mcast_vlan_assert_inactive_v6
> +
> + test_active_setup_bridge "1" "" "" "" "5" ""
> + test_vlan_active_setup_reset_own_querier
> + test_vlan_active_final
> +
> + log_test "Mcast VLAN inactive, own querier, no IPv6 address test"
> +}
> +
> +test_vlan_inactive_snooping_off()
> +{
> + RET=0
> +
> + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> + test_active_setup_config "" "2" "" ""
> + test_vlan_active_setup_config "1" "2" "3" "4"
> + test_vlan_active_setup_add_vlan
> + test_vlan_active_setup_config_vlan "1" "2" "3" ""
> + test_active_setup_wait
> +
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> + mcast_vlan_assert_inactive_v4
> + mcast_vlan_assert_inactive_v6
> +
> + test_active_setup_config "1" "" "" ""
> + test_vlan_active_setup_reset_own_querier
> + test_vlan_active_final
> +
> + log_test "Mcast VLAN inactive, snooping disabled test"
> +}
> +
> +test_vlan_inactive_vlans_snooping_off()
> +{
> + RET=0
> +
> + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> + test_active_setup_config "1" "2" "" ""
> + test_vlan_active_setup_config "1" "2" "" "4"
> + test_vlan_active_setup_add_vlan
> + test_vlan_active_setup_config_vlan "1" "2" "3" ""
> + test_active_setup_wait
> +
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> + mcast_vlan_assert_inactive_v4
> + mcast_vlan_assert_inactive_v6
> +
> + test_vlan_active_setup_config "" "" "3" ""
> + test_vlan_active_setup_reset_own_querier
> + test_vlan_active_final
> +
> + log_test "Mcast VLAN inactive, snooping for VLANs disabled test"
> +}
> +
> +test_vlan_inactive_vlan_snooping_off()
> +{
> + RET=0
> +
> + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> + test_active_setup_config "1" "2" "" ""
> + test_vlan_active_setup_config "1" "2" "3" "4"
> + test_vlan_active_setup_add_vlan
> + test_vlan_active_setup_config_vlan "" "2" "3" ""
> + test_active_setup_wait
> +
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> + mcast_vlan_assert_inactive_v4
> + mcast_vlan_assert_inactive_v6
> +
> + test_vlan_active_setup_config_vlan "1" "" "" ""
> + test_vlan_active_setup_reset_own_querier
> + test_vlan_active_final
> +
> + log_test "Mcast VLAN inactive, snooping for this VLAN disabled test"
> +}
> +
> +test_vlan_inactive_other_querier_norespdelay()
> +{
> + RET=0
> +
> + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> + test_active_setup_config "1" "2" "" ""
> + test_vlan_active_setup_config "1" "2" "3" "4"
> + test_vlan_active_setup_add_vlan
> + test_vlan_active_setup_config_vlan "1" "2" "" "4"
> + #test_active_setup_wait
> +
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> + mcast_vlan_assert_inactive_v4
> + mcast_vlan_assert_inactive_v6
> +
> + test_active_setup_wait
> + test_vlan_active_final
> +
> + log_test "Mcast VLAN inactive, other querier, no response delay test"
> +}
> +
> +test_vlan_inactive_own_querier_norespdelay()
> +{
> + RET=0
> +
> + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> + test_active_setup_config "1" "2" "" ""
> + test_vlan_active_setup_config "1" "2" "3" "4"
> + test_vlan_active_setup_add_vlan
> + test_vlan_active_setup_config_vlan "1" "2" "3" ""
> + #test_active_setup_wait
> +
> + mcast_assert_inactive_v4
> + mcast_assert_inactive_v6
> + mcast_vlan_assert_inactive_v4
> + mcast_vlan_assert_inactive_v6
> +
> + test_active_setup_wait
> + test_vlan_active_final
> +
> + log_test "Mcast VLAN inactive, own querier, no response delay test"
> +}
> +
The test should check if the new attributes are supported by iproute2
and skip if they are not present. See bridge_activity_notify.sh, for
example.
> +trap cleanup EXIT
> +
> +setup_prepare
> +setup_wait
> +
> +tests_run
> +
> +exit $EXIT_STATUS
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 03/14] net: bridge: mcast: avoid sleeping on bridge-down
2026-02-06 2:52 ` [PATCH net-next v2 03/14] net: bridge: mcast: avoid sleeping on bridge-down Linus Lüssing
2026-02-08 11:41 ` Ido Schimmel
@ 2026-02-08 16:01 ` Ido Schimmel
1 sibling, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:01 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:09AM +0100, Linus Lüssing wrote:
> We later want to use the multicast lock when setting the bridge
> interface up or down, to be able to atomically both check all conditions
> to toggle the multicast active state and to subsequently toggle it.
> While most variables we check / contexts we check from are serialized
> (toggled variables through netlink/sysfs) the timer_pending() check is
> not and might run in parallel.
>
> However so far we are not allowed to spinlock __br_multicast_stop() as
> its call to timer_delete_sync() might sleep. Therefore replacing the
> sleeping variant with the non-sleeping one. It is sufficient to only
> wait for any timer callback to finish when we are freeing the multicast
> context.
>
> Using the timer_shutdown() instead of the timer_delete() variant also
> allows us to detect that we are stopping from within the according timer
> callbacks, to retain the promise of the previous timer_delete_sync()
> calls that no multicast state is changed after these
> timer_{delete,shutdown}*() calls. And more importantly that we are not
> inadvertently rearming timers.
Can you clarify what you mean by "allows us to detect that we are
stopping from within the according timer callbacks"?
>
> This new check also makes the netif_running() check redundant/obsolete
> in these contexts.
>
> Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
> ---
> net/bridge/br_device.c | 4 ++
> net/bridge/br_multicast.c | 108 ++++++++++++++++++++++++++------------
> net/bridge/br_private.h | 5 ++
> net/bridge/br_vlan.c | 5 ++
> 4 files changed, 87 insertions(+), 35 deletions(-)
>
> diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> index a818fdc22da9..d9d1227d5708 100644
> --- a/net/bridge/br_device.c
> +++ b/net/bridge/br_device.c
> @@ -168,7 +168,9 @@ static int br_dev_open(struct net_device *dev)
> netdev_update_features(dev);
> netif_start_queue(dev);
> br_stp_enable_bridge(br);
> + spin_lock_bh(&br->multicast_lock);
> br_multicast_open(br);
Maybe move the spin_lock_bh() / spin_unlock_bh() to br_multicast_open()
and have it call br_multicast_open_locked() that will also be invoked
from br_multicast_toggle()?
> + spin_unlock_bh(&br->multicast_lock);
>
> if (br_opt_get(br, BROPT_MULTICAST_ENABLED))
> br_multicast_join_snoopers(br);
> @@ -191,7 +193,9 @@ static int br_dev_stop(struct net_device *dev)
> struct net_bridge *br = netdev_priv(dev);
>
> br_stp_disable_bridge(br);
> + spin_lock_bh(&br->multicast_lock);
> br_multicast_stop(br);
And like br_multicast_open(), move the locking into br_multicast_stop()?
> + spin_unlock_bh(&br->multicast_lock);
>
> if (br_opt_get(br, BROPT_MULTICAST_ENABLED))
> br_multicast_leave_snoopers(br);
> diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
> index dccae08b4f4c..f5a368dd20a3 100644
> --- a/net/bridge/br_multicast.c
> +++ b/net/bridge/br_multicast.c
> @@ -1665,6 +1665,14 @@ static void br_multicast_router_expired(struct net_bridge_mcast_port *pmctx,
> spin_unlock(&br->multicast_lock);
> }
>
> +static bool br_multicast_stopping(struct net_bridge *br,
Nit: br_multicast_is_stopping() ?
> + struct timer_list *timer)
> +{
> + lockdep_assert_held_once(&br->multicast_lock);
> +
> + return !timer->function;
> +}
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 04/14] net: bridge: mcast: track active state, IGMP/MLD querier appearance
2026-02-06 2:52 ` [PATCH net-next v2 04/14] net: bridge: mcast: track active state, IGMP/MLD querier appearance Linus Lüssing
@ 2026-02-08 16:07 ` Ido Schimmel
0 siblings, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:07 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:10AM +0100, Linus Lüssing wrote:
> +static void br_multicast_notify_active(struct net_bridge_mcast *brmctx,
> + bool ip4_active_old, bool ip6_active_old)
> +{
> + if (brmctx->ip4_active == ip4_active_old &&
> + brmctx->ip6_active == ip6_active_old)
> + return;
> +
> + br_info(brmctx->br, "mc_active changed, vid: %i: v4: %i->%i, v6: %i->%i\n",
> + brmctx->vlan ? brmctx->vlan->vid : -1,
> + ip4_active_old, brmctx->ip4_active,
> + ip6_active_old, brmctx->ip6_active);
Make this br_debug() to avoid spamming the kernel log?
I am aware that this can also be notified over netlink, but it will add
extra complexity and I am not sure anyone will use, so it might be best
to defer it for now.
> +}
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 05/14] net: bridge: mcast: track active state, foreign IGMP/MLD querier disappearance
2026-02-06 2:52 ` [PATCH net-next v2 05/14] net: bridge: mcast: track active state, foreign IGMP/MLD querier disappearance Linus Lüssing
2026-02-07 4:56 ` [net-next,v2,05/14] " Jakub Kicinski
@ 2026-02-08 16:08 ` Ido Schimmel
1 sibling, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:08 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:11AM +0100, Linus Lüssing wrote:
> This change ensures that the new multicast active state variable is unset
> again after a foreign IGMP/MLD querier has disappeared (default: 255
> seconds). If no new, other IGMP/MLD querier took over then we can't
> reliably receive IGMP/MLD reports anymore and in turn can't ensure the
> completeness of our MDB anymore either.
>
> No functional change for the fast/data path yet.
>
> Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 06/14] net: bridge: mcast: track active state, IPv6 address availability
2026-02-06 2:52 ` [PATCH net-next v2 06/14] net: bridge: mcast: track active state, IPv6 address availability Linus Lüssing
@ 2026-02-08 16:08 ` Ido Schimmel
0 siblings, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:08 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:12AM +0100, Linus Lüssing wrote:
> If we are the only potential MLD querier but don't have an IPv6
> link-local address configured on our bridge interface then we can't
> create a valid MLD query and in turn can't reliably receive MLD reports
> and can't build a complete MDB. Hence disable the new multicast active
> state variable then. Or reenable it if an IPv6 link-local address
> became available.
>
> No functional change for the fast/data path yet.
>
> Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
> ---
> net/bridge/br_multicast.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
> index 0fc29875db9c..a1cde2ba2a3e 100644
> --- a/net/bridge/br_multicast.c
> +++ b/net/bridge/br_multicast.c
> @@ -1125,6 +1125,7 @@ static void br_multicast_notify_active(struct net_bridge_mcast *brmctx,
> * The multicast active state is set, per protocol family, if:
> *
> * - an IGMP/MLD querier is present
> + * - for own IPv6 MLD querier: an IPv6 address is configured on the bridge
an IPv6 link-local address
> *
> * And is unset otherwise.
> *
> @@ -1222,10 +1223,12 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm
> &ip6h->daddr, 0, &ip6h->saddr)) {
> kfree_skb(skb);
> br_opt_toggle(brmctx->br, BROPT_HAS_IPV6_ADDR, false);
> + br_multicast_update_active(brmctx);
> return NULL;
> }
>
> br_opt_toggle(brmctx->br, BROPT_HAS_IPV6_ADDR, true);
> + br_multicast_update_active(brmctx);
> ipv6_eth_mc_map(&ip6h->daddr, eth->h_dest);
>
> hopopt = (u8 *)(ip6h + 1);
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 07/14] net: bridge: mcast: track active state, own MLD querier disappearance
2026-02-06 2:52 ` [PATCH net-next v2 07/14] net: bridge: mcast: track active state, own MLD querier disappearance Linus Lüssing
@ 2026-02-08 16:09 ` Ido Schimmel
0 siblings, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:09 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
In subject: s/own MLD querier disappearance/own querier disappearance/ ?
On Fri, Feb 06, 2026 at 03:52:13AM +0100, Linus Lüssing wrote:
> This change ensures that the new multicast active state variable is
> immediately unset if our internal IGMP/MLD querier was elected and
> now disabled.
>
> If no IGMP/MLD querier exists on the link then we can't reliably receive
> IGMP/MLD reports and in turn can't ensure the completeness of our MDB
> anymore either.
>
> No functional change for the fast/data path yet. This is the last
> necessary check before using the new multicast active state variable
> in the fast/data path, too.
The last sentence needs to be dropped?
>
> Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
Code looks fine:
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
> ---
> net/bridge/br_multicast.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
> index a1cde2ba2a3e..2710c21daef5 100644
> --- a/net/bridge/br_multicast.c
> +++ b/net/bridge/br_multicast.c
> @@ -4914,6 +4914,7 @@ int br_multicast_set_querier(struct net_bridge_mcast *brmctx, unsigned long val)
> #endif
>
> unlock:
> + br_multicast_update_active(brmctx);
> spin_unlock_bh(&brmctx->br->multicast_lock);
>
> return 0;
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 08/14] net: bridge: mcast: track active state, if snooping is enabled
2026-02-06 2:52 ` [PATCH net-next v2 08/14] net: bridge: mcast: track active state, if snooping is enabled Linus Lüssing
@ 2026-02-08 16:09 ` Ido Schimmel
0 siblings, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:09 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:14AM +0100, Linus Lüssing wrote:
> @@ -4471,6 +4481,7 @@ void br_multicast_toggle_one_vlan(struct net_bridge_vlan *vlan, bool on)
>
> spin_lock_bh(&br->multicast_lock);
> vlan->priv_flags ^= BR_VLFLAG_MCAST_ENABLED;
> + br_multicast_update_active(&vlan->br_mcast_ctx);
Can you move this to the next patch so that this patch is only about the
global multicast snooping and the next patch is about the per-vlan
multicast snooping?
>
> if (on)
> __br_multicast_open(&vlan->br_mcast_ctx);
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 09/14] net: bridge: mcast: track active state, VLAN snooping
2026-02-06 2:52 ` [PATCH net-next v2 09/14] net: bridge: mcast: track active state, VLAN snooping Linus Lüssing
@ 2026-02-08 16:10 ` Ido Schimmel
0 siblings, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:10 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:15AM +0100, Linus Lüssing wrote:
> If VLAN aware multicast snooping is enabled then we need to perform a
> few extra checks to figure out if multicast snooping is actually enabled
> for a specific VLAN, as there is then an additional per VLAN multicast
> snooping toggle.
>
> Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
> ---
> net/bridge/br_multicast.c | 20 ++++++++++++++++++++
> 1 file changed, 20 insertions(+)
>
> diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
> index 4bdc3838c3dc..2a2f54009e0f 100644
> --- a/net/bridge/br_multicast.c
> +++ b/net/bridge/br_multicast.c
> @@ -1143,6 +1143,26 @@ static void br_multicast_update_active(struct net_bridge_mcast *brmctx)
> if (!br_opt_get(brmctx->br, BROPT_MULTICAST_ENABLED))
> force_inactive = true;
Once you set 'force_inactive' the other checks become redundant, maybe
just jump to br_ip{4,6}_multicast_update_active()?
>
> + if (br_opt_get(brmctx->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED)) {
> + /* with per-vlan snooping enabled there is an extra per-vlan
> + * toggle to enable/disable snooping which we must check
> + */
> + if (br_multicast_ctx_vlan_global_disabled(brmctx))
> + force_inactive = true;
> +
> + /* with per-vlan snooping enabled the non-vlan multicast
> + * snooping context is inactive
> + */
> + if (!br_multicast_ctx_is_vlan(brmctx))
> + force_inactive = true;
> + } else {
> + /* with per-vlan snooping disabled a vlan multicast
> + * snooping context is inactive
> + */
> + if (br_multicast_ctx_is_vlan(brmctx))
> + force_inactive = true;
> + }
> +
> br_ip4_multicast_update_active(brmctx, force_inactive);
> br_ip6_multicast_update_active(brmctx, force_inactive);
>
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 10/14] net: bridge: mcast: track active state, bridge up/down
2026-02-06 2:52 ` [PATCH net-next v2 10/14] net: bridge: mcast: track active state, bridge up/down Linus Lüssing
@ 2026-02-08 16:10 ` Ido Schimmel
0 siblings, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:10 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:16AM +0100, Linus Lüssing wrote:
> diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
> index 2a2f54009e0f..7a32c6bed111 100644
> --- a/net/bridge/br_multicast.c
> +++ b/net/bridge/br_multicast.c
> @@ -1124,6 +1124,7 @@ static void br_multicast_notify_active(struct net_bridge_mcast *brmctx,
> *
> * The multicast active state is set, per protocol family, if:
> *
> + * - the bridge interface is up
> * - multicast snooping is enabled
> * - an IGMP/MLD querier is present
> * - for own IPv6 MLD querier: an IPv6 address is configured on the bridge
> @@ -1140,6 +1141,9 @@ static void br_multicast_update_active(struct net_bridge_mcast *brmctx)
>
> lockdep_assert_held_once(&brmctx->br->multicast_lock);
>
> + if (!netif_running(brmctx->br->dev))
> + force_inactive = true;
> +
Same comment as before. Let's skip unnecessary checks
> if (!br_opt_get(brmctx->br, BROPT_MULTICAST_ENABLED))
> force_inactive = true;
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 11/14] net: bridge: mcast: track active state, prepare for outside lock reads
2026-02-06 2:52 ` [PATCH net-next v2 11/14] net: bridge: mcast: track active state, prepare for outside lock reads Linus Lüssing
@ 2026-02-08 16:11 ` Ido Schimmel
0 siblings, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:11 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:17AM +0100, Linus Lüssing wrote:
> @@ -1095,23 +1095,25 @@ static void br_ip6_multicast_update_active(struct net_bridge_mcast *brmctx,
> {
> #if IS_ENABLED(CONFIG_IPV6)
> if (force_inactive)
> - brmctx->ip6_active = false;
> + WRITE_ONCE(brmctx->ip6_active, false);
> else
> - brmctx->ip6_active = br_ip6_multicast_querier_exists(brmctx);
> + WRITE_ONCE(brmctx->ip6_active, br_ip6_multicast_querier_exists(brmctx));
> #endif
> }
>
> static void br_multicast_notify_active(struct net_bridge_mcast *brmctx,
> bool ip4_active_old, bool ip6_active_old)
> {
> - if (brmctx->ip4_active == ip4_active_old &&
> - brmctx->ip6_active == ip6_active_old)
> + int ip4_active = READ_ONCE(brmctx->ip4_active);
> + int ip6_active = READ_ONCE(brmctx->ip6_active);
I believe that this is unnecessary since we are holding the lock and it
will confuse people reading the code. We only need the READ_ONCE() later
on in the data path
> +
> + if (ip4_active == ip4_active_old &&
> + ip6_active == ip6_active_old)
> return;
>
> br_info(brmctx->br, "mc_active changed, vid: %i: v4: %i->%i, v6: %i->%i\n",
> brmctx->vlan ? brmctx->vlan->vid : -1,
> - ip4_active_old, brmctx->ip4_active,
> - ip6_active_old, brmctx->ip6_active);
> + ip4_active_old, ip4_active, ip6_active_old, ip6_active);
> }
>
> /**
> @@ -1136,7 +1138,8 @@ static void br_multicast_notify_active(struct net_bridge_mcast *brmctx,
> */
> static void br_multicast_update_active(struct net_bridge_mcast *brmctx)
> {
> - bool ip4_active_old = brmctx->ip4_active, ip6_active_old = brmctx->ip6_active;
> + bool ip4_active_old = READ_ONCE(brmctx->ip4_active);
> + bool ip6_active_old = READ_ONCE(brmctx->ip6_active);
Same
> bool force_inactive = false;
>
> lockdep_assert_held_once(&brmctx->br->multicast_lock);
> @@ -4266,13 +4269,13 @@ void br_multicast_ctx_init(struct net_bridge *br,
> brmctx->multicast_membership_interval = 260 * HZ;
>
> brmctx->ip4_querier.port_ifidx = 0;
> - brmctx->ip4_active = 0;
> + WRITE_ONCE(brmctx->ip4_active, 0);
> seqcount_spinlock_init(&brmctx->ip4_querier.seq, &br->multicast_lock);
> brmctx->multicast_igmp_version = 2;
> #if IS_ENABLED(CONFIG_IPV6)
> brmctx->multicast_mld_version = 1;
> brmctx->ip6_querier.port_ifidx = 0;
> - brmctx->ip6_active = 0;
> + WRITE_ONCE(brmctx->ip6_active, 0);
> seqcount_spinlock_init(&brmctx->ip6_querier.seq, &br->multicast_lock);
> #endif
>
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 12/14] net: bridge: mcast: use combined active state in netlink
2026-02-06 2:52 ` [PATCH net-next v2 12/14] net: bridge: mcast: use combined active state in netlink Linus Lüssing
@ 2026-02-08 16:11 ` Ido Schimmel
0 siblings, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:11 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:18AM +0100, Linus Lüssing wrote:
> Use the new multicast ip{4,6}_active variables for the netlink output.
> The result for the user should be the same. But this allows us to check
> that from userspace tests, to ensure that ip{4,6}_active behave as intended
> for the upcoming fast/data path changes.
>
> Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 13/14] net: bridge: mcast: use combined active state in fast/data path
2026-02-06 2:52 ` [PATCH net-next v2 13/14] net: bridge: mcast: use combined active state in fast/data path Linus Lüssing
@ 2026-02-08 16:12 ` Ido Schimmel
0 siblings, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:12 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:19AM +0100, Linus Lüssing wrote:
> @@ -1081,6 +1101,20 @@ static bool br_ip6_multicast_querier_exists(struct net_bridge_mcast *brmctx)
> }
> #endif
>
> +static bool br_multicast_querier_exists(struct net_bridge_mcast *brmctx, int proto)
Nit: Can you change this to 'u16 proto'
> +{
> + switch (proto) {
> + case (ETH_P_IP):
And drop the parentheses?
> + return br_ip4_multicast_querier_exists(brmctx);
> +#if IS_ENABLED(CONFIG_IPV6)
> + case (ETH_P_IPV6):
> + return br_ip6_multicast_querier_exists(brmctx);
> +#endif
> + default:
> + return false;
> + }
> +}
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 14/14] net: bridge: mcast: add inactive state assertions
2026-02-06 2:52 ` [PATCH net-next v2 14/14] net: bridge: mcast: add inactive state assertions Linus Lüssing
@ 2026-02-08 16:13 ` Ido Schimmel
0 siblings, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-08 16:13 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Fri, Feb 06, 2026 at 03:52:20AM +0100, Linus Lüssing wrote:
> @@ -1418,10 +1418,29 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge_mcast *brmctx,
> return NULL;
> }
>
> +static void br_ip4_multicast_assert_inactive(struct net_bridge_mcast *brmctx)
> +{
> + WARN_ON(br_multicast_snooping_active(brmctx, htons(ETH_P_IP), NULL));
Can't this be WARN_ON_ONCE()? If something is actually wrong, WARN_ON()
has the potential to flood the kernel log
> +}
> +
> +static void br_ip6_multicast_assert_inactive(struct net_bridge_mcast *brmctx)
> +{
> + WARN_ON(br_multicast_snooping_active(brmctx, htons(ETH_P_IPV6), NULL));
Same
> +}
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 02/14] net: bridge: mcast: track active state, adding tests
2026-02-08 16:00 ` Ido Schimmel
@ 2026-02-10 21:06 ` Linus Lüssing
2026-02-11 9:42 ` Ido Schimmel
0 siblings, 1 reply; 35+ messages in thread
From: Linus Lüssing @ 2026-02-10 21:06 UTC (permalink / raw)
To: Ido Schimmel
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Sun, Feb 08, 2026 at 06:00:46PM +0200, Ido Schimmel wrote:
[...]
> > +test_inactive_other_querier_norespdelay()
> > +{
> > + RET=0
> > +
> > + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> > + test_active_setup_config "1" "2" "3" ""
> > + #test_active_setup_wait
>
> Why the comment? There are more instances below
The intention was just to highlight what was skipped in comparison
to the according test_{vlan,}_active_*_querier() variants.
Do you think I should remove these lines? Or add a brief
clarification, something like "# skipped: test_active_setup_wait"?
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [net-next,v2,05/14] net: bridge: mcast: track active state, foreign IGMP/MLD querier disappearance
2026-02-07 4:56 ` [net-next,v2,05/14] " Jakub Kicinski
@ 2026-02-11 3:05 ` Linus Lüssing
0 siblings, 0 replies; 35+ messages in thread
From: Linus Lüssing @ 2026-02-11 3:05 UTC (permalink / raw)
To: Jakub Kicinski
Cc: razor, kuniyu, davem, edumazet, idosch, sdf, shaw.leon, netdev,
bridge, horms, pabeni, andrew+netdev, linux-kernel
Hi Jakub,
Thanks for checking.
On Fri, Feb 06, 2026 at 08:56:05PM -0800, Jakub Kicinski wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> [...]
> This isn't a bug, but I noticed a couple of unaddressed review comments
> from Ido Schimmel's feedback on v1 back in May 2025 that don't appear to
> have been incorporated into this v2 series.
I have the feeling this bot got confused somehow, maybe it does
not fully get how things got restructured in
br_multicast_querier_exists(), __br_multicast_querier_exists()
and the newly added br_multicast_snooping_active()?
>
> In __br_multicast_querier_exists() in net/bridge/br_private.h around line
> 1164, the 'is_ipv6' bool parameter is marked const. Since this is a
> pass-by-value parameter, the const qualifier doesn't provide any benefit.
> Should it be removed?
>
> Reference: https://lore.kernel.org/all/aDNPxdmDPYwasUEM@shredder/
I'm not adding or moving any "const bool is_ipv6" anymore. Au
contraire, I'm explicitly removing the "const" in is_ipv6 to
__br_multicast_querier_exists() while restructuring
things in [PATCH v2 13/14] now?
>
> Also in br_multicast_querier_exists() in net/bridge/br_private.h around
> line 1182, the function takes a struct ethhdr *eth parameter but only uses
> eth->h_proto. Would it make more sense to either make the 'eth' parameter
> const, or just pass the EtherType (__be16) directly instead of the entire
> struct pointer?
>
> Reference: https://lore.kernel.org/all/aDNPxdmDPYwasUEM@shredder/
>
I think I also followed this one, too? In [PATCH v2 13/14] I'm
removing passing a "struct ethhdr *eth" and am instead passing an
"int proto" to br_multicast_querier_exists() and an
"__be16 eth_proto" to br_multicast_snooping_active()?
(I'm also unsure why this reply was to this patch and not
[PATCH v2 13/14] ->
https://patchwork.kernel.org/project/netdevbpf/patch/20260206030123.5430-14-linus.luessing@c0d3.blue/)
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v2 02/14] net: bridge: mcast: track active state, adding tests
2026-02-10 21:06 ` Linus Lüssing
@ 2026-02-11 9:42 ` Ido Schimmel
0 siblings, 0 replies; 35+ messages in thread
From: Ido Schimmel @ 2026-02-11 9:42 UTC (permalink / raw)
To: Linus Lüssing
Cc: bridge, netdev, linux-kernel, Nikolay Aleksandrov, Andrew Lunn,
Simon Horman, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S . Miller, Kuniyuki Iwashima, Stanislav Fomichev,
Xiao Liang
On Tue, Feb 10, 2026 at 10:06:58PM +0100, Linus Lüssing wrote:
> On Sun, Feb 08, 2026 at 06:00:46PM +0200, Ido Schimmel wrote:
> [...]
> > > +test_inactive_other_querier_norespdelay()
> > > +{
> > > + RET=0
> > > +
> > > + test_active_setup_bridge "1" "2" "3" "4" "5" "6"
> > > + test_active_setup_config "1" "2" "3" ""
> > > + #test_active_setup_wait
> >
> > Why the comment? There are more instances below
>
> The intention was just to highlight what was skipped in comparison
> to the according test_{vlan,}_active_*_querier() variants.
>
> Do you think I should remove these lines? Or add a brief
> clarification, something like "# skipped: test_active_setup_wait"?
OK, I see. A brief clarification would be good. I initially thought that
these are leftovers that you forgot to remove before submitting.
^ permalink raw reply [flat|nested] 35+ messages in thread
end of thread, other threads:[~2026-02-11 9:42 UTC | newest]
Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-06 2:52 [PATCH net-next v2 00/14] net: bridge: reduce multicast checks in fast path Linus Lüssing
2026-02-06 2:52 ` [PATCH net-next v2 01/14] net: bridge: mcast: export ip{4,6}_active state to netlink Linus Lüssing
2026-02-08 16:00 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 02/14] net: bridge: mcast: track active state, adding tests Linus Lüssing
2026-02-07 4:58 ` Jakub Kicinski
2026-02-08 16:00 ` Ido Schimmel
2026-02-10 21:06 ` Linus Lüssing
2026-02-11 9:42 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 03/14] net: bridge: mcast: avoid sleeping on bridge-down Linus Lüssing
2026-02-08 11:41 ` Ido Schimmel
2026-02-08 16:01 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 04/14] net: bridge: mcast: track active state, IGMP/MLD querier appearance Linus Lüssing
2026-02-08 16:07 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 05/14] net: bridge: mcast: track active state, foreign IGMP/MLD querier disappearance Linus Lüssing
2026-02-07 4:56 ` [net-next,v2,05/14] " Jakub Kicinski
2026-02-11 3:05 ` Linus Lüssing
2026-02-08 16:08 ` [PATCH net-next v2 05/14] " Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 06/14] net: bridge: mcast: track active state, IPv6 address availability Linus Lüssing
2026-02-08 16:08 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 07/14] net: bridge: mcast: track active state, own MLD querier disappearance Linus Lüssing
2026-02-08 16:09 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 08/14] net: bridge: mcast: track active state, if snooping is enabled Linus Lüssing
2026-02-08 16:09 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 09/14] net: bridge: mcast: track active state, VLAN snooping Linus Lüssing
2026-02-08 16:10 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 10/14] net: bridge: mcast: track active state, bridge up/down Linus Lüssing
2026-02-08 16:10 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 11/14] net: bridge: mcast: track active state, prepare for outside lock reads Linus Lüssing
2026-02-08 16:11 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 12/14] net: bridge: mcast: use combined active state in netlink Linus Lüssing
2026-02-08 16:11 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 13/14] net: bridge: mcast: use combined active state in fast/data path Linus Lüssing
2026-02-08 16:12 ` Ido Schimmel
2026-02-06 2:52 ` [PATCH net-next v2 14/14] net: bridge: mcast: add inactive state assertions Linus Lüssing
2026-02-08 16:13 ` Ido Schimmel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox