Commit f4b7002a authored by Nikolay Aleksandrov's avatar Nikolay Aleksandrov Committed by David S. Miller
Browse files

net: bridge: add vlan mcast snooping knob



Add a global knob that controls if vlan multicast snooping is enabled.
The proper contexts (vlan or bridge-wide) will be chosen based on the knob
when processing packets and changing bridge device state. Note that
vlans have their individual mcast snooping enabled by default, but this
knob is needed to turn on bridge vlan snooping. It is disabled by
default. To enable the knob vlan filtering must also be enabled, it
doesn't make sense to have vlan mcast snooping without vlan filtering
since that would lead to inconsistencies. Disabling vlan filtering will
also automatically disable vlan mcast snooping.

Signed-off-by: default avatarNikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 7b54aaaf
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -720,12 +720,14 @@ struct br_mcast_stats {

/* bridge boolean options
 * BR_BOOLOPT_NO_LL_LEARN - disable learning from link-local packets
 * BR_BOOLOPT_MCAST_VLAN_SNOOPING - control vlan multicast snooping
 *
 * IMPORTANT: if adding a new option do not forget to handle
 *            it in br_boolopt_toggle/get and bridge sysfs
 */
enum br_boolopt_id {
	BR_BOOLOPT_NO_LL_LEARN,
	BR_BOOLOPT_MCAST_VLAN_SNOOPING,
	BR_BOOLOPT_MAX
};

+8 −1
Original line number Diff line number Diff line
@@ -214,17 +214,22 @@ static struct notifier_block br_switchdev_notifier = {
int br_boolopt_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on,
		      struct netlink_ext_ack *extack)
{
	int err = 0;

	switch (opt) {
	case BR_BOOLOPT_NO_LL_LEARN:
		br_opt_toggle(br, BROPT_NO_LL_LEARN, on);
		break;
	case BR_BOOLOPT_MCAST_VLAN_SNOOPING:
		err = br_multicast_toggle_vlan_snooping(br, on, extack);
		break;
	default:
		/* shouldn't be called with unsupported options */
		WARN_ON(1);
		break;
	}

	return 0;
	return err;
}

int br_boolopt_get(const struct net_bridge *br, enum br_boolopt_id opt)
@@ -232,6 +237,8 @@ int br_boolopt_get(const struct net_bridge *br, enum br_boolopt_id opt)
	switch (opt) {
	case BR_BOOLOPT_NO_LL_LEARN:
		return br_opt_get(br, BROPT_NO_LL_LEARN);
	case BR_BOOLOPT_MCAST_VLAN_SNOOPING:
		return br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED);
	default:
		/* shouldn't be called with unsupported options */
		WARN_ON(1);
+5 −2
Original line number Diff line number Diff line
@@ -27,12 +27,14 @@ EXPORT_SYMBOL_GPL(nf_br_ops);
/* net device transmit always called with BH disabled */
netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
{
	struct net_bridge_mcast_port *pmctx_null = NULL;
	struct net_bridge *br = netdev_priv(dev);
	struct net_bridge_mcast *brmctx = &br->multicast_ctx;
	struct net_bridge_fdb_entry *dst;
	struct net_bridge_mdb_entry *mdst;
	const struct nf_br_ops *nf_ops;
	u8 state = BR_STATE_FORWARDING;
	struct net_bridge_vlan *vlan;
	const unsigned char *dest;
	u16 vid = 0;

@@ -54,7 +56,8 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
	skb_reset_mac_header(skb);
	skb_pull(skb, ETH_HLEN);

	if (!br_allowed_ingress(br, br_vlan_group_rcu(br), skb, &vid, &state))
	if (!br_allowed_ingress(br, br_vlan_group_rcu(br), skb, &vid,
				&state, &vlan))
		goto out;

	if (IS_ENABLED(CONFIG_INET) &&
@@ -83,7 +86,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
			br_flood(br, skb, BR_PKT_MULTICAST, false, true);
			goto out;
		}
		if (br_multicast_rcv(brmctx, NULL, skb, vid)) {
		if (br_multicast_rcv(&brmctx, &pmctx_null, vlan, skb, vid)) {
			kfree_skb(skb);
			goto out;
		}
+3 −2
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
	struct net_bridge_mdb_entry *mdst;
	bool local_rcv, mcast_hit = false;
	struct net_bridge_mcast *brmctx;
	struct net_bridge_vlan *vlan;
	struct net_bridge *br;
	u16 vid = 0;
	u8 state;
@@ -84,7 +85,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
	pmctx = &p->multicast_ctx;
	state = p->state;
	if (!br_allowed_ingress(p->br, nbp_vlan_group_rcu(p), skb, &vid,
				&state))
				&state, &vlan))
		goto out;

	nbp_switchdev_frame_mark(p, skb);
@@ -102,7 +103,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
			local_rcv = true;
		} else {
			pkt_type = BR_PKT_MULTICAST;
			if (br_multicast_rcv(brmctx, pmctx, skb, vid))
			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
				goto drop;
		}
	}
+112 −31
Original line number Diff line number Diff line
@@ -1797,9 +1797,9 @@ void br_multicast_enable_port(struct net_bridge_port *port)
{
	struct net_bridge *br = port->br;

	spin_lock(&br->multicast_lock);
	spin_lock_bh(&br->multicast_lock);
	__br_multicast_enable_port_ctx(&port->multicast_ctx);
	spin_unlock(&br->multicast_lock);
	spin_unlock_bh(&br->multicast_lock);
}

static void __br_multicast_disable_port_ctx(struct net_bridge_mcast_port *pmctx)
@@ -1827,9 +1827,9 @@ static void __br_multicast_disable_port_ctx(struct net_bridge_mcast_port *pmctx)

void br_multicast_disable_port(struct net_bridge_port *port)
{
	spin_lock(&port->br->multicast_lock);
	spin_lock_bh(&port->br->multicast_lock);
	__br_multicast_disable_port_ctx(&port->multicast_ctx);
	spin_unlock(&port->br->multicast_lock);
	spin_unlock_bh(&port->br->multicast_lock);
}

static int __grp_src_delete_marked(struct net_bridge_port_group *pg)
@@ -3510,8 +3510,9 @@ static int br_multicast_ipv6_rcv(struct net_bridge_mcast *brmctx,
}
#endif

int br_multicast_rcv(struct net_bridge_mcast *brmctx,
		     struct net_bridge_mcast_port *pmctx,
int br_multicast_rcv(struct net_bridge_mcast **brmctx,
		     struct net_bridge_mcast_port **pmctx,
		     struct net_bridge_vlan *vlan,
		     struct sk_buff *skb, u16 vid)
{
	int ret = 0;
@@ -3519,16 +3520,36 @@ int br_multicast_rcv(struct net_bridge_mcast *brmctx,
	BR_INPUT_SKB_CB(skb)->igmp = 0;
	BR_INPUT_SKB_CB(skb)->mrouters_only = 0;

	if (!br_opt_get(brmctx->br, BROPT_MULTICAST_ENABLED))
	if (!br_opt_get((*brmctx)->br, BROPT_MULTICAST_ENABLED))
		return 0;

	if (br_opt_get((*brmctx)->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) && vlan) {
		const struct net_bridge_vlan *masterv;

		/* the vlan has the master flag set only when transmitting
		 * through the bridge device
		 */
		if (br_vlan_is_master(vlan)) {
			masterv = vlan;
			*brmctx = &vlan->br_mcast_ctx;
			*pmctx = NULL;
		} else {
			masterv = vlan->brvlan;
			*brmctx = &vlan->brvlan->br_mcast_ctx;
			*pmctx = &vlan->port_mcast_ctx;
		}

		if (!(masterv->priv_flags & BR_VLFLAG_GLOBAL_MCAST_ENABLED))
			return 0;
	}

	switch (skb->protocol) {
	case htons(ETH_P_IP):
		ret = br_multicast_ipv4_rcv(brmctx, pmctx, skb, vid);
		ret = br_multicast_ipv4_rcv(*brmctx, *pmctx, skb, vid);
		break;
#if IS_ENABLED(CONFIG_IPV6)
	case htons(ETH_P_IPV6):
		ret = br_multicast_ipv6_rcv(brmctx, pmctx, skb, vid);
		ret = br_multicast_ipv6_rcv(*brmctx, *pmctx, skb, vid);
		break;
#endif
	}
@@ -3727,11 +3748,12 @@ static void __br_multicast_open(struct net_bridge_mcast *brmctx)

void br_multicast_open(struct net_bridge *br)
{
	ASSERT_RTNL();

	if (br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED)) {
		struct net_bridge_vlan_group *vg;
		struct net_bridge_vlan *vlan;

	ASSERT_RTNL();

		vg = br_vlan_group(br);
		if (vg) {
			list_for_each_entry(vlan, &vg->vlan_list, vlist) {
@@ -3743,6 +3765,7 @@ void br_multicast_open(struct net_bridge *br)
					__br_multicast_open(&vlan->br_mcast_ctx);
			}
		}
	}

	__br_multicast_open(&br->multicast_ctx);
}
@@ -3804,13 +3827,70 @@ void br_multicast_toggle_one_vlan(struct net_bridge_vlan *vlan, bool on)
	}
}

void br_multicast_stop(struct net_bridge *br)
void br_multicast_toggle_vlan(struct net_bridge_vlan *vlan, bool on)
{
	struct net_bridge_port *p;

	if (WARN_ON_ONCE(!br_vlan_is_master(vlan)))
		return;

	list_for_each_entry(p, &vlan->br->port_list, list) {
		struct net_bridge_vlan *vport;

		vport = br_vlan_find(nbp_vlan_group(p), vlan->vid);
		if (!vport)
			continue;
		br_multicast_toggle_one_vlan(vport, on);
	}
}

int br_multicast_toggle_vlan_snooping(struct net_bridge *br, bool on,
				      struct netlink_ext_ack *extack)
{
	struct net_bridge_vlan_group *vg;
	struct net_bridge_vlan *vlan;
	struct net_bridge_port *p;

	if (br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) == on)
		return 0;

	if (on && !br_opt_get(br, BROPT_VLAN_ENABLED)) {
		NL_SET_ERR_MSG_MOD(extack, "Cannot enable multicast vlan snooping with vlan filtering disabled");
		return -EINVAL;
	}

	vg = br_vlan_group(br);
	if (!vg)
		return 0;

	br_opt_toggle(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED, on);

	/* disable/enable non-vlan mcast contexts based on vlan snooping */
	if (on)
		__br_multicast_stop(&br->multicast_ctx);
	else
		__br_multicast_open(&br->multicast_ctx);
	list_for_each_entry(p, &br->port_list, list) {
		if (on)
			br_multicast_disable_port(p);
		else
			br_multicast_enable_port(p);
	}

	list_for_each_entry(vlan, &vg->vlan_list, vlist)
		br_multicast_toggle_vlan(vlan, on);

	return 0;
}

void br_multicast_stop(struct net_bridge *br)
{
	ASSERT_RTNL();

	if (br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED)) {
		struct net_bridge_vlan_group *vg;
		struct net_bridge_vlan *vlan;

		vg = br_vlan_group(br);
		if (vg) {
			list_for_each_entry(vlan, &vg->vlan_list, vlist) {
@@ -3822,6 +3902,7 @@ void br_multicast_stop(struct net_bridge *br)
					__br_multicast_stop(&vlan->br_mcast_ctx);
			}
		}
	}

	__br_multicast_stop(&br->multicast_ctx);
}
Loading