Commit 13b127d2 authored by Jiri Pirko's avatar Jiri Pirko Committed by Paolo Abeni
Browse files

devlink: add a command to set notification filter and use it for multicasts



Currently the user listening on a socket for devlink notifications
gets always all messages for all existing instances, even if he is
interested only in one of those. That may cause unnecessary overhead
on setups with thousands of instances present.

User is currently able to narrow down the devlink objects replies
to dump commands by specifying select attributes.

Allow similar approach for notifications. Introduce a new devlink
NOTIFY_FILTER_SET which the user passes the select attributes. Store
these per-socket and use them for filtering messages
during multicast send.

Signed-off-by: default avatarJiri Pirko <jiri@nvidia.com>
Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent 971b4ad8
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -2254,3 +2254,13 @@ operations:
            - bus-name
            - dev-name
            - selftests

    -
      name: notify-filter-set
      doc: Set notification messages socket filter.
      attribute-set: devlink
      do:
        request:
          attributes:
            - bus-name
            - dev-name
+2 −0
Original line number Diff line number Diff line
@@ -139,6 +139,8 @@ enum devlink_command {
	DEVLINK_CMD_SELFTESTS_GET,	/* can dump */
	DEVLINK_CMD_SELFTESTS_RUN,

	DEVLINK_CMD_NOTIFY_FILTER_SET,

	/* add new commands above here */
	__DEVLINK_CMD_MAX,
	DEVLINK_CMD_MAX = __DEVLINK_CMD_MAX - 1
+32 −2
Original line number Diff line number Diff line
@@ -191,11 +191,41 @@ static inline bool devlink_nl_notify_need(struct devlink *devlink)
				  DEVLINK_MCGRP_CONFIG);
}

struct devlink_obj_desc {
	struct rcu_head rcu;
	const char *bus_name;
	const char *dev_name;
	long data[];
};

static inline void devlink_nl_obj_desc_init(struct devlink_obj_desc *desc,
					    struct devlink *devlink)
{
	memset(desc, 0, sizeof(*desc));
	desc->bus_name = devlink->dev->bus->name;
	desc->dev_name = dev_name(devlink->dev);
}

int devlink_nl_notify_filter(struct sock *dsk, struct sk_buff *skb, void *data);

static inline void devlink_nl_notify_send_desc(struct devlink *devlink,
					       struct sk_buff *msg,
					       struct devlink_obj_desc *desc)
{
	genlmsg_multicast_netns_filtered(&devlink_nl_family,
					 devlink_net(devlink),
					 msg, 0, DEVLINK_MCGRP_CONFIG,
					 GFP_KERNEL,
					 devlink_nl_notify_filter, desc);
}

static inline void devlink_nl_notify_send(struct devlink *devlink,
					  struct sk_buff *msg)
{
	genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink),
				msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL);
	struct devlink_obj_desc desc;

	devlink_nl_obj_desc_init(&desc, devlink);
	devlink_nl_notify_send_desc(devlink, msg, &desc);
}

/* Notify */
+108 −0
Original line number Diff line number Diff line
@@ -17,6 +17,111 @@ static const struct genl_multicast_group devlink_nl_mcgrps[] = {
	[DEVLINK_MCGRP_CONFIG] = { .name = DEVLINK_GENL_MCGRP_CONFIG_NAME },
};

struct devlink_nl_sock_priv {
	struct devlink_obj_desc __rcu *flt;
	spinlock_t flt_lock; /* Protects flt. */
};

static void devlink_nl_sock_priv_init(void *priv)
{
	struct devlink_nl_sock_priv *sk_priv = priv;

	spin_lock_init(&sk_priv->flt_lock);
}

static void devlink_nl_sock_priv_destroy(void *priv)
{
	struct devlink_nl_sock_priv *sk_priv = priv;
	struct devlink_obj_desc *flt;

	flt = rcu_dereference_protected(sk_priv->flt, true);
	kfree_rcu(flt, rcu);
}

int devlink_nl_notify_filter_set_doit(struct sk_buff *skb,
				      struct genl_info *info)
{
	struct devlink_nl_sock_priv *sk_priv;
	struct nlattr **attrs = info->attrs;
	struct devlink_obj_desc *flt;
	size_t data_offset = 0;
	size_t data_size = 0;
	char *pos;

	if (attrs[DEVLINK_ATTR_BUS_NAME])
		data_size = size_add(data_size,
				     nla_len(attrs[DEVLINK_ATTR_BUS_NAME]) + 1);
	if (attrs[DEVLINK_ATTR_DEV_NAME])
		data_size = size_add(data_size,
				     nla_len(attrs[DEVLINK_ATTR_DEV_NAME]) + 1);

	flt = kzalloc(size_add(sizeof(*flt), data_size), GFP_KERNEL);
	if (!flt)
		return -ENOMEM;

	pos = (char *) flt->data;
	if (attrs[DEVLINK_ATTR_BUS_NAME]) {
		data_offset += nla_strscpy(pos,
					   attrs[DEVLINK_ATTR_BUS_NAME],
					   data_size) + 1;
		flt->bus_name = pos;
		pos += data_offset;
	}
	if (attrs[DEVLINK_ATTR_DEV_NAME]) {
		nla_strscpy(pos, attrs[DEVLINK_ATTR_DEV_NAME],
			    data_size - data_offset);
		flt->dev_name = pos;
	}

	/* Don't attach empty filter. */
	if (!flt->bus_name && !flt->dev_name) {
		kfree(flt);
		flt = NULL;
	}

	sk_priv = genl_sk_priv_get(&devlink_nl_family, NETLINK_CB(skb).sk);
	if (IS_ERR(sk_priv)) {
		kfree(flt);
		return PTR_ERR(sk_priv);
	}
	spin_lock(&sk_priv->flt_lock);
	flt = rcu_replace_pointer(sk_priv->flt, flt,
				  lockdep_is_held(&sk_priv->flt_lock));
	spin_unlock(&sk_priv->flt_lock);
	kfree_rcu(flt, rcu);
	return 0;
}

static bool devlink_obj_desc_match(const struct devlink_obj_desc *desc,
				   const struct devlink_obj_desc *flt)
{
	if (desc->bus_name && flt->bus_name &&
	    strcmp(desc->bus_name, flt->bus_name))
		return false;
	if (desc->dev_name && flt->dev_name &&
	    strcmp(desc->dev_name, flt->dev_name))
		return false;
	return true;
}

int devlink_nl_notify_filter(struct sock *dsk, struct sk_buff *skb, void *data)
{
	struct devlink_obj_desc *desc = data;
	struct devlink_nl_sock_priv *sk_priv;
	struct devlink_obj_desc *flt;
	int ret = 0;

	rcu_read_lock();
	sk_priv = __genl_sk_priv_get(&devlink_nl_family, dsk);
	if (!IS_ERR_OR_NULL(sk_priv)) {
		flt = rcu_dereference(sk_priv->flt);
		if (flt)
			ret = !devlink_obj_desc_match(desc, flt);
	}
	rcu_read_unlock();
	return ret;
}

int devlink_nl_put_nested_handle(struct sk_buff *msg, struct net *net,
				 struct devlink *devlink, int attrtype)
{
@@ -256,4 +361,7 @@ struct genl_family devlink_nl_family __ro_after_init = {
	.resv_start_op	= DEVLINK_CMD_SELFTESTS_RUN + 1,
	.mcgrps		= devlink_nl_mcgrps,
	.n_mcgrps	= ARRAY_SIZE(devlink_nl_mcgrps),
	.sock_priv_size		= sizeof(struct devlink_nl_sock_priv),
	.sock_priv_init		= devlink_nl_sock_priv_init,
	.sock_priv_destroy	= devlink_nl_sock_priv_destroy,
};
+14 −1
Original line number Diff line number Diff line
@@ -560,8 +560,14 @@ static const struct nla_policy devlink_selftests_run_nl_policy[DEVLINK_ATTR_SELF
	[DEVLINK_ATTR_SELFTESTS] = NLA_POLICY_NESTED(devlink_dl_selftest_id_nl_policy),
};

/* DEVLINK_CMD_NOTIFY_FILTER_SET - do */
static const struct nla_policy devlink_notify_filter_set_nl_policy[DEVLINK_ATTR_DEV_NAME + 1] = {
	[DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, },
	[DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, },
};

/* Ops table for devlink */
const struct genl_split_ops devlink_nl_ops[73] = {
const struct genl_split_ops devlink_nl_ops[74] = {
	{
		.cmd		= DEVLINK_CMD_GET,
		.validate	= GENL_DONT_VALIDATE_STRICT,
@@ -1233,4 +1239,11 @@ const struct genl_split_ops devlink_nl_ops[73] = {
		.maxattr	= DEVLINK_ATTR_SELFTESTS,
		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
	},
	{
		.cmd		= DEVLINK_CMD_NOTIFY_FILTER_SET,
		.doit		= devlink_nl_notify_filter_set_doit,
		.policy		= devlink_notify_filter_set_nl_policy,
		.maxattr	= DEVLINK_ATTR_DEV_NAME,
		.flags		= GENL_CMD_CAP_DO,
	},
};
Loading