Commit 5955a948 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'bridge-mcast-preparations-for-evpn-extensions'

Ido Schimmel says:

====================
bridge: mcast: Preparations for EVPN extensions

This patchset was split from [1] and includes non-functional changes
aimed at making it easier to add additional netlink attributes later on.
Future extensions are available here [2].

The idea behind these patches is to create an MDB configuration
structure into which netlink messages are parsed into. The structure is
then passed in the entry creation / deletion call chain instead of
passing the netlink attributes themselves. The same pattern is used by
other rtnetlink objects such as routes and nexthops.

I initially tried to extend the current code, but it proved to be too
difficult, which is why I decided to refactor it to the extensible and
familiar pattern used by other rtnetlink objects.

Tested using existing selftests and using a new selftest that will be
submitted together with the planned extensions.

[1] https://lore.kernel.org/netdev/20221018120420.561846-1-idosch@nvidia.com/
[2] https://github.com/idosch/linux/commits/submit/mdb_v1
====================

Link: https://lore.kernel.org/r/20221206105809.363767-1-idosch@nvidia.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 16dc16d9 f86c3e2c
Loading
Loading
Loading
Loading
+149 −163
Original line number Diff line number Diff line
@@ -754,73 +754,6 @@ static const struct nla_policy br_mdbe_attrs_pol[MDBE_ATTR_MAX + 1] = {
					      sizeof(struct in6_addr)),
};

static int br_mdb_parse(struct sk_buff *skb, struct nlmsghdr *nlh,
			struct net_device **pdev, struct br_mdb_entry **pentry,
			struct nlattr **mdb_attrs, struct netlink_ext_ack *extack)
{
	struct net *net = sock_net(skb->sk);
	struct br_mdb_entry *entry;
	struct br_port_msg *bpm;
	struct nlattr *tb[MDBA_SET_ENTRY_MAX+1];
	struct net_device *dev;
	int err;

	err = nlmsg_parse_deprecated(nlh, sizeof(*bpm), tb,
				     MDBA_SET_ENTRY_MAX, NULL, NULL);
	if (err < 0)
		return err;

	bpm = nlmsg_data(nlh);
	if (bpm->ifindex == 0) {
		NL_SET_ERR_MSG_MOD(extack, "Invalid bridge ifindex");
		return -EINVAL;
	}

	dev = __dev_get_by_index(net, bpm->ifindex);
	if (dev == NULL) {
		NL_SET_ERR_MSG_MOD(extack, "Bridge device doesn't exist");
		return -ENODEV;
	}

	if (!netif_is_bridge_master(dev)) {
		NL_SET_ERR_MSG_MOD(extack, "Device is not a bridge");
		return -EOPNOTSUPP;
	}

	*pdev = dev;

	if (!tb[MDBA_SET_ENTRY]) {
		NL_SET_ERR_MSG_MOD(extack, "Missing MDBA_SET_ENTRY attribute");
		return -EINVAL;
	}
	if (nla_len(tb[MDBA_SET_ENTRY]) != sizeof(struct br_mdb_entry)) {
		NL_SET_ERR_MSG_MOD(extack, "Invalid MDBA_SET_ENTRY attribute length");
		return -EINVAL;
	}

	entry = nla_data(tb[MDBA_SET_ENTRY]);
	if (!is_valid_mdb_entry(entry, extack))
		return -EINVAL;
	*pentry = entry;

	if (tb[MDBA_SET_ENTRY_ATTRS]) {
		err = nla_parse_nested(mdb_attrs, MDBE_ATTR_MAX,
				       tb[MDBA_SET_ENTRY_ATTRS],
				       br_mdbe_attrs_pol, extack);
		if (err)
			return err;
		if (mdb_attrs[MDBE_ATTR_SOURCE] &&
		    !is_valid_mdb_source(mdb_attrs[MDBE_ATTR_SOURCE],
					 entry->addr.proto, extack))
			return -EINVAL;
	} else {
		memset(mdb_attrs, 0,
		       sizeof(struct nlattr *) * (MDBE_ATTR_MAX + 1));
	}

	return 0;
}

static struct net_bridge_mcast *
__br_mdb_choose_context(struct net_bridge *br,
			const struct br_mdb_entry *entry,
@@ -853,44 +786,26 @@ __br_mdb_choose_context(struct net_bridge *br,
	return brmctx;
}

static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port,
			    struct br_mdb_entry *entry,
			    struct nlattr **mdb_attrs,
static int br_mdb_add_group(const struct br_mdb_config *cfg,
			    struct netlink_ext_ack *extack)
{
	struct net_bridge_mdb_entry *mp, *star_mp;
	struct net_bridge_port_group __rcu **pp;
	struct br_mdb_entry *entry = cfg->entry;
	struct net_bridge_port *port = cfg->p;
	struct net_bridge *br = cfg->br;
	struct net_bridge_port_group *p;
	struct net_bridge_mcast *brmctx;
	struct br_ip group, star_group;
	struct br_ip group = cfg->group;
	unsigned long now = jiffies;
	unsigned char flags = 0;
	struct br_ip star_group;
	u8 filter_mode;

	__mdb_entry_to_br_ip(entry, &group, mdb_attrs);

	brmctx = __br_mdb_choose_context(br, entry, extack);
	if (!brmctx)
		return -EINVAL;

	/* host join errors which can happen before creating the group */
	if (!port && !br_group_is_l2(&group)) {
		/* don't allow any flags for host-joined IP groups */
		if (entry->state) {
			NL_SET_ERR_MSG_MOD(extack, "Flags are not allowed for host groups");
			return -EINVAL;
		}
		if (!br_multicast_is_star_g(&group)) {
			NL_SET_ERR_MSG_MOD(extack, "Groups with sources cannot be manually host joined");
			return -EINVAL;
		}
	}

	if (br_group_is_l2(&group) && entry->state != MDB_PERMANENT) {
		NL_SET_ERR_MSG_MOD(extack, "Only permanent L2 entries allowed");
		return -EINVAL;
	}

	mp = br_multicast_new_group(br, &group);
	if (IS_ERR(mp))
		return PTR_ERR(mp);
@@ -959,107 +874,197 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port,
	return 0;
}

static int __br_mdb_add(struct net *net, struct net_bridge *br,
			struct net_bridge_port *p,
			struct br_mdb_entry *entry,
			struct nlattr **mdb_attrs,
static int __br_mdb_add(const struct br_mdb_config *cfg,
			struct netlink_ext_ack *extack)
{
	int ret;

	spin_lock_bh(&br->multicast_lock);
	ret = br_mdb_add_group(br, p, entry, mdb_attrs, extack);
	spin_unlock_bh(&br->multicast_lock);
	spin_lock_bh(&cfg->br->multicast_lock);
	ret = br_mdb_add_group(cfg, extack);
	spin_unlock_bh(&cfg->br->multicast_lock);

	return ret;
}

static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh,
static int br_mdb_config_attrs_init(struct nlattr *set_attrs,
				    struct br_mdb_config *cfg,
				    struct netlink_ext_ack *extack)
{
	struct nlattr *mdb_attrs[MDBE_ATTR_MAX + 1];
	struct net *net = sock_net(skb->sk);
	struct net_bridge_vlan_group *vg;
	struct net_bridge_port *p = NULL;
	struct net_device *dev, *pdev;
	struct br_mdb_entry *entry;
	struct net_bridge_vlan *v;
	struct net_bridge *br;
	int err;

	err = br_mdb_parse(skb, nlh, &dev, &entry, mdb_attrs, extack);
	if (err < 0)
	err = nla_parse_nested(mdb_attrs, MDBE_ATTR_MAX, set_attrs,
			       br_mdbe_attrs_pol, extack);
	if (err)
		return err;

	if (mdb_attrs[MDBE_ATTR_SOURCE] &&
	    !is_valid_mdb_source(mdb_attrs[MDBE_ATTR_SOURCE],
				 cfg->entry->addr.proto, extack))
		return -EINVAL;

	__mdb_entry_to_br_ip(cfg->entry, &cfg->group, mdb_attrs);

	return 0;
}

static int br_mdb_config_init(struct net *net, const struct nlmsghdr *nlh,
			      struct br_mdb_config *cfg,
			      struct netlink_ext_ack *extack)
{
	struct nlattr *tb[MDBA_SET_ENTRY_MAX + 1];
	struct br_port_msg *bpm;
	struct net_device *dev;
	int err;

	err = nlmsg_parse_deprecated(nlh, sizeof(*bpm), tb,
				     MDBA_SET_ENTRY_MAX, NULL, extack);
	if (err)
		return err;

	br = netdev_priv(dev);
	memset(cfg, 0, sizeof(*cfg));

	bpm = nlmsg_data(nlh);
	if (!bpm->ifindex) {
		NL_SET_ERR_MSG_MOD(extack, "Invalid bridge ifindex");
		return -EINVAL;
	}

	dev = __dev_get_by_index(net, bpm->ifindex);
	if (!dev) {
		NL_SET_ERR_MSG_MOD(extack, "Bridge device doesn't exist");
		return -ENODEV;
	}

	if (!netif_is_bridge_master(dev)) {
		NL_SET_ERR_MSG_MOD(extack, "Device is not a bridge");
		return -EOPNOTSUPP;
	}

	cfg->br = netdev_priv(dev);

	if (!netif_running(br->dev)) {
	if (!netif_running(cfg->br->dev)) {
		NL_SET_ERR_MSG_MOD(extack, "Bridge device is not running");
		return -EINVAL;
	}

	if (!br_opt_get(br, BROPT_MULTICAST_ENABLED)) {
	if (!br_opt_get(cfg->br, BROPT_MULTICAST_ENABLED)) {
		NL_SET_ERR_MSG_MOD(extack, "Bridge's multicast processing is disabled");
		return -EINVAL;
	}

	if (entry->ifindex != br->dev->ifindex) {
		pdev = __dev_get_by_index(net, entry->ifindex);
	if (NL_REQ_ATTR_CHECK(extack, NULL, tb, MDBA_SET_ENTRY)) {
		NL_SET_ERR_MSG_MOD(extack, "Missing MDBA_SET_ENTRY attribute");
		return -EINVAL;
	}
	if (nla_len(tb[MDBA_SET_ENTRY]) != sizeof(struct br_mdb_entry)) {
		NL_SET_ERR_MSG_MOD(extack, "Invalid MDBA_SET_ENTRY attribute length");
		return -EINVAL;
	}

	cfg->entry = nla_data(tb[MDBA_SET_ENTRY]);
	if (!is_valid_mdb_entry(cfg->entry, extack))
		return -EINVAL;

	if (cfg->entry->ifindex != cfg->br->dev->ifindex) {
		struct net_device *pdev;

		pdev = __dev_get_by_index(net, cfg->entry->ifindex);
		if (!pdev) {
			NL_SET_ERR_MSG_MOD(extack, "Port net device doesn't exist");
			return -ENODEV;
		}

		p = br_port_get_rtnl(pdev);
		if (!p) {
		cfg->p = br_port_get_rtnl(pdev);
		if (!cfg->p) {
			NL_SET_ERR_MSG_MOD(extack, "Net device is not a bridge port");
			return -EINVAL;
		}

		if (p->br != br) {
		if (cfg->p->br != cfg->br) {
			NL_SET_ERR_MSG_MOD(extack, "Port belongs to a different bridge device");
			return -EINVAL;
		}
		if (p->state == BR_STATE_DISABLED && entry->state != MDB_PERMANENT) {
	}

	if (tb[MDBA_SET_ENTRY_ATTRS])
		return br_mdb_config_attrs_init(tb[MDBA_SET_ENTRY_ATTRS], cfg,
						extack);
	else
		__mdb_entry_to_br_ip(cfg->entry, &cfg->group, NULL);

	return 0;
}

static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh,
		      struct netlink_ext_ack *extack)
{
	struct net *net = sock_net(skb->sk);
	struct net_bridge_vlan_group *vg;
	struct net_bridge_vlan *v;
	struct br_mdb_config cfg;
	int err;

	err = br_mdb_config_init(net, nlh, &cfg, extack);
	if (err)
		return err;

	/* host join errors which can happen before creating the group */
	if (!cfg.p && !br_group_is_l2(&cfg.group)) {
		/* don't allow any flags for host-joined IP groups */
		if (cfg.entry->state) {
			NL_SET_ERR_MSG_MOD(extack, "Flags are not allowed for host groups");
			return -EINVAL;
		}
		if (!br_multicast_is_star_g(&cfg.group)) {
			NL_SET_ERR_MSG_MOD(extack, "Groups with sources cannot be manually host joined");
			return -EINVAL;
		}
	}

	if (br_group_is_l2(&cfg.group) && cfg.entry->state != MDB_PERMANENT) {
		NL_SET_ERR_MSG_MOD(extack, "Only permanent L2 entries allowed");
		return -EINVAL;
	}

	if (cfg.p) {
		if (cfg.p->state == BR_STATE_DISABLED && cfg.entry->state != MDB_PERMANENT) {
			NL_SET_ERR_MSG_MOD(extack, "Port is in disabled state and entry is not permanent");
			return -EINVAL;
		}
		vg = nbp_vlan_group(p);
		vg = nbp_vlan_group(cfg.p);
	} else {
		vg = br_vlan_group(br);
		vg = br_vlan_group(cfg.br);
	}

	/* If vlan filtering is enabled and VLAN is not specified
	 * install mdb entry on all vlans configured on the port.
	 */
	if (br_vlan_enabled(br->dev) && vg && entry->vid == 0) {
	if (br_vlan_enabled(cfg.br->dev) && vg && cfg.entry->vid == 0) {
		list_for_each_entry(v, &vg->vlan_list, vlist) {
			entry->vid = v->vid;
			err = __br_mdb_add(net, br, p, entry, mdb_attrs, extack);
			cfg.entry->vid = v->vid;
			cfg.group.vid = v->vid;
			err = __br_mdb_add(&cfg, extack);
			if (err)
				break;
		}
	} else {
		err = __br_mdb_add(net, br, p, entry, mdb_attrs, extack);
		err = __br_mdb_add(&cfg, extack);
	}

	return err;
}

static int __br_mdb_del(struct net_bridge *br, struct br_mdb_entry *entry,
			struct nlattr **mdb_attrs)
static int __br_mdb_del(const struct br_mdb_config *cfg)
{
	struct br_mdb_entry *entry = cfg->entry;
	struct net_bridge *br = cfg->br;
	struct net_bridge_mdb_entry *mp;
	struct net_bridge_port_group *p;
	struct net_bridge_port_group __rcu **pp;
	struct br_ip ip;
	struct br_ip ip = cfg->group;
	int err = -EINVAL;

	if (!netif_running(br->dev) || !br_opt_get(br, BROPT_MULTICAST_ENABLED))
		return -EINVAL;

	__mdb_entry_to_br_ip(entry, &ip, mdb_attrs);

	spin_lock_bh(&br->multicast_lock);
	mp = br_mdb_ip_get(br, &ip);
	if (!mp)
@@ -1094,51 +1099,32 @@ static int __br_mdb_del(struct net_bridge *br, struct br_mdb_entry *entry,
static int br_mdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
		      struct netlink_ext_ack *extack)
{
	struct nlattr *mdb_attrs[MDBE_ATTR_MAX + 1];
	struct net *net = sock_net(skb->sk);
	struct net_bridge_vlan_group *vg;
	struct net_bridge_port *p = NULL;
	struct net_device *dev, *pdev;
	struct br_mdb_entry *entry;
	struct net_bridge_vlan *v;
	struct net_bridge *br;
	struct br_mdb_config cfg;
	int err;

	err = br_mdb_parse(skb, nlh, &dev, &entry, mdb_attrs, extack);
	if (err < 0)
	err = br_mdb_config_init(net, nlh, &cfg, extack);
	if (err)
		return err;

	br = netdev_priv(dev);

	if (entry->ifindex != br->dev->ifindex) {
		pdev = __dev_get_by_index(net, entry->ifindex);
		if (!pdev)
			return -ENODEV;

		p = br_port_get_rtnl(pdev);
		if (!p) {
			NL_SET_ERR_MSG_MOD(extack, "Net device is not a bridge port");
			return -EINVAL;
		}
		if (p->br != br) {
			NL_SET_ERR_MSG_MOD(extack, "Port belongs to a different bridge device");
			return -EINVAL;
		}
		vg = nbp_vlan_group(p);
	} else {
		vg = br_vlan_group(br);
	}
	if (cfg.p)
		vg = nbp_vlan_group(cfg.p);
	else
		vg = br_vlan_group(cfg.br);

	/* If vlan filtering is enabled and VLAN is not specified
	 * delete mdb entry on all vlans configured on the port.
	 */
	if (br_vlan_enabled(br->dev) && vg && entry->vid == 0) {
	if (br_vlan_enabled(cfg.br->dev) && vg && cfg.entry->vid == 0) {
		list_for_each_entry(v, &vg->vlan_list, vlist) {
			entry->vid = v->vid;
			err = __br_mdb_del(br, entry, mdb_attrs);
			cfg.entry->vid = v->vid;
			cfg.group.vid = v->vid;
			err = __br_mdb_del(&cfg);
		}
	} else {
		err = __br_mdb_del(br, entry, mdb_attrs);
		err = __br_mdb_del(&cfg);
	}

	return err;
+1 −1
Original line number Diff line number Diff line
@@ -1273,7 +1273,7 @@ br_multicast_new_group_src(struct net_bridge_port_group *pg, struct br_ip *src_i

struct net_bridge_port_group *br_multicast_new_port_group(
			struct net_bridge_port *port,
			struct br_ip *group,
			const struct br_ip *group,
			struct net_bridge_port_group __rcu *next,
			unsigned char flags,
			const unsigned char *src,
+9 −1
Original line number Diff line number Diff line
@@ -92,6 +92,13 @@ struct bridge_mcast_stats {
	struct br_mcast_stats mstats;
	struct u64_stats_sync syncp;
};

struct br_mdb_config {
	struct net_bridge		*br;
	struct net_bridge_port		*p;
	struct br_mdb_entry		*entry;
	struct br_ip			group;
};
#endif

/* net_bridge_mcast_port must be always defined due to forwarding stubs */
@@ -934,7 +941,8 @@ br_mdb_ip_get(struct net_bridge *br, struct br_ip *dst);
struct net_bridge_mdb_entry *
br_multicast_new_group(struct net_bridge *br, struct br_ip *group);
struct net_bridge_port_group *
br_multicast_new_port_group(struct net_bridge_port *port, struct br_ip *group,
br_multicast_new_port_group(struct net_bridge_port *port,
			    const struct br_ip *group,
			    struct net_bridge_port_group __rcu *next,
			    unsigned char flags, const unsigned char *src,
			    u8 filter_mode, u8 rt_protocol);