[PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

Subsystems: networking drivers, networking [general], the rest

STALE1748d

18 messages, 5 authors, 2022-01-04 · open the first message on its own page

[PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: Lahav Schlesinger <hidden>
Date: 2021-12-05 09:37:24

Under large scale, some routers are required to support tens of thousands
of devices at once, both physical and virtual (e.g. loopbacks, tunnels,
vrfs, etc).
At times such routers are required to delete massive amounts of devices
at once, such as when a factory reset is performed on the router (causing
a deletion of all devices), or when a configuration is restored after an
upgrade, or as a request from an operator.

Currently there are 2 means of deleting devices using Netlink:
1. Deleting a single device (either by ifindex using ifinfomsg::ifi_index,
or by name using IFLA_IFNAME)
2. Delete all device that belong to a group (using IFLA_GROUP)

Deletion of devices one-by-one has poor performance on large scale of
devices compared to "group deletion":
After all device are handled, netdev_run_todo() is called which
calls rcu_barrier() to finish any outstanding RCU callbacks that were
registered during the deletion of the device, then wait until the
refcount of all the devices is 0, then perform final cleanups.

However, calling rcu_barrier() is a very costly operation, each call
taking in the order of 10s of milliseconds.

When deleting a large number of device one-by-one, rcu_barrier()
will be called for each device being deleted.
As an example, following benchmark deletes 10K loopback devices,
all of which are UP and with only IPv6 LLA being configured:

1. Deleting one-by-one using 1 thread : 243 seconds
2. Deleting one-by-one using 10 thread: 70 seconds
3. Deleting one-by-one using 50 thread: 54 seconds
4. Deleting all using "group deletion": 30 seconds

Note that even though the deletion logic takes place under the rtnl
lock, since the call to rcu_barrier() is outside the lock we gain
some improvements.

But, while "group deletion" is the fastest, it is not suited for
deleting large number of arbitrary devices which are unknown a head of
time. Furthermore, moving large number of devices to a group is also a
costly operation.

This patch adds support for passing an arbitrary list of ifindex of
devices to delete with a new IFLA_IFINDEX attribute. A single message
may contain multiple instances of this attribute).
This gives a more fine-grained control over which devices to delete,
while still resulting in rcu_barrier() being called only once.
Indeed, the timings of using this new API to delete 10K devices is
the same as using the existing "group" deletion.

Signed-off-by: Lahav Schlesinger <redacted>
---
v4 -> v5
 - Call ->dellink() only once for duplicated devices.

v3 -> v4
 - Change single IFLA_INDEX_LIST into multiple IFLA_IFINDEX
 - Fail if passing both IFLA_GROUP and at least one IFLA_IFNEX

v2 -> v3
 - Rename 'ifindex_list' to 'ifindices', and pass it as int*
 - Clamp 'ops' variable in second loop.

v1 -> v2
 - Unset 'len' of IFLA_IFINDEX_LIST in policy.
 - Use __dev_get_by_index() instead of n^2 loop.
 - Return -ENODEV if any ifindex is not present.
 - Saved devices in an array.
 - Fix formatting.

 include/uapi/linux/if_link.h |  1 +
 net/core/rtnetlink.c         | 82 ++++++++++++++++++++++++++++++++++++
 2 files changed, 83 insertions(+)
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index eebd3894fe89..68fcde9c0c5e 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -348,6 +348,7 @@ enum {
 	IFLA_PARENT_DEV_NAME,
 	IFLA_PARENT_DEV_BUS_NAME,
 
+	IFLA_IFINDEX,
 	__IFLA_MAX
 };
 
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index fd030e02f16d..5165cc699d97 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -37,6 +37,7 @@
 #include <linux/pci.h>
 #include <linux/etherdevice.h>
 #include <linux/bpf.h>
+#include <linux/sort.h>
 
 #include <linux/uaccess.h>
 
@@ -1880,6 +1881,7 @@ static const struct nla_policy ifla_policy[IFLA_MAX+1] = {
 	[IFLA_PROTO_DOWN_REASON] = { .type = NLA_NESTED },
 	[IFLA_NEW_IFINDEX]	= NLA_POLICY_MIN(NLA_S32, 1),
 	[IFLA_PARENT_DEV_NAME]	= { .type = NLA_NUL_STRING },
+	[IFLA_IFINDEX]		= { .type = NLA_S32 },
 };
 
 static const struct nla_policy ifla_info_policy[IFLA_INFO_MAX+1] = {
@@ -3050,6 +3052,78 @@ static int rtnl_group_dellink(const struct net *net, int group)
 	return 0;
 }
 
+static int dev_ifindex_cmp(const void *a, const void *b)
+{
+	struct net_device * const *dev1 = a, * const *dev2 = b;
+
+	return (*dev1)->ifindex - (*dev2)->ifindex;
+}
+
+static int rtnl_ifindex_dellink(struct net *net, struct nlattr *head, int len,
+				struct netlink_ext_ack *extack)
+{
+	int i = 0, num_devices = 0, rem;
+	struct net_device **dev_list;
+	const struct nlattr *nla;
+	LIST_HEAD(list_kill);
+	int ret;
+
+	nla_for_each_attr(nla, head, len, rem) {
+		if (nla_type(nla) == IFLA_IFINDEX)
+			num_devices++;
+	}
+
+	dev_list = kmalloc_array(num_devices, sizeof(*dev_list), GFP_KERNEL);
+	if (!dev_list)
+		return -ENOMEM;
+
+	nla_for_each_attr(nla, head, len, rem) {
+		const struct rtnl_link_ops *ops;
+		struct net_device *dev;
+		int ifindex;
+
+		if (nla_type(nla) != IFLA_IFINDEX)
+			continue;
+
+		ifindex = nla_get_s32(nla);
+		ret = -ENODEV;
+		dev = __dev_get_by_index(net, ifindex);
+		if (!dev) {
+			NL_SET_ERR_MSG_ATTR(extack, nla, "Unknown ifindex");
+			goto out_free;
+		}
+
+		ret = -EOPNOTSUPP;
+		ops = dev->rtnl_link_ops;
+		if (!ops || !ops->dellink) {
+			NL_SET_ERR_MSG_ATTR(extack, nla, "Device cannot be deleted");
+			goto out_free;
+		}
+
+		dev_list[i++] = dev;
+	}
+
+	/* Sort devices, so we could skip duplicates */
+	sort(dev_list, num_devices, sizeof(*dev_list), dev_ifindex_cmp, NULL);
+
+	for (i = 0; i < num_devices; i++) {
+		struct net_device *dev = dev_list[i];
+
+		if (i != 0 && dev_list[i - 1]->ifindex == dev->ifindex)
+			continue;
+
+		dev->rtnl_link_ops->dellink(dev, &list_kill);
+	}
+
+	unregister_netdevice_many(&list_kill);
+
+	ret = 0;
+
+out_free:
+	kfree(dev_list);
+	return ret;
+}
+
 int rtnl_delete_link(struct net_device *dev)
 {
 	const struct rtnl_link_ops *ops;
@@ -3093,6 +3167,11 @@ static int rtnl_dellink(struct sk_buff *skb, struct nlmsghdr *nlh,
 			return PTR_ERR(tgt_net);
 	}
 
+	if (tb[IFLA_GROUP] && tb[IFLA_IFINDEX]) {
+		NL_SET_ERR_MSG(extack, "Can't pass both IFLA_GROUP and IFLA_IFINDEX");
+		return -EOPNOTSUPP;
+	}
+
 	err = -EINVAL;
 	ifm = nlmsg_data(nlh);
 	if (ifm->ifi_index > 0)
@@ -3102,6 +3181,9 @@ static int rtnl_dellink(struct sk_buff *skb, struct nlmsghdr *nlh,
 				   tb[IFLA_ALT_IFNAME], NULL);
 	else if (tb[IFLA_GROUP])
 		err = rtnl_group_dellink(tgt_net, nla_get_u32(tb[IFLA_GROUP]));
+	else if (tb[IFLA_IFINDEX])
+		err = rtnl_ifindex_dellink(tgt_net, nlmsg_attrdata(nlh, sizeof(*ifm)),
+					   nlmsg_attrlen(nlh, sizeof(*ifm)), extack);
 	else
 		goto out;
 
-- 
2.25.1

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: Nikolay Aleksandrov <hidden>
Date: 2021-12-05 10:31:54

On 05/12/2021 11:36, Lahav Schlesinger wrote:
[snip]
But, while "group deletion" is the fastest, it is not suited for
deleting large number of arbitrary devices which are unknown a head of
time. Furthermore, moving large number of devices to a group is also a
costly operation.

This patch adds support for passing an arbitrary list of ifindex of
devices to delete with a new IFLA_IFINDEX attribute. A single message
may contain multiple instances of this attribute).
This gives a more fine-grained control over which devices to delete,
while still resulting in rcu_barrier() being called only once.
Indeed, the timings of using this new API to delete 10K devices is
the same as using the existing "group" deletion.

Signed-off-by: Lahav Schlesinger <redacted>
---
[snip]

Note that Ido raised a good point[1] about group operations and their efficiency
as a reply to v4 of the patch.

+CC Ido

[1] https://marc.info/?l=linux-netdev&m=163869796129581&w=2

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: Nicolas Dichtel <hidden>
Date: 2021-12-06 08:25:20

Le 05/12/2021 à 10:36, Lahav Schlesinger a écrit :
Some comments below, but please, keep the people who replied to previous
versions of this patch in cc.

[snip]
quoted hunk
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index eebd3894fe89..68fcde9c0c5e 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -348,6 +348,7 @@ enum {
 	IFLA_PARENT_DEV_NAME,
 	IFLA_PARENT_DEV_BUS_NAME,
 
+	IFLA_IFINDEX,
nit: maybe the previous blank line sit better after this new attribute (and
before __IFLA_MAX)?
quoted hunk
 	__IFLA_MAX
 };
 
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index fd030e02f16d..5165cc699d97 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -37,6 +37,7 @@
 #include <linux/pci.h>
 #include <linux/etherdevice.h>
 #include <linux/bpf.h>
+#include <linux/sort.h>
 
 #include <linux/uaccess.h>
 
@@ -1880,6 +1881,7 @@ static const struct nla_policy ifla_policy[IFLA_MAX+1] = {
 	[IFLA_PROTO_DOWN_REASON] = { .type = NLA_NESTED },
 	[IFLA_NEW_IFINDEX]	= NLA_POLICY_MIN(NLA_S32, 1),
 	[IFLA_PARENT_DEV_NAME]	= { .type = NLA_NUL_STRING },
+	[IFLA_IFINDEX]		= { .type = NLA_S32 },
Same policy than IFLA_NEW_IFINDEX to refuse negative ifindex.
quoted hunk
 };
 
 static const struct nla_policy ifla_info_policy[IFLA_INFO_MAX+1] = {
@@ -3050,6 +3052,78 @@ static int rtnl_group_dellink(const struct net *net, int group)
 	return 0;
 }
 
+static int dev_ifindex_cmp(const void *a, const void *b)
+{
+	struct net_device * const *dev1 = a, * const *dev2 = b;
+
+	return (*dev1)->ifindex - (*dev2)->ifindex;
+}
+
+static int rtnl_ifindex_dellink(struct net *net, struct nlattr *head, int len,
+				struct netlink_ext_ack *extack)
+{
+	int i = 0, num_devices = 0, rem;
+	struct net_device **dev_list;
+	const struct nlattr *nla;
+	LIST_HEAD(list_kill);
+	int ret;
+
+	nla_for_each_attr(nla, head, len, rem) {
+		if (nla_type(nla) == IFLA_IFINDEX)
+			num_devices++;
+	}
+
+	dev_list = kmalloc_array(num_devices, sizeof(*dev_list), GFP_KERNEL);
+	if (!dev_list)
+		return -ENOMEM;
+
+	nla_for_each_attr(nla, head, len, rem) {
+		const struct rtnl_link_ops *ops;
+		struct net_device *dev;
+		int ifindex;
+
+		if (nla_type(nla) != IFLA_IFINDEX)
+			continue;
+
+		ifindex = nla_get_s32(nla);
+		ret = -ENODEV;
+		dev = __dev_get_by_index(net, ifindex);
+		if (!dev) {
+			NL_SET_ERR_MSG_ATTR(extack, nla, "Unknown ifindex");
It would be nice to have the ifindex in the error message. This message does not
give more information than "ENODEV".
quoted hunk
+			goto out_free;
+		}
+
+		ret = -EOPNOTSUPP;
+		ops = dev->rtnl_link_ops;
+		if (!ops || !ops->dellink) {
+			NL_SET_ERR_MSG_ATTR(extack, nla, "Device cannot be deleted");
Same here.


Thank you,
Nicolas

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: David Ahern <hidden>
Date: 2021-12-06 15:34:49

On 12/6/21 1:25 AM, Nicolas Dichtel wrote:
quoted
@@ -3050,6 +3052,78 @@ static int rtnl_group_dellink(const struct net *net, int group)
 	return 0;
 }
 
+static int dev_ifindex_cmp(const void *a, const void *b)
+{
+	struct net_device * const *dev1 = a, * const *dev2 = b;
+
+	return (*dev1)->ifindex - (*dev2)->ifindex;
+}
+
+static int rtnl_ifindex_dellink(struct net *net, struct nlattr *head, int len,
+				struct netlink_ext_ack *extack)
+{
+	int i = 0, num_devices = 0, rem;
+	struct net_device **dev_list;
+	const struct nlattr *nla;
+	LIST_HEAD(list_kill);
+	int ret;
+
+	nla_for_each_attr(nla, head, len, rem) {
+		if (nla_type(nla) == IFLA_IFINDEX)
+			num_devices++;
+	}
+
+	dev_list = kmalloc_array(num_devices, sizeof(*dev_list), GFP_KERNEL);
+	if (!dev_list)
+		return -ENOMEM;
+
+	nla_for_each_attr(nla, head, len, rem) {
+		const struct rtnl_link_ops *ops;
+		struct net_device *dev;
+		int ifindex;
+
+		if (nla_type(nla) != IFLA_IFINDEX)
+			continue;
+
+		ifindex = nla_get_s32(nla);
+		ret = -ENODEV;
+		dev = __dev_get_by_index(net, ifindex);
+		if (!dev) {
+			NL_SET_ERR_MSG_ATTR(extack, nla, "Unknown ifindex");
It would be nice to have the ifindex in the error message. This message does not
give more information than "ENODEV".
extack infra does not allow dynamic messages. It does point to the
location of the bad index, so userspace could figure it out.
quoted
+			goto out_free;
+		}
+
+		ret = -EOPNOTSUPP;
+		ops = dev->rtnl_link_ops;
+		if (!ops || !ops->dellink) {
+			NL_SET_ERR_MSG_ATTR(extack, nla, "Device cannot be deleted");
Same here.


Thank you,
Nicolas

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: David Ahern <hidden>
Date: 2021-12-07 04:12:36

On 12/5/21 2:36 AM, Lahav Schlesinger wrote:
quoted hunk
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index fd030e02f16d..5165cc699d97 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -37,6 +37,7 @@
 #include <linux/pci.h>
 #include <linux/etherdevice.h>
 #include <linux/bpf.h>
+#include <linux/sort.h>
 
 #include <linux/uaccess.h>
 
@@ -1880,6 +1881,7 @@ static const struct nla_policy ifla_policy[IFLA_MAX+1] = {
 	[IFLA_PROTO_DOWN_REASON] = { .type = NLA_NESTED },
 	[IFLA_NEW_IFINDEX]	= NLA_POLICY_MIN(NLA_S32, 1),
 	[IFLA_PARENT_DEV_NAME]	= { .type = NLA_NUL_STRING },
+	[IFLA_IFINDEX]		= { .type = NLA_S32 },
you need to make sure this new attribute can not be used in setlink
requests or getlink.
quoted hunk
 };
 
 static const struct nla_policy ifla_info_policy[IFLA_INFO_MAX+1] = {
@@ -3050,6 +3052,78 @@ static int rtnl_group_dellink(const struct net *net, int group)
 	return 0;
 }
 
+static int dev_ifindex_cmp(const void *a, const void *b)
+{
+	struct net_device * const *dev1 = a, * const *dev2 = b;
const struct net_device *dev1 = 1, *dev2 = b;
quoted hunk
+
+	return (*dev1)->ifindex - (*dev2)->ifindex;
+}
+
+static int rtnl_ifindex_dellink(struct net *net, struct nlattr *head, int len,
+				struct netlink_ext_ack *extack)
+{
+	int i = 0, num_devices = 0, rem;
+	struct net_device **dev_list;
+	const struct nlattr *nla;
+	LIST_HEAD(list_kill);
+	int ret;
+
+	nla_for_each_attr(nla, head, len, rem) {
+		if (nla_type(nla) == IFLA_IFINDEX)
+			num_devices++;
+	}

The need to walk the list twice (3 really with the sort) to means the
array solution is better.
quoted hunk
+
+	dev_list = kmalloc_array(num_devices, sizeof(*dev_list), GFP_KERNEL);
+	if (!dev_list)
+		return -ENOMEM;
+
+	nla_for_each_attr(nla, head, len, rem) {
+		const struct rtnl_link_ops *ops;
+		struct net_device *dev;
+		int ifindex;
+
+		if (nla_type(nla) != IFLA_IFINDEX)
+			continue;
and this business too. We have arrays in other places
(net/ipv4/nexthop.c), so this is not the first.

quoted hunk
+
+		ifindex = nla_get_s32(nla);
+		ret = -ENODEV;
+		dev = __dev_get_by_index(net, ifindex);
+		if (!dev) {
+			NL_SET_ERR_MSG_ATTR(extack, nla, "Unknown ifindex");
+			goto out_free;
+		}
+
+		ret = -EOPNOTSUPP;
+		ops = dev->rtnl_link_ops;
+		if (!ops || !ops->dellink) {
+			NL_SET_ERR_MSG_ATTR(extack, nla, "Device cannot be deleted");
+			goto out_free;
+		}
+
+		dev_list[i++] = dev;
+	}
+
+	/* Sort devices, so we could skip duplicates */
+	sort(dev_list, num_devices, sizeof(*dev_list), dev_ifindex_cmp, NULL);
how did this sort change the results? 10k compares and re-order has to
add some overhead.
quoted hunk
+
+	for (i = 0; i < num_devices; i++) {
+		struct net_device *dev = dev_list[i];
+
+		if (i != 0 && dev_list[i - 1]->ifindex == dev->ifindex)
		if (i && ...)


I liked the array variant better. Jakub?

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: Nicolas Dichtel <hidden>
Date: 2021-12-07 08:27:07

Le 06/12/2021 à 16:20, David Ahern a écrit :
On 12/6/21 1:25 AM, Nicolas Dichtel wrote:
quoted
quoted
@@ -3050,6 +3052,78 @@ static int rtnl_group_dellink(const struct net *net, int group)
 	return 0;
 }
 
+static int dev_ifindex_cmp(const void *a, const void *b)
+{
+	struct net_device * const *dev1 = a, * const *dev2 = b;
+
+	return (*dev1)->ifindex - (*dev2)->ifindex;
+}
+
+static int rtnl_ifindex_dellink(struct net *net, struct nlattr *head, int len,
+				struct netlink_ext_ack *extack)
+{
+	int i = 0, num_devices = 0, rem;
+	struct net_device **dev_list;
+	const struct nlattr *nla;
+	LIST_HEAD(list_kill);
+	int ret;
+
+	nla_for_each_attr(nla, head, len, rem) {
+		if (nla_type(nla) == IFLA_IFINDEX)
+			num_devices++;
+	}
+
+	dev_list = kmalloc_array(num_devices, sizeof(*dev_list), GFP_KERNEL);
+	if (!dev_list)
+		return -ENOMEM;
+
+	nla_for_each_attr(nla, head, len, rem) {
+		const struct rtnl_link_ops *ops;
+		struct net_device *dev;
+		int ifindex;
+
+		if (nla_type(nla) != IFLA_IFINDEX)
+			continue;
+
+		ifindex = nla_get_s32(nla);
+		ret = -ENODEV;
+		dev = __dev_get_by_index(net, ifindex);
+		if (!dev) {
+			NL_SET_ERR_MSG_ATTR(extack, nla, "Unknown ifindex");
It would be nice to have the ifindex in the error message. This message does not
give more information than "ENODEV".
extack infra does not allow dynamic messages. It does point to the
location of the bad index, so userspace could figure it out.
Oh yes, my bad.


Thank you,
Nicolas

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: Lahav Schlesinger <hidden>
Date: 2021-12-07 12:49:11

On Mon, Dec 06, 2021 at 09:25:17AM +0100, Nicolas Dichtel wrote:
CAUTION: External E-Mail - Use caution with links and attachments


Le 05/12/2021 à 10:36, Lahav Schlesinger a écrit :
Some comments below, but please, keep the people who replied to previous
versions of this patch in cc.

[snip]
quoted
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index eebd3894fe89..68fcde9c0c5e 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -348,6 +348,7 @@ enum {
      IFLA_PARENT_DEV_NAME,
      IFLA_PARENT_DEV_BUS_NAME,

+     IFLA_IFINDEX,
nit: maybe the previous blank line sit better after this new attribute (and
before __IFLA_MAX)?
Due to the comment above the previous 2 attributes, I think that by
removing this empty line it can be accidentally thought as if the new
attribute is part of this "block".
As for adding a new line before __IFLA_MAX, I wanted to preserve the
appearance we had before the IFLA_PARENT_DEV_xxx attributes where added,
where there was no empty line before __IFLA_MAX.

I don't mind either way though, whatever looks better to you.
quoted
      __IFLA_MAX
 };
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index fd030e02f16d..5165cc699d97 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -37,6 +37,7 @@
 #include <linux/pci.h>
 #include <linux/etherdevice.h>
 #include <linux/bpf.h>
+#include <linux/sort.h>

 #include <linux/uaccess.h>
@@ -1880,6 +1881,7 @@ static const struct nla_policy ifla_policy[IFLA_MAX+1] = {
      [IFLA_PROTO_DOWN_REASON] = { .type = NLA_NESTED },
      [IFLA_NEW_IFINDEX]      = NLA_POLICY_MIN(NLA_S32, 1),
      [IFLA_PARENT_DEV_NAME]  = { .type = NLA_NUL_STRING },
+     [IFLA_IFINDEX]          = { .type = NLA_S32 },
Same policy than IFLA_NEW_IFINDEX to refuse negative ifindex.
Right, thanks
quoted
 };

 static const struct nla_policy ifla_info_policy[IFLA_INFO_MAX+1] = {
@@ -3050,6 +3052,78 @@ static int rtnl_group_dellink(const struct net *net, int group)
      return 0;
 }

+static int dev_ifindex_cmp(const void *a, const void *b)
+{
+     struct net_device * const *dev1 = a, * const *dev2 = b;
+
+     return (*dev1)->ifindex - (*dev2)->ifindex;
+}
+
+static int rtnl_ifindex_dellink(struct net *net, struct nlattr *head, int len,
+                             struct netlink_ext_ack *extack)
+{
+     int i = 0, num_devices = 0, rem;
+     struct net_device **dev_list;
+     const struct nlattr *nla;
+     LIST_HEAD(list_kill);
+     int ret;
+
+     nla_for_each_attr(nla, head, len, rem) {
+             if (nla_type(nla) == IFLA_IFINDEX)
+                     num_devices++;
+     }
+
+     dev_list = kmalloc_array(num_devices, sizeof(*dev_list), GFP_KERNEL);
+     if (!dev_list)
+             return -ENOMEM;
+
+     nla_for_each_attr(nla, head, len, rem) {
+             const struct rtnl_link_ops *ops;
+             struct net_device *dev;
+             int ifindex;
+
+             if (nla_type(nla) != IFLA_IFINDEX)
+                     continue;
+
+             ifindex = nla_get_s32(nla);
+             ret = -ENODEV;
+             dev = __dev_get_by_index(net, ifindex);
+             if (!dev) {
+                     NL_SET_ERR_MSG_ATTR(extack, nla, "Unknown ifindex");
It would be nice to have the ifindex in the error message. This message does not
give more information than "ENODEV".
quoted
+                     goto out_free;
+             }
+
+             ret = -EOPNOTSUPP;
+             ops = dev->rtnl_link_ops;
+             if (!ops || !ops->dellink) {
+                     NL_SET_ERR_MSG_ATTR(extack, nla, "Device cannot be deleted");
Same here.


Thank you,
Nicolas

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: Jakub Kicinski <kuba@kernel.org>
Date: 2021-12-07 15:37:42

On Mon, 6 Dec 2021 21:12:33 -0700 David Ahern wrote:
The need to walk the list twice (3 really with the sort) to means the
array solution is better.
Technically the linear walk will be dwarfed by the sort for non-trivial
inputs (have I been conducting too many coding interviews?) 

Especially with retpolines :(
I liked the array variant better. Jakub?
I always default to attr-per-member unless there is a strong constraint
on message size. Trying to extend old school netlink (TC etc) left me
scarred. But we can do array, no big deal.

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: Nicolas Dichtel <hidden>
Date: 2021-12-08 07:44:11

Le 07/12/2021 à 13:48, Lahav Schlesinger a écrit :
On Mon, Dec 06, 2021 at 09:25:17AM +0100, Nicolas Dichtel wrote:
quoted
CAUTION: External E-Mail - Use caution with links and attachments


Le 05/12/2021 à 10:36, Lahav Schlesinger a écrit :
Some comments below, but please, keep the people who replied to previous
versions of this patch in cc.

[snip]
quoted
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index eebd3894fe89..68fcde9c0c5e 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -348,6 +348,7 @@ enum {
      IFLA_PARENT_DEV_NAME,
      IFLA_PARENT_DEV_BUS_NAME,

+     IFLA_IFINDEX,
nit: maybe the previous blank line sit better after this new attribute (and
before __IFLA_MAX)?
Due to the comment above the previous 2 attributes, I think that by
removing this empty line it can be accidentally thought as if the new
attribute is part of this "block".
As for adding a new line before __IFLA_MAX, I wanted to preserve the
appearance we had before the IFLA_PARENT_DEV_xxx attributes where added,
where there was no empty line before __IFLA_MAX.
Good point.
I don't mind either way though, whatever looks better to you.
Ok, forget my comment.


Regards,
Nicolas

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: Lahav Schlesinger <hidden>
Date: 2021-12-08 21:47:25

On Mon, Dec 06, 2021 at 09:12:33PM -0700, David Ahern wrote:
CAUTION: External E-Mail - Use caution with links and attachments


On 12/5/21 2:36 AM, Lahav Schlesinger wrote:
quoted
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index fd030e02f16d..5165cc699d97 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -37,6 +37,7 @@
 #include <linux/pci.h>
 #include <linux/etherdevice.h>
 #include <linux/bpf.h>
+#include <linux/sort.h>

 #include <linux/uaccess.h>
@@ -1880,6 +1881,7 @@ static const struct nla_policy ifla_policy[IFLA_MAX+1] = {
      [IFLA_PROTO_DOWN_REASON] = { .type = NLA_NESTED },
      [IFLA_NEW_IFINDEX]      = NLA_POLICY_MIN(NLA_S32, 1),
      [IFLA_PARENT_DEV_NAME]  = { .type = NLA_NUL_STRING },
+     [IFLA_IFINDEX]          = { .type = NLA_S32 },
you need to make sure this new attribute can not be used in setlink
requests or getlink.
Right, I'll add it.
quoted
 };

 static const struct nla_policy ifla_info_policy[IFLA_INFO_MAX+1] = {
@@ -3050,6 +3052,78 @@ static int rtnl_group_dellink(const struct net *net, int group)
      return 0;
 }

+static int dev_ifindex_cmp(const void *a, const void *b)
+{
+     struct net_device * const *dev1 = a, * const *dev2 = b;
const struct net_device *dev1 = 1, *dev2 = b;
The array stores 'struct net_device *', and 'a' and 'b' are pointers to
elements in this array (so 'struct net_device **', ignoring constness)
quoted
+
+     return (*dev1)->ifindex - (*dev2)->ifindex;
+}
+
+static int rtnl_ifindex_dellink(struct net *net, struct nlattr *head, int len,
+                             struct netlink_ext_ack *extack)
+{
+     int i = 0, num_devices = 0, rem;
+     struct net_device **dev_list;
+     const struct nlattr *nla;
+     LIST_HEAD(list_kill);
+     int ret;
+
+     nla_for_each_attr(nla, head, len, rem) {
+             if (nla_type(nla) == IFLA_IFINDEX)
+                     num_devices++;
+     }

The need to walk the list twice (3 really with the sort) to means the
array solution is better.
quoted
+
+     dev_list = kmalloc_array(num_devices, sizeof(*dev_list), GFP_KERNEL);
+     if (!dev_list)
+             return -ENOMEM;
+
+     nla_for_each_attr(nla, head, len, rem) {
+             const struct rtnl_link_ops *ops;
+             struct net_device *dev;
+             int ifindex;
+
+             if (nla_type(nla) != IFLA_IFINDEX)
+                     continue;
and this business too. We have arrays in other places
(net/ipv4/nexthop.c), so this is not the first.

quoted
+
+             ifindex = nla_get_s32(nla);
+             ret = -ENODEV;
+             dev = __dev_get_by_index(net, ifindex);
+             if (!dev) {
+                     NL_SET_ERR_MSG_ATTR(extack, nla, "Unknown ifindex");
+                     goto out_free;
+             }
+
+             ret = -EOPNOTSUPP;
+             ops = dev->rtnl_link_ops;
+             if (!ops || !ops->dellink) {
+                     NL_SET_ERR_MSG_ATTR(extack, nla, "Device cannot be deleted");
+                     goto out_free;
+             }
+
+             dev_list[i++] = dev;
+     }
+
+     /* Sort devices, so we could skip duplicates */
+     sort(dev_list, num_devices, sizeof(*dev_list), dev_ifindex_cmp, NULL);
how did this sort change the results? 10k compares and re-order has to
add some overhead.
No visible changes from what I saw, this API is as fast as group
deletion. Maybe a few tens of milliseconds slower, but it's lost in the
noise.
I'll run more thorough benchmarks to get to a more conclusive conclusion.

Also just pointing out that the sort will be needed even if we pass an
array (IFLA_IFINDEX_LIST) instead.
Feels like CS 101, but do you have a better approach for detecting
duplicates in an array? I imagine a hash table will be slower as it will
need to allocate a node object for each device (assuming we don't want
to add a new hlist_node to 'struct net_device' just for this)
quoted
+
+     for (i = 0; i < num_devices; i++) {
+             struct net_device *dev = dev_list[i];
+
+             if (i != 0 && dev_list[i - 1]->ifindex == dev->ifindex)
                if (i && ...)


I liked the array variant better. Jakub?

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: Lahav Schlesinger <hidden>
Date: 2021-12-08 21:50:45

On Tue, Dec 07, 2021 at 07:37:15AM -0800, Jakub Kicinski wrote:
CAUTION: External E-Mail - Use caution with links and attachments


On Mon, 6 Dec 2021 21:12:33 -0700 David Ahern wrote:
quoted
The need to walk the list twice (3 really with the sort) to means the
array solution is better.
Technically the linear walk will be dwarfed by the sort for non-trivial
inputs (have I been conducting too many coding interviews?)

Especially with retpolines :(
quoted
I liked the array variant better. Jakub?
I always default to attr-per-member unless there is a strong constraint
on message size. Trying to extend old school netlink (TC etc) left me
scarred. But we can do array, no big deal.
Thanks, I'll send a new patch with array API and all previous comments

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: David Ahern <hidden>
Date: 2021-12-08 23:43:34

On 12/8/21 2:47 PM, Lahav Schlesinger wrote:
No visible changes from what I saw, this API is as fast as group
deletion. Maybe a few tens of milliseconds slower, but it's lost in the
noise.
I'll run more thorough benchmarks to get to a more conclusive conclusion.

Also just pointing out that the sort will be needed even if we pass an
array (IFLA_IFINDEX_LIST) instead.
Feels like CS 101, but do you have a better approach for detecting
duplicates in an array? I imagine a hash table will be slower as it will
need to allocate a node object for each device (assuming we don't want
to add a new hlist_node to 'struct net_device' just for this)
I think marking the dev's and then using a delete loop is going to be
the better approach - avoid the sort and duplicate problem. I use that
approach for nexthop deletes:

https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/tree/net/ipv4/nexthop.c#n1849

Find a hole in net_device struct in an area used only for control path
and add 'bool grp_delete' (or a 1-bit hole). Mark the devices on pass
and delete them on another.

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: Jakub Kicinski <kuba@kernel.org>
Date: 2021-12-09 00:04:15

On Wed, 8 Dec 2021 16:43:28 -0700 David Ahern wrote:
On 12/8/21 2:47 PM, Lahav Schlesinger wrote:
quoted
No visible changes from what I saw, this API is as fast as group
deletion. Maybe a few tens of milliseconds slower, but it's lost in the
noise.
I'll run more thorough benchmarks to get to a more conclusive conclusion.

Also just pointing out that the sort will be needed even if we pass an
array (IFLA_IFINDEX_LIST) instead.
Feels like CS 101, but do you have a better approach for detecting
duplicates in an array? I imagine a hash table will be slower as it will
need to allocate a node object for each device (assuming we don't want
to add a new hlist_node to 'struct net_device' just for this)  
I think marking the dev's and then using a delete loop is going to be
the better approach - avoid the sort and duplicate problem. I use that
approach for nexthop deletes:

https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/tree/net/ipv4/nexthop.c#n1849

Find a hole in net_device struct in an area used only for control path
and add 'bool grp_delete' (or a 1-bit hole). Mark the devices on pass
and delete them on another.
If we want to keep state in the netdev itself we can probably piggy
back on dev->unreg_list. It should be initialized to empty and not
touched unless device goes thru unregister.

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: David Ahern <hidden>
Date: 2021-12-09 00:18:57

On 12/8/21 5:04 PM, Jakub Kicinski wrote:
On Wed, 8 Dec 2021 16:43:28 -0700 David Ahern wrote:
quoted
On 12/8/21 2:47 PM, Lahav Schlesinger wrote:
quoted
No visible changes from what I saw, this API is as fast as group
deletion. Maybe a few tens of milliseconds slower, but it's lost in the
noise.
I'll run more thorough benchmarks to get to a more conclusive conclusion.

Also just pointing out that the sort will be needed even if we pass an
array (IFLA_IFINDEX_LIST) instead.
Feels like CS 101, but do you have a better approach for detecting
duplicates in an array? I imagine a hash table will be slower as it will
need to allocate a node object for each device (assuming we don't want
to add a new hlist_node to 'struct net_device' just for this)  
I think marking the dev's and then using a delete loop is going to be
the better approach - avoid the sort and duplicate problem. I use that
approach for nexthop deletes:

https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/tree/net/ipv4/nexthop.c#n1849

Find a hole in net_device struct in an area used only for control path
and add 'bool grp_delete' (or a 1-bit hole). Mark the devices on pass
and delete them on another.
If we want to keep state in the netdev itself we can probably piggy
back on dev->unreg_list. It should be initialized to empty and not
touched unless device goes thru unregister.
isn't that used when the delink function calls unregister_netdevice_queue?

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: Jakub Kicinski <kuba@kernel.org>
Date: 2021-12-09 00:45:51

On Wed, 8 Dec 2021 17:18:48 -0700 David Ahern wrote:
On 12/8/21 5:04 PM, Jakub Kicinski wrote:
quoted
quoted
I think marking the dev's and then using a delete loop is going to be
the better approach - avoid the sort and duplicate problem. I use that
approach for nexthop deletes:

https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/tree/net/ipv4/nexthop.c#n1849

Find a hole in net_device struct in an area used only for control path
and add 'bool grp_delete' (or a 1-bit hole). Mark the devices on pass
and delete them on another.  
If we want to keep state in the netdev itself we can probably piggy
back on dev->unreg_list. It should be initialized to empty and not
touched unless device goes thru unregister.
isn't that used when the delink function calls unregister_netdevice_queue?
Sure but all the validation is before we start calling delink, so
doesn't matter?

list to_kill, queued;

for_each_attr(nest) {
	...

	dev = get_by_index(nla_get_s32(..));
	if (!dev)
		goto cleanup;
	if (!list_empty(&dev->unreg_list))
		goto cleanup;
	...

	list_add(&to_kill, &dev->unreg_list);
}

for_each_entry_safe(to_kill) {
	list_del_init(&dev->unreg_list);
	->dellink(dev, queued);
}

unreg_many(queued);

return

cleanup:
	for_each_entry_safe(to_kill) {
		list_del_init(&dev->unreg_list);
	}

No?

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: David Ahern <hidden>
Date: 2021-12-09 00:47:10

On 12/8/21 5:45 PM, Jakub Kicinski wrote:
On Wed, 8 Dec 2021 17:18:48 -0700 David Ahern wrote:
quoted
On 12/8/21 5:04 PM, Jakub Kicinski wrote:
quoted
quoted
I think marking the dev's and then using a delete loop is going to be
the better approach - avoid the sort and duplicate problem. I use that
approach for nexthop deletes:

https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/tree/net/ipv4/nexthop.c#n1849

Find a hole in net_device struct in an area used only for control path
and add 'bool grp_delete' (or a 1-bit hole). Mark the devices on pass
and delete them on another.  
If we want to keep state in the netdev itself we can probably piggy
back on dev->unreg_list. It should be initialized to empty and not
touched unless device goes thru unregister.
isn't that used when the delink function calls unregister_netdevice_queue?
Sure but all the validation is before we start calling delink, so
doesn't matter?

list to_kill, queued;

for_each_attr(nest) {
	...

	dev = get_by_index(nla_get_s32(..));
	if (!dev)
		goto cleanup;
	if (!list_empty(&dev->unreg_list))
		goto cleanup;
	...

	list_add(&to_kill, &dev->unreg_list);
}

for_each_entry_safe(to_kill) {
	list_del_init(&dev->unreg_list);
sure my mental model was walking the list and not doing that part. :-)

	->dellink(dev, queued);
}

unreg_many(queued);

return

cleanup:
	for_each_entry_safe(to_kill) {
		list_del_init(&dev->unreg_list);
	}

No?

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: Lahav Schlesinger <hidden>
Date: 2022-01-04 08:21:34

On Wed, Dec 08, 2021 at 04:45:44PM -0800, Jakub Kicinski wrote:
CAUTION: External E-Mail - Use caution with links and attachments


On Wed, 8 Dec 2021 17:18:48 -0700 David Ahern wrote:
quoted
On 12/8/21 5:04 PM, Jakub Kicinski wrote:
quoted
quoted
I think marking the dev's and then using a delete loop is going to be
the better approach - avoid the sort and duplicate problem. I use that
approach for nexthop deletes:

https://urldefense.proofpoint.com/v2/url?u=https-3A__git.kernel.org_pub_scm_linux_kernel_git_netdev_net-2Dnext.git_tree_net_ipv4_nexthop.c-23n1849&d=DwICAg&c=euGZstcaTDllvimEN8b7jXrwqOf-v5A_CdpgnVfiiMM&r=-WbGKi6-8WU3IfnrkySymtcZWJMn_aVdolIPebPZGu4&m=td9sv1_NnALM3QjLFg8h9u1P7DUJJL2YhzqyneZ95Gc&s=nEGgvxCwd3MrSXWhv-5Fo7mUlBkuKbHRBAO4VO9mG2o&e=

Find a hole in net_device struct in an area used only for control path
and add 'bool grp_delete' (or a 1-bit hole). Mark the devices on pass
and delete them on another.
If we want to keep state in the netdev itself we can probably piggy
back on dev->unreg_list. It should be initialized to empty and not
touched unless device goes thru unregister.
isn't that used when the delink function calls unregister_netdevice_queue?
Sure but all the validation is before we start calling delink, so
doesn't matter?

list to_kill, queued;

for_each_attr(nest) {
        ...

        dev = get_by_index(nla_get_s32(..));
        if (!dev)
                goto cleanup;
        if (!list_empty(&dev->unreg_list))
                goto cleanup;
        ...

        list_add(&to_kill, &dev->unreg_list);
}

for_each_entry_safe(to_kill) {
        list_del_init(&dev->unreg_list);
        ->dellink(dev, queued);
}

unreg_many(queued);

return

cleanup:
        for_each_entry_safe(to_kill) {
                list_del_init(&dev->unreg_list);
        }

No?
Hi Jakub, I just sent a V6 for this patch. Just wanted to note that I
tried this approach of using unreg_list, but it caused issues with e.g.
veth deletion - the dellink() of a veth peer automatically adds the
other peer to the queued list. So if the list of ifindices contains
both peers then when 'to_kill' is iterated, both the current device
and the next pointer will have their 'unreg_list' moved to the other
list, which later raised a page fault when 'to_kill' was further
iterated upon.

Therefore instead I added a big flag in a "hole" inside struct
net_device, as David suggested.

Re: [PATCH net-next v5] rtnetlink: Support fine-grained netdevice bulk deletion

From: Jakub Kicinski <kuba@kernel.org>
Date: 2022-01-04 14:55:55

On Tue, 4 Jan 2022 10:21:21 +0200 Lahav Schlesinger wrote:
Hi Jakub, I just sent a V6 for this patch. Just wanted to note that I
tried this approach of using unreg_list, but it caused issues with e.g.
veth deletion - the dellink() of a veth peer automatically adds the
other peer to the queued list. So if the list of ifindices contains
both peers then when 'to_kill' is iterated, both the current device
and the next pointer will have their 'unreg_list' moved to the other
list, which later raised a page fault when 'to_kill' was further
iterated upon.

Therefore instead I added a big flag in a "hole" inside struct
net_device, as David suggested.
Ack. Thanks for trying it out.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help