Merge branch 'bridge-mc-per-vlan-qquery'

Yong Wang says:

====================
bridge: multicast: per vlan query improvement when port or vlan state changes

The current implementation of br_multicast_enable_port() only operates on
port's multicast context, which doesn't take into account in case of vlan
snooping, one downside is the port's igmp query timer will NOT resume when
port state gets changed from BR_STATE_BLOCKING to BR_STATE_FORWARDING etc.

Such code flow will briefly look like:
1.vlan snooping
  --> br_multicast_port_query_expired with per vlan port_mcast_ctx
  --> port in BR_STATE_BLOCKING state --> then one-shot timer discontinued

The port state could be changed by STP daemon or kernel STP, taking mstpd
as example:

2.mstpd --> netlink_sendmsg --> br_setlink --> br_set_port_state with non
  blocking states, i.e. BR_STATE_LEARNING or BR_STATE_FORWARDING
  --> br_port_state_selection --> br_multicast_enable_port
  --> enable multicast with port's multicast_ctx

Here for per vlan snooping, the vlan context of the port should be used
instead of port's multicast_ctx. The first patch corrects such behavior.

Similarly, vlan state change also impacts multicast behavior, the 2nd patch
adds function to update the corresponding multicast context when vlan state
changes.

The 3rd patch adds the selftests to confirm that IGMP/MLD query does happen
when the STP state becomes forwarding.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller
2025-04-23 13:02:20 +01:00
6 changed files with 261 additions and 19 deletions

View File

@@ -80,10 +80,10 @@ static void br_mst_vlan_set_state(struct net_bridge_vlan_group *vg,
if (br_vlan_get_state(v) == state)
return;
br_vlan_set_state(v, state);
if (v->vid == vg->pvid)
br_vlan_set_pvid_state(vg, state);
br_vlan_set_state(v, state);
}
int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state,

View File

@@ -2105,12 +2105,17 @@ static void __br_multicast_enable_port_ctx(struct net_bridge_mcast_port *pmctx)
}
}
void br_multicast_enable_port(struct net_bridge_port *port)
static void br_multicast_enable_port_ctx(struct net_bridge_mcast_port *pmctx)
{
struct net_bridge *br = port->br;
struct net_bridge *br = pmctx->port->br;
spin_lock_bh(&br->multicast_lock);
__br_multicast_enable_port_ctx(&port->multicast_ctx);
if (br_multicast_port_ctx_is_vlan(pmctx) &&
!(pmctx->vlan->priv_flags & BR_VLFLAG_MCAST_ENABLED)) {
spin_unlock_bh(&br->multicast_lock);
return;
}
__br_multicast_enable_port_ctx(pmctx);
spin_unlock_bh(&br->multicast_lock);
}
@@ -2137,11 +2142,67 @@ static void __br_multicast_disable_port_ctx(struct net_bridge_mcast_port *pmctx)
br_multicast_rport_del_notify(pmctx, del);
}
static void br_multicast_disable_port_ctx(struct net_bridge_mcast_port *pmctx)
{
struct net_bridge *br = pmctx->port->br;
spin_lock_bh(&br->multicast_lock);
if (br_multicast_port_ctx_is_vlan(pmctx) &&
!(pmctx->vlan->priv_flags & BR_VLFLAG_MCAST_ENABLED)) {
spin_unlock_bh(&br->multicast_lock);
return;
}
__br_multicast_disable_port_ctx(pmctx);
spin_unlock_bh(&br->multicast_lock);
}
static void br_multicast_toggle_port(struct net_bridge_port *port, bool on)
{
#if IS_ENABLED(CONFIG_BRIDGE_VLAN_FILTERING)
if (br_opt_get(port->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED)) {
struct net_bridge_vlan_group *vg;
struct net_bridge_vlan *vlan;
rcu_read_lock();
vg = nbp_vlan_group_rcu(port);
if (!vg) {
rcu_read_unlock();
return;
}
/* iterate each vlan, toggle vlan multicast context */
list_for_each_entry_rcu(vlan, &vg->vlan_list, vlist) {
struct net_bridge_mcast_port *pmctx =
&vlan->port_mcast_ctx;
u8 state = br_vlan_get_state(vlan);
/* enable vlan multicast context when state is
* LEARNING or FORWARDING
*/
if (on && br_vlan_state_allowed(state, true))
br_multicast_enable_port_ctx(pmctx);
else
br_multicast_disable_port_ctx(pmctx);
}
rcu_read_unlock();
return;
}
#endif
/* toggle port multicast context when vlan snooping is disabled */
if (on)
br_multicast_enable_port_ctx(&port->multicast_ctx);
else
br_multicast_disable_port_ctx(&port->multicast_ctx);
}
void br_multicast_enable_port(struct net_bridge_port *port)
{
br_multicast_toggle_port(port, true);
}
void br_multicast_disable_port(struct net_bridge_port *port)
{
spin_lock_bh(&port->br->multicast_lock);
__br_multicast_disable_port_ctx(&port->multicast_ctx);
spin_unlock_bh(&port->br->multicast_lock);
br_multicast_toggle_port(port, false);
}
static int __grp_src_delete_marked(struct net_bridge_port_group *pg)
@@ -4211,6 +4272,32 @@ static void __br_multicast_stop(struct net_bridge_mcast *brmctx)
#endif
}
void br_multicast_update_vlan_mcast_ctx(struct net_bridge_vlan *v, u8 state)
{
#if IS_ENABLED(CONFIG_BRIDGE_VLAN_FILTERING)
struct net_bridge *br;
if (!br_vlan_should_use(v))
return;
if (br_vlan_is_master(v))
return;
br = v->port->br;
if (!br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED))
return;
if (br_vlan_state_allowed(state, true))
br_multicast_enable_port_ctx(&v->port_mcast_ctx);
/* Multicast is not disabled for the vlan when it goes in
* blocking state because the timers will expire and stop by
* themselves without sending more queries.
*/
#endif
}
void br_multicast_toggle_one_vlan(struct net_bridge_vlan *vlan, bool on)
{
struct net_bridge *br;
@@ -4304,9 +4391,9 @@ int br_multicast_toggle_vlan_snooping(struct net_bridge *br, bool on,
__br_multicast_open(&br->multicast_ctx);
list_for_each_entry(p, &br->port_list, list) {
if (on)
br_multicast_disable_port(p);
br_multicast_disable_port_ctx(&p->multicast_ctx);
else
br_multicast_enable_port(p);
br_multicast_enable_port_ctx(&p->multicast_ctx);
}
list_for_each_entry(vlan, &vg->vlan_list, vlist)

View File

@@ -1055,6 +1055,7 @@ void br_multicast_port_ctx_init(struct net_bridge_port *port,
struct net_bridge_vlan *vlan,
struct net_bridge_mcast_port *pmctx);
void br_multicast_port_ctx_deinit(struct net_bridge_mcast_port *pmctx);
void br_multicast_update_vlan_mcast_ctx(struct net_bridge_vlan *v, u8 state);
void br_multicast_toggle_one_vlan(struct net_bridge_vlan *vlan, bool on);
int br_multicast_toggle_vlan_snooping(struct net_bridge *br, bool on,
struct netlink_ext_ack *extack);
@@ -1521,6 +1522,11 @@ static inline void br_multicast_port_ctx_deinit(struct net_bridge_mcast_port *pm
{
}
static inline void br_multicast_update_vlan_mcast_ctx(struct net_bridge_vlan *v,
u8 state)
{
}
static inline void br_multicast_toggle_one_vlan(struct net_bridge_vlan *vlan,
bool on)
{
@@ -1881,7 +1887,9 @@ 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);
/* vlan state manipulation helpers using *_ONCE to annotate lock-free access */
/* vlan state manipulation helpers using *_ONCE to annotate lock-free access,
* while br_vlan_set_state() may access data protected by multicast_lock.
*/
static inline u8 br_vlan_get_state(const struct net_bridge_vlan *v)
{
return READ_ONCE(v->state);
@@ -1890,6 +1898,7 @@ static inline u8 br_vlan_get_state(const struct net_bridge_vlan *v)
static inline void br_vlan_set_state(struct net_bridge_vlan *v, u8 state)
{
WRITE_ONCE(v->state, state);
br_multicast_update_vlan_mcast_ctx(v, state);
}
static inline u8 br_vlan_get_pvid_state(const struct net_bridge_vlan_group *vg)

View File

@@ -1,10 +1,24 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
ALL_TESTS="v2reportleave_test v3include_test v3inc_allow_test v3inc_is_include_test \
v3inc_is_exclude_test v3inc_to_exclude_test v3exc_allow_test v3exc_is_include_test \
v3exc_is_exclude_test v3exc_to_exclude_test v3inc_block_test v3exc_block_test \
v3exc_timeout_test v3star_ex_auto_add_test"
ALL_TESTS="
v2reportleave_test
v3include_test
v3inc_allow_test
v3inc_is_include_test
v3inc_is_exclude_test
v3inc_to_exclude_test
v3exc_allow_test
v3exc_is_include_test
v3exc_is_exclude_test
v3exc_to_exclude_test
v3inc_block_test
v3exc_block_test
v3exc_timeout_test
v3star_ex_auto_add_test
v2per_vlan_snooping_port_stp_test
v2per_vlan_snooping_vlan_stp_test
"
NUM_NETIFS=4
CHECK_TC="yes"
TEST_GROUP="239.10.10.10"
@@ -554,6 +568,64 @@ v3star_ex_auto_add_test()
v3cleanup $swp2 $TEST_GROUP
}
v2per_vlan_snooping_stp_test()
{
local is_port=$1
local msg="port"
[[ $is_port -ne 1 ]] && msg="vlan"
ip link set br0 up type bridge vlan_filtering 1 \
mcast_igmp_version 2 \
mcast_snooping 1 \
mcast_vlan_snooping 1 \
mcast_querier 1 \
mcast_stats_enabled 1
bridge vlan global set vid 1 dev br0 \
mcast_snooping 1 \
mcast_querier 1 \
mcast_query_interval 100 \
mcast_startup_query_count 0
[[ $is_port -eq 1 ]] && bridge link set dev $swp1 state 0
[[ $is_port -ne 1 ]] && bridge vlan set vid 1 dev $swp1 state 4
sleep 5
local tx_s=$(ip -j -p stats show dev $swp1 \
group xstats_slave subgroup bridge suite mcast \
| jq '.[]["multicast"]["igmp_queries"]["tx_v2"]')
[[ $is_port -eq 1 ]] && bridge link set dev $swp1 state 3
[[ $is_port -ne 1 ]] && bridge vlan set vid 1 dev $swp1 state 3
sleep 5
local tx_e=$(ip -j -p stats show dev $swp1 \
group xstats_slave subgroup bridge suite mcast \
| jq '.[]["multicast"]["igmp_queries"]["tx_v2"]')
RET=0
local tx=$(expr $tx_e - $tx_s)
test $tx -gt 0
check_err $? "No IGMP queries after STP state becomes forwarding"
log_test "per vlan snooping with $msg stp state change"
# restore settings
bridge vlan global set vid 1 dev br0 \
mcast_querier 0 \
mcast_query_interval 12500 \
mcast_startup_query_count 2
ip link set br0 up type bridge vlan_filtering 0 \
mcast_vlan_snooping 0 \
mcast_stats_enabled 0
}
v2per_vlan_snooping_port_stp_test()
{
v2per_vlan_snooping_stp_test 1
}
v2per_vlan_snooping_vlan_stp_test()
{
v2per_vlan_snooping_stp_test 0
}
trap cleanup EXIT
setup_prepare

View File

@@ -1,10 +1,23 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
ALL_TESTS="mldv2include_test mldv2inc_allow_test mldv2inc_is_include_test mldv2inc_is_exclude_test \
mldv2inc_to_exclude_test mldv2exc_allow_test mldv2exc_is_include_test \
mldv2exc_is_exclude_test mldv2exc_to_exclude_test mldv2inc_block_test \
mldv2exc_block_test mldv2exc_timeout_test mldv2star_ex_auto_add_test"
ALL_TESTS="
mldv2include_test
mldv2inc_allow_test
mldv2inc_is_include_test
mldv2inc_is_exclude_test
mldv2inc_to_exclude_test
mldv2exc_allow_test
mldv2exc_is_include_test
mldv2exc_is_exclude_test
mldv2exc_to_exclude_test
mldv2inc_block_test
mldv2exc_block_test
mldv2exc_timeout_test
mldv2star_ex_auto_add_test
mldv2per_vlan_snooping_port_stp_test
mldv2per_vlan_snooping_vlan_stp_test
"
NUM_NETIFS=4
CHECK_TC="yes"
TEST_GROUP="ff02::cc"
@@ -554,6 +567,66 @@ mldv2star_ex_auto_add_test()
mldv2cleanup $swp2
}
mldv2per_vlan_snooping_stp_test()
{
local is_port=$1
local msg="port"
[[ $is_port -ne 1 ]] && msg="vlan"
ip link set br0 up type bridge vlan_filtering 1 \
mcast_mld_version 2 \
mcast_snooping 1 \
mcast_vlan_snooping 1 \
mcast_querier 1 \
mcast_stats_enabled 1
bridge vlan global set vid 1 dev br0 \
mcast_mld_version 2 \
mcast_snooping 1 \
mcast_querier 1 \
mcast_query_interval 100 \
mcast_startup_query_count 0
[[ $is_port -eq 1 ]] && bridge link set dev $swp1 state 0
[[ $is_port -ne 1 ]] && bridge vlan set vid 1 dev $swp1 state 4
sleep 5
local tx_s=$(ip -j -p stats show dev $swp1 \
group xstats_slave subgroup bridge suite mcast \
| jq '.[]["multicast"]["mld_queries"]["tx_v2"]')
[[ $is_port -eq 1 ]] && bridge link set dev $swp1 state 3
[[ $is_port -ne 1 ]] && bridge vlan set vid 1 dev $swp1 state 3
sleep 5
local tx_e=$(ip -j -p stats show dev $swp1 \
group xstats_slave subgroup bridge suite mcast \
| jq '.[]["multicast"]["mld_queries"]["tx_v2"]')
RET=0
local tx=$(expr $tx_e - $tx_s)
test $tx -gt 0
check_err $? "No MLD queries after STP state becomes forwarding"
log_test "per vlan snooping with $msg stp state change"
# restore settings
bridge vlan global set vid 1 dev br0 \
mcast_querier 0 \
mcast_query_interval 12500 \
mcast_startup_query_count 2 \
mcast_mld_version 1
ip link set br0 up type bridge vlan_filtering 0 \
mcast_vlan_snooping 0 \
mcast_stats_enabled 0
}
mldv2per_vlan_snooping_port_stp_test()
{
mldv2per_vlan_snooping_stp_test 1
}
mldv2per_vlan_snooping_vlan_stp_test()
{
mldv2per_vlan_snooping_stp_test 0
}
trap cleanup EXIT
setup_prepare

View File

@@ -1,6 +1,7 @@
CONFIG_BRIDGE=m
CONFIG_VLAN_8021Q=m
CONFIG_BRIDGE_VLAN_FILTERING=y
CONFIG_BRIDGE_IGMP_SNOOPING=y
CONFIG_NET_L3_MASTER_DEV=y
CONFIG_IPV6_MULTIPLE_TABLES=y
CONFIG_NET_VRF=m