Merge branch 'bridge-handle-changes-in-vlan_flag_bridge_binding'

Petr Machata says:

====================
bridge: Handle changes in VLAN_FLAG_BRIDGE_BINDING

When bridge binding is enabled on a VLAN netdevice, its link state should
track bridge ports that are members of the corresponding VLAN. This works
for a newly-added netdevices. However toggling the option does not have the
effect of enabling or disabling the behavior as appropriate.

In this patchset, have bridge react to bridge_binding toggles on VLAN
uppers.

There has been another attempt at supporting this behavior in 2022 by
Sevinj Aghayeva [0]. A discussion ensued that informed how this new
patchset is constructed, namely that the logic is in the bridge as opposed
to the 8021q driver, and the bridge reacts to NETDEV_CHANGE events on the
8021q upper.

Patches #1 and #2 contain the implementation, patches #3 and #4 a
selftest.

[0] https://lore.kernel.org/netdev/cover.1660100506.git.sevinj.aghayeva@gmail.com/
====================

Link: https://patch.msgid.link/cover.1734540770.git.petrm@nvidia.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2024-12-20 13:16:46 -08:00
commit c1bc6d2178
6 changed files with 340 additions and 8 deletions

View File

@ -51,6 +51,13 @@ static int br_device_event(struct notifier_block *unused, unsigned long event, v
}
}
if (is_vlan_dev(dev)) {
struct net_device *real_dev = vlan_dev_real_dev(dev);
if (netif_is_bridge_master(real_dev))
br_vlan_vlan_upper_event(real_dev, dev, event);
}
/* not a port of a bridge */
p = br_port_get_rtnl(dev);
if (!p)

View File

@ -1571,6 +1571,9 @@ void br_vlan_get_stats(const struct net_bridge_vlan *v,
void br_vlan_port_event(struct net_bridge_port *p, unsigned long event);
int br_vlan_bridge_event(struct net_device *dev, unsigned long event,
void *ptr);
void br_vlan_vlan_upper_event(struct net_device *br_dev,
struct net_device *vlan_dev,
unsigned long event);
int br_vlan_rtnl_init(void);
void br_vlan_rtnl_uninit(void);
void br_vlan_notify(const struct net_bridge *br,
@ -1802,6 +1805,12 @@ static inline int br_vlan_bridge_event(struct net_device *dev,
return 0;
}
static inline void br_vlan_vlan_upper_event(struct net_device *br_dev,
struct net_device *vlan_dev,
unsigned long event)
{
}
static inline int br_vlan_rtnl_init(void)
{
return 0;

View File

@ -1664,6 +1664,18 @@ static void br_vlan_set_all_vlan_dev_state(struct net_bridge_port *p)
}
}
static void br_vlan_toggle_bridge_binding(struct net_device *br_dev,
bool enable)
{
struct net_bridge *br = netdev_priv(br_dev);
if (enable)
br_opt_toggle(br, BROPT_VLAN_BRIDGE_BINDING, true);
else
br_opt_toggle(br, BROPT_VLAN_BRIDGE_BINDING,
br_vlan_has_upper_bind_vlan_dev(br_dev));
}
static void br_vlan_upper_change(struct net_device *dev,
struct net_device *upper_dev,
bool linking)
@ -1673,13 +1685,9 @@ static void br_vlan_upper_change(struct net_device *dev,
if (!br_vlan_is_bind_vlan_dev(upper_dev))
return;
if (linking) {
br_vlan_toggle_bridge_binding(dev, linking);
if (linking)
br_vlan_set_vlan_dev_state(br, upper_dev);
br_opt_toggle(br, BROPT_VLAN_BRIDGE_BINDING, true);
} else {
br_opt_toggle(br, BROPT_VLAN_BRIDGE_BINDING,
br_vlan_has_upper_bind_vlan_dev(dev));
}
}
struct br_vlan_link_state_walk_data {
@ -1764,6 +1772,30 @@ int br_vlan_bridge_event(struct net_device *dev, unsigned long event, void *ptr)
return ret;
}
void br_vlan_vlan_upper_event(struct net_device *br_dev,
struct net_device *vlan_dev,
unsigned long event)
{
struct vlan_dev_priv *vlan = vlan_dev_priv(vlan_dev);
struct net_bridge *br = netdev_priv(br_dev);
bool bridge_binding;
switch (event) {
case NETDEV_CHANGE:
case NETDEV_UP:
break;
default:
return;
}
bridge_binding = vlan->flags & VLAN_FLAG_BRIDGE_BINDING;
br_vlan_toggle_bridge_binding(br_dev, bridge_binding);
if (bridge_binding)
br_vlan_set_vlan_dev_state(br, vlan_dev);
else if (!bridge_binding && netif_carrier_ok(br_dev))
netif_carrier_on(vlan_dev);
}
/* Must be protected by RTNL. */
void br_vlan_port_event(struct net_bridge_port *p, unsigned long event)
{

View File

@ -96,6 +96,7 @@ TEST_PROGS += test_bridge_backup_port.sh
TEST_PROGS += fdb_flush.sh fdb_notify.sh
TEST_PROGS += fq_band_pktlimit.sh
TEST_PROGS += vlan_hw_filter.sh
TEST_PROGS += vlan_bridge_binding.sh
TEST_PROGS += bpf_offload.py
TEST_PROGS += ipv6_route_update_soft_lockup.sh
TEST_PROGS += busy_poll_test.sh

View File

@ -477,12 +477,33 @@ ip_link_set_addr()
defer ip link set dev "$name" address "$old_addr"
}
ip_link_is_up()
{
local name=$1; shift
local state=$(ip -j link show "$name" |
jq -r '(.[].flags[] | select(. == "UP")) // "DOWN"')
[[ $state == "UP" ]]
}
ip_link_set_up()
{
local name=$1; shift
ip link set dev "$name" up
defer ip link set dev "$name" down
if ! ip_link_is_up "$name"; then
ip link set dev "$name" up
defer ip link set dev "$name" down
fi
}
ip_link_set_down()
{
local name=$1; shift
if ip_link_is_up "$name"; then
ip link set dev "$name" down
defer ip link set dev "$name" up
fi
}
ip_addr_add()
@ -498,3 +519,9 @@ ip_route_add()
ip route add "$@"
defer ip route del "$@"
}
bridge_vlan_add()
{
bridge vlan add "$@"
defer bridge vlan del "$@"
}

View File

@ -0,0 +1,256 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source lib.sh
ALL_TESTS="
test_binding_on
test_binding_off
test_binding_toggle_on
test_binding_toggle_off
test_binding_toggle_on_when_upper_down
test_binding_toggle_off_when_upper_down
test_binding_toggle_on_when_lower_down
test_binding_toggle_off_when_lower_down
"
setup_prepare()
{
local port
ip_link_add br up type bridge vlan_filtering 1
for port in d1 d2 d3; do
ip_link_add $port type veth peer name r$port
ip_link_set_up $port
ip_link_set_up r$port
ip_link_set_master $port br
done
bridge_vlan_add vid 11 dev br self
bridge_vlan_add vid 11 dev d1 master
bridge_vlan_add vid 12 dev br self
bridge_vlan_add vid 12 dev d2 master
bridge_vlan_add vid 13 dev br self
bridge_vlan_add vid 13 dev d1 master
bridge_vlan_add vid 13 dev d2 master
bridge_vlan_add vid 14 dev br self
bridge_vlan_add vid 14 dev d1 master
bridge_vlan_add vid 14 dev d2 master
bridge_vlan_add vid 14 dev d3 master
}
operstate_is()
{
local dev=$1; shift
local expect=$1; shift
local operstate=$(ip -j link show $dev | jq -r .[].operstate)
if [[ $operstate == UP ]]; then
operstate=1
elif [[ $operstate == DOWN || $operstate == LOWERLAYERDOWN ]]; then
operstate=0
fi
echo -n $operstate
[[ $operstate == $expect ]]
}
check_operstate()
{
local dev=$1; shift
local expect=$1; shift
local operstate
operstate=$(busywait 1000 \
operstate_is "$dev" "$expect")
check_err $? "Got operstate of $operstate, expected $expect"
}
add_one_vlan()
{
local link=$1; shift
local id=$1; shift
ip_link_add $link.$id link $link type vlan id $id "$@"
}
add_vlans()
{
add_one_vlan br 11 "$@"
add_one_vlan br 12 "$@"
add_one_vlan br 13 "$@"
add_one_vlan br 14 "$@"
}
set_vlans()
{
ip link set dev br.11 "$@"
ip link set dev br.12 "$@"
ip link set dev br.13 "$@"
ip link set dev br.14 "$@"
}
down_netdevs()
{
local dev
for dev in "$@"; do
ip_link_set_down $dev
done
}
check_operstates()
{
local opst_11=$1; shift
local opst_12=$1; shift
local opst_13=$1; shift
local opst_14=$1; shift
check_operstate br.11 $opst_11
check_operstate br.12 $opst_12
check_operstate br.13 $opst_13
check_operstate br.14 $opst_14
}
do_test_binding()
{
local inject=$1; shift
local what=$1; shift
local opsts_d1=$1; shift
local opsts_d2=$1; shift
local opsts_d12=$1; shift
local opsts_d123=$1; shift
RET=0
defer_scope_push
down_netdevs d1
$inject
check_operstates $opsts_d1
defer_scope_pop
defer_scope_push
down_netdevs d2
$inject
check_operstates $opsts_d2
defer_scope_pop
defer_scope_push
down_netdevs d1 d2
$inject
check_operstates $opsts_d12
defer_scope_pop
defer_scope_push
down_netdevs d1 d2 d3
$inject
check_operstates $opsts_d123
defer_scope_pop
log_test "Test bridge_binding $what"
}
do_test_binding_on()
{
local inject=$1; shift
local what=$1; shift
do_test_binding "$inject" "$what" \
"0 1 1 1" \
"1 0 1 1" \
"0 0 0 1" \
"0 0 0 0"
}
do_test_binding_off()
{
local inject=$1; shift
local what=$1; shift
do_test_binding "$inject" "$what" \
"1 1 1 1" \
"1 1 1 1" \
"1 1 1 1" \
"0 0 0 0"
}
test_binding_on()
{
add_vlans bridge_binding on
set_vlans up
do_test_binding_on : "on"
}
test_binding_off()
{
add_vlans bridge_binding off
set_vlans up
do_test_binding_off : "off"
}
test_binding_toggle_on()
{
add_vlans bridge_binding off
set_vlans up
set_vlans type vlan bridge_binding on
do_test_binding_on : "off->on"
}
test_binding_toggle_off()
{
add_vlans bridge_binding on
set_vlans up
set_vlans type vlan bridge_binding off
do_test_binding_off : "on->off"
}
dfr_set_binding_on()
{
set_vlans type vlan bridge_binding on
defer set_vlans type vlan bridge_binding off
}
dfr_set_binding_off()
{
set_vlans type vlan bridge_binding off
defer set_vlans type vlan bridge_binding on
}
test_binding_toggle_on_when_lower_down()
{
add_vlans bridge_binding off
set_vlans up
do_test_binding_on dfr_set_binding_on "off->on when lower down"
}
test_binding_toggle_off_when_lower_down()
{
add_vlans bridge_binding on
set_vlans up
do_test_binding_off dfr_set_binding_off "on->off when lower down"
}
test_binding_toggle_on_when_upper_down()
{
add_vlans bridge_binding off
set_vlans type vlan bridge_binding on
set_vlans up
do_test_binding_on : "off->on when upper down"
}
test_binding_toggle_off_when_upper_down()
{
add_vlans bridge_binding on
set_vlans type vlan bridge_binding off
set_vlans up
do_test_binding_off : "on->off when upper down"
}
trap defer_scopes_cleanup EXIT
setup_prepare
tests_run
exit $EXIT_STATUS