Subject: [RFC PATCH] rtnetlink: Add filter for VF info dump requests

From: Greg Rose <[email-masked]>
Date: Tue, 07 Feb 2012 14:33:46 -0800

Add a 256 bit filter to allow the user to filter which VFs' info will
get displayed during the dump info request.  This is to fix a bug in
which a an info dump request on a device with many VFs would overflow
the recvmsg buffer which is allocated to max(8192, PAGE_SIZE).  A
complimentary patch to the iproute2 ip tool will allow the user to
set/clear individual VF filters.

Signed-off-by: Greg Rose <[email-masked]>
---

 include/linux/if_link.h   |    6 ++++++
 include/linux/netdevice.h |    2 ++
 net/core/rtnetlink.c      |   40 +++++++++++++++++++++++++++++++++++-----
 3 files changed, 43 insertions(+), 5 deletions(-)

diff --git a/include/linux/if_link.h b/include/linux/if_link.h
index c52d4b5..052c240 100644
--- a/include/linux/if_link.h
+++ b/include/linux/if_link.h
@@ -280,6 +280,7 @@ enum {
 	IFLA_VF_VLAN,
 	IFLA_VF_TX_RATE,	/* TX Bandwidth Allocation */
 	IFLA_VF_SPOOFCHK,	/* Spoof Checking on/off switch */
+	IFLA_VF_INFOFILTER,	/* Filter vfinfo on dumps */
 	__IFLA_VF_MAX,
 };
 
@@ -305,6 +306,11 @@ struct ifla_vf_spoofchk {
 	__u32 vf;
 	__u32 setting;
 };
+
+struct ifla_vf_infofilter {
+	__u32 vf;
+	__u32 filter;
+};
 #ifdef __KERNEL__
 
 /* We don't want this structure exposed to user space */
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 0eac07c..dd30b5d 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1298,6 +1298,8 @@ struct net_device {
 
 	/* group the device belongs to */
 	int group;
+	/* VF info display filter - Number of VFs max is 256 */
+	unsigned long show_vfinfo_filter[BITS_TO_LONGS(256)];
 };
 #define to_net_dev(d) container_of(d, struct net_device, dev)
 
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 39aa20b..4fd78dd 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -727,9 +727,13 @@ static void copy_rtnl_link_stats64(void *v, const struct rtnl_link_stats64 *b)
 static inline int rtnl_vfinfo_size(const struct net_device *dev)
 {
 	if (dev->dev.parent && dev_is_pci(dev->dev.parent)) {
-
-		int num_vfs = dev_num_vf(dev->dev.parent);
+		int j;
+		int num_vfs = 0;
 		size_t size = nla_total_size(sizeof(struct nlattr));
+		for (j = 0; j < 256; j++) {
+			if (test_bit(j, dev->show_vfinfo_filter))
+				num_vfs++;
+		}
 		size += nla_total_size(num_vfs * sizeof(struct nlattr));
 		size += num_vfs *
 			(nla_total_size(sizeof(struct ifla_vf_mac)) +
@@ -876,6 +880,7 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
 	const struct rtnl_link_stats64 *stats;
 	struct nlattr *attr, *af_spec;
 	struct rtnl_af_ops *af_ops;
+	u32 num_vf_filters_set = 0;
 
 	ASSERT_RTNL();
 	nlh = nlmsg_put(skb, pid, seq, type, sizeof(*ifm), flags);
@@ -941,10 +946,18 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
 		goto nla_put_failure;
 	copy_rtnl_link_stats64(nla_data(attr), stats);
 
-	if (dev->dev.parent)
-		NLA_PUT_U32(skb, IFLA_NUM_VF, dev_num_vf(dev->dev.parent));
+	if (dev->dev.parent) {
+		int j;
+		for (j = 0; j < 256; j++) {
+			if (test_bit(j, dev->show_vfinfo_filter))
+				num_vf_filters_set++;
+		}
+		if (num_vf_filters_set)
+			NLA_PUT_U32(skb, IFLA_NUM_VF, num_vf_filters_set);
+	}
 
-	if (dev->netdev_ops->ndo_get_vf_config && dev->dev.parent) {
+	if (dev->netdev_ops->ndo_get_vf_config && dev->dev.parent &&
+			num_vf_filters_set) {
 		int i;
 
 		struct nlattr *vfinfo, *vf;
@@ -960,6 +973,9 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
 			struct ifla_vf_tx_rate vf_tx_rate;
 			struct ifla_vf_spoofchk vf_spoofchk;
 
+			if (!test_bit(i, dev->show_vfinfo_filter))
+				continue;
+
 			/*
 			 * Not all SR-IOV capable drivers support the
 			 * spoofcheck query.  Preset to -1 so the user
@@ -1234,6 +1250,20 @@ static int do_setvfinfo(struct net_device *dev, struct nlattr *attr)
 							       ivs->setting);
 			break;
 		}
+		case IFLA_VF_INFOFILTER: {
+			struct ifla_vf_infofilter *ivf;
+			ivf = nla_data(vf);
+			if (ivf->vf < dev_num_vf(dev->dev.parent)) {
+				if (ivf->filter)
+					set_bit(ivf->vf,
+						dev->show_vfinfo_filter);
+			else
+					clear_bit(ivf->vf,
+						dev->show_vfinfo_filter);
+				err = 0;
+			}
+			break;
+		}
 		default:
 			err = -EINVAL;
 			break;