Commit c1bc6d21 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

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: default avatarJakub Kicinski <kuba@kernel.org>
parents 05dd04b2 dca12e9a
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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)
+9 −0
Original line number Diff line number Diff line
@@ -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;
+38 −6
Original line number Diff line number Diff line
@@ -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)
{
+1 −0
Original line number Diff line number Diff line
@@ -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
+29 −2
Original line number Diff line number Diff line
@@ -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

	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 "$@"
}
Loading