Commit 6afaae6d authored by Ido Schimmel's avatar Ido Schimmel Committed by Jakub Kicinski
Browse files

bridge: mcast: Allow user space to add (*, G) with a source list and filter mode



Add new netlink attributes to the RTM_NEWMDB request that allow user
space to add (*, G) with a source list and filter mode.

The RTM_NEWMDB message can already dump such entries (created by the
kernel) so there is no need to add dump support. However, the message
contains a different set of attributes depending if it is a request or a
response. The naming and structure of the new attributes try to follow
the existing ones used in the response.

Request:

[ struct nlmsghdr ]
[ struct br_port_msg ]
[ MDBA_SET_ENTRY ]
	struct br_mdb_entry
[ MDBA_SET_ENTRY_ATTRS ]
	[ MDBE_ATTR_SOURCE ]
		struct in_addr / struct in6_addr
	[ MDBE_ATTR_SRC_LIST ]		// new
		[ MDBE_SRC_LIST_ENTRY ]
			[ MDBE_SRCATTR_ADDRESS ]
				struct in_addr / struct in6_addr
		[ ...]
	[ MDBE_ATTR_GROUP_MODE ]	// new
		u8

Response:

[ struct nlmsghdr ]
[ struct br_port_msg ]
[ MDBA_MDB ]
	[ MDBA_MDB_ENTRY ]
		[ MDBA_MDB_ENTRY_INFO ]
			struct br_mdb_entry
		[ MDBA_MDB_EATTR_TIMER ]
			u32
		[ MDBA_MDB_EATTR_SOURCE ]
			struct in_addr / struct in6_addr
		[ MDBA_MDB_EATTR_RTPROT ]
			u8
		[ MDBA_MDB_EATTR_SRC_LIST ]
			[ MDBA_MDB_SRCLIST_ENTRY ]
				[ MDBA_MDB_SRCATTR_ADDRESS ]
					struct in_addr / struct in6_addr
				[ MDBA_MDB_SRCATTR_TIMER ]
					u8
			[...]
		[ MDBA_MDB_EATTR_GROUP_MODE ]
			u8

Signed-off-by: default avatarIdo Schimmel <idosch@nvidia.com>
Acked-by: default avatarNikolay Aleksandrov <razor@blackwall.org>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent b1c8fec8
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -723,10 +723,30 @@ enum {
enum {
	MDBE_ATTR_UNSPEC,
	MDBE_ATTR_SOURCE,
	MDBE_ATTR_SRC_LIST,
	MDBE_ATTR_GROUP_MODE,
	__MDBE_ATTR_MAX,
};
#define MDBE_ATTR_MAX (__MDBE_ATTR_MAX - 1)

/* per mdb entry source */
enum {
	MDBE_SRC_LIST_UNSPEC,
	MDBE_SRC_LIST_ENTRY,
	__MDBE_SRC_LIST_MAX,
};
#define MDBE_SRC_LIST_MAX (__MDBE_SRC_LIST_MAX - 1)

/* per mdb entry per source attributes
 * these are embedded in MDBE_SRC_LIST_ENTRY
 */
enum {
	MDBE_SRCATTR_UNSPEC,
	MDBE_SRCATTR_ADDRESS,
	__MDBE_SRCATTR_MAX,
};
#define MDBE_SRCATTR_MAX (__MDBE_SRCATTR_MAX - 1)

/* Embedded inside LINK_XSTATS_TYPE_BRIDGE */
enum {
	BRIDGE_XSTATS_UNSPEC,
+130 −0
Original line number Diff line number Diff line
@@ -663,10 +663,25 @@ void br_rtr_notify(struct net_device *dev, struct net_bridge_mcast_port *pmctx,
	rtnl_set_sk_err(net, RTNLGRP_MDB, err);
}

static const struct nla_policy
br_mdbe_src_list_entry_pol[MDBE_SRCATTR_MAX + 1] = {
	[MDBE_SRCATTR_ADDRESS] = NLA_POLICY_RANGE(NLA_BINARY,
						  sizeof(struct in_addr),
						  sizeof(struct in6_addr)),
};

static const struct nla_policy
br_mdbe_src_list_pol[MDBE_SRC_LIST_MAX + 1] = {
	[MDBE_SRC_LIST_ENTRY] = NLA_POLICY_NESTED(br_mdbe_src_list_entry_pol),
};

static const struct nla_policy br_mdbe_attrs_pol[MDBE_ATTR_MAX + 1] = {
	[MDBE_ATTR_SOURCE] = NLA_POLICY_RANGE(NLA_BINARY,
					      sizeof(struct in_addr),
					      sizeof(struct in6_addr)),
	[MDBE_ATTR_GROUP_MODE] = NLA_POLICY_RANGE(NLA_U8, MCAST_EXCLUDE,
						  MCAST_INCLUDE),
	[MDBE_ATTR_SRC_LIST] = NLA_POLICY_NESTED(br_mdbe_src_list_pol),
};

static bool is_valid_mdb_entry(struct br_mdb_entry *entry,
@@ -1051,6 +1066,76 @@ static int __br_mdb_add(const struct br_mdb_config *cfg,
	return ret;
}

static int br_mdb_config_src_entry_init(struct nlattr *src_entry,
					struct br_mdb_src_entry *src,
					__be16 proto,
					struct netlink_ext_ack *extack)
{
	struct nlattr *tb[MDBE_SRCATTR_MAX + 1];
	int err;

	err = nla_parse_nested(tb, MDBE_SRCATTR_MAX, src_entry,
			       br_mdbe_src_list_entry_pol, extack);
	if (err)
		return err;

	if (NL_REQ_ATTR_CHECK(extack, src_entry, tb, MDBE_SRCATTR_ADDRESS))
		return -EINVAL;

	if (!is_valid_mdb_source(tb[MDBE_SRCATTR_ADDRESS], proto, extack))
		return -EINVAL;

	src->addr.proto = proto;
	nla_memcpy(&src->addr.src, tb[MDBE_SRCATTR_ADDRESS],
		   nla_len(tb[MDBE_SRCATTR_ADDRESS]));

	return 0;
}

static int br_mdb_config_src_list_init(struct nlattr *src_list,
				       struct br_mdb_config *cfg,
				       struct netlink_ext_ack *extack)
{
	struct nlattr *src_entry;
	int rem, err;
	int i = 0;

	nla_for_each_nested(src_entry, src_list, rem)
		cfg->num_src_entries++;

	if (cfg->num_src_entries >= PG_SRC_ENT_LIMIT) {
		NL_SET_ERR_MSG_FMT_MOD(extack, "Exceeded maximum number of source entries (%u)",
				       PG_SRC_ENT_LIMIT - 1);
		return -EINVAL;
	}

	cfg->src_entries = kcalloc(cfg->num_src_entries,
				   sizeof(struct br_mdb_src_entry), GFP_KERNEL);
	if (!cfg->src_entries)
		return -ENOMEM;

	nla_for_each_nested(src_entry, src_list, rem) {
		err = br_mdb_config_src_entry_init(src_entry,
						   &cfg->src_entries[i],
						   cfg->entry->addr.proto,
						   extack);
		if (err)
			goto err_src_entry_init;
		i++;
	}

	return 0;

err_src_entry_init:
	kfree(cfg->src_entries);
	return err;
}

static void br_mdb_config_src_list_fini(struct br_mdb_config *cfg)
{
	kfree(cfg->src_entries);
}

static int br_mdb_config_attrs_init(struct nlattr *set_attrs,
				    struct br_mdb_config *cfg,
				    struct netlink_ext_ack *extack)
@@ -1070,6 +1155,44 @@ static int br_mdb_config_attrs_init(struct nlattr *set_attrs,

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

	if (mdb_attrs[MDBE_ATTR_GROUP_MODE]) {
		if (!cfg->p) {
			NL_SET_ERR_MSG_MOD(extack, "Filter mode cannot be set for host groups");
			return -EINVAL;
		}
		if (!br_multicast_is_star_g(&cfg->group)) {
			NL_SET_ERR_MSG_MOD(extack, "Filter mode can only be set for (*, G) entries");
			return -EINVAL;
		}
		cfg->filter_mode = nla_get_u8(mdb_attrs[MDBE_ATTR_GROUP_MODE]);
	} else {
		cfg->filter_mode = MCAST_EXCLUDE;
	}

	if (mdb_attrs[MDBE_ATTR_SRC_LIST]) {
		if (!cfg->p) {
			NL_SET_ERR_MSG_MOD(extack, "Source list cannot be set for host groups");
			return -EINVAL;
		}
		if (!br_multicast_is_star_g(&cfg->group)) {
			NL_SET_ERR_MSG_MOD(extack, "Source list can only be set for (*, G) entries");
			return -EINVAL;
		}
		if (!mdb_attrs[MDBE_ATTR_GROUP_MODE]) {
			NL_SET_ERR_MSG_MOD(extack, "Source list cannot be set without filter mode");
			return -EINVAL;
		}
		err = br_mdb_config_src_list_init(mdb_attrs[MDBE_ATTR_SRC_LIST],
						  cfg, extack);
		if (err)
			return err;
	}

	if (!cfg->num_src_entries && cfg->filter_mode == MCAST_INCLUDE) {
		NL_SET_ERR_MSG_MOD(extack, "Cannot add (*, G) INCLUDE with an empty source list");
		return -EINVAL;
	}

	return 0;
}

@@ -1162,6 +1285,11 @@ static int br_mdb_config_init(struct net *net, const struct nlmsghdr *nlh,
	return 0;
}

static void br_mdb_config_fini(struct br_mdb_config *cfg)
{
	br_mdb_config_src_list_fini(cfg);
}

static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh,
		      struct netlink_ext_ack *extack)
{
@@ -1220,6 +1348,7 @@ static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh,
	}

out:
	br_mdb_config_fini(&cfg);
	return err;
}

@@ -1295,6 +1424,7 @@ static int br_mdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
		err = __br_mdb_del(&cfg);
	}

	br_mdb_config_fini(&cfg);
	return err;
}