lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Mon, 28 Jan 2019 15:44:56 -0800
From:   Jakub Kicinski <jakub.kicinski@...ronome.com>
To:     davem@...emloft.net
Cc:     oss-drivers@...ronome.com, netdev@...r.kernel.org,
        jiri@...nulli.us, f.fainelli@...il.com, andrew@...n.ch,
        mkubecek@...e.cz, dsahern@...il.com, simon.horman@...ronome.com,
        jesse.brandeburg@...el.com, maciejromanfijalkowski@...il.com,
        vasundhara-v.volam@...adcom.com, michael.chan@...adcom.com,
        shalomt@...lanox.com, idosch@...lanox.com,
        Jakub Kicinski <jakub.kicinski@...ronome.com>
Subject: [RFC 03/14] net: hstats: add basic/core functionality

Add basic hierarchical stats.  For now there is no hierarchies
or other fancy features.  An ndo is added, and drivers can return
multiple groups of stats by adding the to them dump with
rtnl_hstat_add_grp().

Each group has attributes (qualifiers) which designate the direction
of the statistic (TX vs RX) and the source (device vs driver).

A handful of common statistics maintained by Linux drivers is added.

Dumping machinery is a little involved to make extensions easy.
A simple stack-based machine is employed which will in due course
help keep tracking of children and iteration over classifiers.

Signed-off-by: Jakub Kicinski <jakub.kicinski@...ronome.com>
---
 include/linux/netdevice.h    |   9 +
 include/net/hstats.h         |  94 +++++++
 include/uapi/linux/if_link.h |  45 ++++
 net/core/Makefile            |   2 +-
 net/core/hstats.c            | 497 +++++++++++++++++++++++++++++++++++
 net/core/rtnetlink.c         |  21 ++
 6 files changed, 667 insertions(+), 1 deletion(-)
 create mode 100644 include/net/hstats.h
 create mode 100644 net/core/hstats.c

diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index e675ef97a426..9f16036312f9 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -941,6 +941,8 @@ struct dev_ifalias {
 	char ifalias[];
 };
 
+struct rtnl_hstat_req;
+
 /*
  * This structure defines the management hooks for network devices.
  * The following hooks can be defined; unless noted otherwise, they are
@@ -1245,6 +1247,11 @@ struct dev_ifalias {
  *	that got dropped are freed/returned via xdp_return_frame().
  *	Returns negative number, means general error invoking ndo, meaning
  *	no frames were xmit'ed and core-caller will free all frames.
+ * int (*ndo_hstat_get_groups)(const struct net_device *dev,
+ *			       struct rtnl_hstat_req *req);
+ *	This function is used to retrieve driver's groups of hierarchical stats.
+ *	Driver should use rtnl_hstat_add_grp() to report its groups.
+ *	See Documentation/networking/hstats.rst for details.
  */
 struct net_device_ops {
 	int			(*ndo_init)(struct net_device *dev);
@@ -1441,6 +1448,8 @@ struct net_device_ops {
 						u32 flags);
 	int			(*ndo_xsk_async_xmit)(struct net_device *dev,
 						      u32 queue_id);
+	int			(*ndo_hstat_get_groups)(const struct net_device *dev,
+							struct rtnl_hstat_req *req);
 };
 
 /**
diff --git a/include/net/hstats.h b/include/net/hstats.h
new file mode 100644
index 000000000000..c2e8b379237a
--- /dev/null
+++ b/include/net/hstats.h
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/* Copyright (C) 2019 Netronome Systems, Inc. */
+
+#ifndef _NET_HSTATS_H
+#define _NET_HSTATS_H
+
+#include <linux/if_link.h>
+#include <linux/kernel.h>
+#include <net/netlink.h>
+
+struct net_device;
+struct sk_buff;
+
+/* Internal driver/core qualifiers used as indexes in qualifier tables
+ * and translated into IFLA_HSTATS_QUAL_* in dumps.
+ */
+enum {
+	RTNL_HSTATS_QUAL_TYPE,
+	RTNL_HSTATS_QUAL_DIRECTION,
+
+	RTNL_HSTATS_QUAL_CNT
+};
+
+struct hstat_dumper;
+struct rtnl_hstat_group;
+
+struct rtnl_hstat_req {
+	int err;
+	struct sk_buff *skb;
+	struct hstat_dumper *dumper;
+};
+
+struct rtnl_hstat_qualifier {
+	unsigned int constant;
+};
+
+/**
+ * struct rtnl_hstat_group - node in the hstat hierarchy
+ * @qualifiers:	attributes describing this group
+ * @stats_cnt:	number of stats in the bitmask
+ * @stats:	bitmask of stats present
+ * @get_stats:	driver callback for dumping the stats
+ */
+struct rtnl_hstat_group {
+	/* Note: this is *not* indexed with IFLA_* attributes! */
+	struct rtnl_hstat_qualifier qualifiers[RTNL_HSTATS_QUAL_CNT];
+	/* Can't use bitmaps - words are variable length */
+	unsigned int stats_cnt;
+	u64 stats[DIV_ROUND_UP(IFLA_HSTATS_STAT_MAX + 1, 64)];
+	int (*get_stats)(struct net_device *dev, struct rtnl_hstat_req *req,
+			 const struct rtnl_hstat_group *grp);
+};
+
+void rtnl_hstat_add_grp(struct rtnl_hstat_req *req,
+			const struct rtnl_hstat_group *grp);
+
+static inline void
+rtnl_hstat_dump(struct rtnl_hstat_req *req, const int id, const u64 val)
+{
+	if (req->err)
+		return;
+	if (nla_put_u64_64bit(req->skb, id, val, IFLA_HSTATS_STAT_UNSPEC))
+		req->err = -EMSGSIZE;
+}
+
+size_t rtnl_get_link_hstats_size(const struct net_device *dev);
+size_t rtnl_get_link_hstats(struct sk_buff *skb, struct net_device *dev,
+			    int *prividx);
+
+enum {
+#define RTNL_HSTAT_BIT(_name, _word) \
+	RTNL_HSTATS_STAT_ ## _name ## _BIT = \
+		BIT_ULL(IFLA_HSTATS_STAT_ ## _name - 1 - ((_word) * 64))
+
+	/* Common Linux stats */
+	RTNL_HSTAT_BIT(LINUX_PKTS, 0),
+	RTNL_HSTAT_BIT(LINUX_BYTES, 0),
+	RTNL_HSTAT_BIT(LINUX_BUSY, 0),
+	RTNL_HSTAT_BIT(LINUX_CSUM_PARTIAL, 0),
+	RTNL_HSTAT_BIT(LINUX_CSUM_COMPLETE, 0),
+	RTNL_HSTAT_BIT(LINUX_CSUM_UNNECESSARY, 0),
+	RTNL_HSTAT_BIT(LINUX_SEGMENTATION_OFFLOAD_PKTS, 0),
+#undef RTNL_HSTAT_BIT
+};
+
+/* Helper defines for common qualifier sets */
+#define RTNL_HSTATS_QUALS_BASIC(type, dir)				\
+	[RTNL_HSTATS_QUAL_TYPE] = {					\
+		.constant	= IFLA_HSTATS_QUAL_TYPE_ ##type,	\
+	},								\
+	[RTNL_HSTATS_QUAL_DIRECTION] = {				\
+		.constant	= IFLA_HSTATS_QUAL_DIR_ ##dir,		\
+	}
+#endif
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index 5b225ff63b48..55fcef81e142 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -910,6 +910,7 @@ enum {
 	IFLA_STATS_LINK_XSTATS_SLAVE,
 	IFLA_STATS_LINK_OFFLOAD_XSTATS,
 	IFLA_STATS_AF_SPEC,
+	IFLA_STATS_LINK_HSTATS,
 	__IFLA_STATS_MAX,
 };
 
@@ -938,6 +939,50 @@ enum {
 };
 #define IFLA_OFFLOAD_XSTATS_MAX (__IFLA_OFFLOAD_XSTATS_MAX - 1)
 
+/* These are embedded into IFLA_STATS_LINK_HSTATS:
+ * See Documentation/networking/hstats.rst for details.
+ */
+enum {
+	IFLA_HSTATS_UNSPEC,
+	IFLA_HSTATS_GROUP,
+	IFLA_HSTATS_STATS,
+	IFLA_HSTATS_QUAL_TYPE,
+	IFLA_HSTATS_QUAL_DIRECTION,
+	__IFLA_HSTATS_MAX,
+};
+#define IFLA_HSTATS_MAX (__IFLA_HSTATS_MAX - 1)
+
+enum {
+	IFLA_HSTATS_QUAL_TYPE_UNSPEC,
+	IFLA_HSTATS_QUAL_TYPE_DEV,
+	IFLA_HSTATS_QUAL_TYPE_DRV,
+	__IFLA_HSTATS_QUAL_TYPE_MAX,
+};
+#define IFLA_HSTATS_QUAL_TYPE_MAX (__IFLA_HSTATS_QUAL_TYPE_MAX - 1)
+
+enum {
+	IFLA_HSTATS_QUAL_DIR_UNSPEC,
+	IFLA_HSTATS_QUAL_DIR_RX,
+	IFLA_HSTATS_QUAL_DIR_TX,
+	__IFLA_HSTATS_QUAL_DIR_MAX,
+};
+#define IFLA_HSTATS_QUAL_DIR_MAX (__IFLA_HSTATS_QUAL_DIR_MAX - 1)
+
+enum {
+	IFLA_HSTATS_STAT_UNSPEC,
+	/* Common statistics */
+	IFLA_HSTATS_STAT_LINUX_PKTS, /* 0 */
+	IFLA_HSTATS_STAT_LINUX_BYTES,
+	IFLA_HSTATS_STAT_LINUX_BUSY,
+	IFLA_HSTATS_STAT_LINUX_CSUM_PARTIAL,
+	IFLA_HSTATS_STAT_LINUX_CSUM_COMPLETE,
+	IFLA_HSTATS_STAT_LINUX_CSUM_UNNECESSARY,
+	IFLA_HSTATS_STAT_LINUX_SEGMENTATION_OFFLOAD_PKTS,
+
+	__IFLA_HSTATS_STAT_MAX,
+};
+#define IFLA_HSTATS_STAT_MAX (__IFLA_HSTATS_STAT_MAX - 1)
+
 /* XDP section */
 
 #define XDP_FLAGS_UPDATE_IF_NOEXIST	(1U << 0)
diff --git a/net/core/Makefile b/net/core/Makefile
index fccd31e0e7f7..30635dfbbe9b 100644
--- a/net/core/Makefile
+++ b/net/core/Makefile
@@ -11,7 +11,7 @@ obj-$(CONFIG_SYSCTL) += sysctl_net_core.o
 obj-y		     += dev.o ethtool.o dev_addr_lists.o dst.o netevent.o \
 			neighbour.o rtnetlink.o utils.o link_watch.o filter.o \
 			sock_diag.o dev_ioctl.o tso.o sock_reuseport.o \
-			fib_notifier.o xdp.o
+			fib_notifier.o xdp.o hstats.o
 
 obj-y += net-sysfs.o
 obj-$(CONFIG_PAGE_POOL) += page_pool.o
diff --git a/net/core/hstats.c b/net/core/hstats.c
new file mode 100644
index 000000000000..183a1c5dd93a
--- /dev/null
+++ b/net/core/hstats.c
@@ -0,0 +1,497 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/* Copyright (C) 2019 Netronome Systems, Inc. */
+
+#include <linux/bitmap.h>
+#include <linux/err.h>
+#include <linux/if_link.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <net/hstats.h>
+#include <net/netlink.h>
+
+/* We deploy a simple stack-based dumper to walk the hierarchies.
+ * This is the documentation format for quick analysis of the state machine:
+ *
+ *   Header (in case there are move than one possibility):
+ *
+ * o |  direct action 1 \ __ these are performed by the code
+ * r |  direct action 2 /
+ * d |  --------------
+ * e | |  STACK CMD 1 | \ __  these are popped from the stack and run
+ * r v |  STACK CMD 2 | /     in order after current handler completes
+ *      ============== <---- top of the stack before current handler
+ */
+enum hstat_dumper_cmd {
+	/*      open grp
+	 *   put const quals
+	 *   ---------------
+	 *  |   DUMP STATS  |
+	 *  |    CLOSE grp  |
+	 *   ===============
+	 */
+	HSTAT_DCMD_GRP_LOAD,
+	/* dump all statitics
+	 */
+	HSTAT_DCMD_GRP_DUMP,
+	/* close grp */
+	HSTAT_DCMD_GRP_CLOSE,
+	/* count root group (netlink restart index) */
+	HSTAT_DCMD_ROOT_GRP_DONE,
+};
+
+struct hstat_dumper {
+	struct sk_buff *skb;
+	struct net_device *dev;
+	/* For sizing we only have a const pointer to dev */
+	const struct net_device *const_dev;
+	int err;
+
+	/* For calculating skb size */
+	bool sizing;
+	size_t size;
+
+	int current_root_grp;
+	int last_completed_root_grp;
+
+	u8 *cmd_stack;
+	size_t cmd_stack_top;
+	size_t cmd_stack_len;
+};
+
+struct hstat_dumper_cmd_simple {
+	u64 cmd;
+};
+
+struct hstat_dumper_cmd_grp_load {
+	const struct rtnl_hstat_group *grp;
+	u64 cmd;
+};
+
+struct hstat_dumper_cmd_grp_dump {
+	const struct rtnl_hstat_group *grp;
+	u64 cmd;
+};
+
+struct hstat_dumper_cmd_grp_close {
+	struct nlattr *nl_attr;
+	u64 cmd;
+};
+
+/* RTNL helpers */
+static const int rtnl_qual2ifla[RTNL_HSTATS_QUAL_CNT] = {
+	[RTNL_HSTATS_QUAL_TYPE]		= IFLA_HSTATS_QUAL_TYPE,
+	[RTNL_HSTATS_QUAL_DIRECTION]	= IFLA_HSTATS_QUAL_DIRECTION,
+};
+
+static bool rtnl_hstat_qualifier_present(const struct rtnl_hstat_qualifier *q)
+{
+	return q->constant;
+}
+
+/* Dumper basics */
+static u64 hstat_dumper_peek_cmd(struct hstat_dumper *dumper)
+{
+	return *(u64 *)(dumper->cmd_stack + dumper->cmd_stack_top - 8);
+}
+
+static int hstat_dumper_discard(struct hstat_dumper *dumper, size_t len)
+{
+	if (WARN_ON_ONCE(dumper->cmd_stack_top < len))
+		return -EINVAL;
+	dumper->cmd_stack_top -= len;
+	return 0;
+}
+
+static int hstat_dumper_pop(struct hstat_dumper *dumper, void *dst, size_t len)
+{
+	if (WARN_ON_ONCE(dumper->cmd_stack_top < len))
+		return -EINVAL;
+	dumper->cmd_stack_top -= len;
+	memcpy(dst, dumper->cmd_stack + dumper->cmd_stack_top, len);
+	return 0;
+}
+
+static bool hstat_dumper_done(struct hstat_dumper *dumper)
+{
+	return !dumper->cmd_stack_top;
+}
+
+static int hstat_dumper_error(struct hstat_dumper *dumper)
+{
+	if (WARN_ON_ONCE(dumper->cmd_stack_top && dumper->cmd_stack_top < 8))
+		return -EINVAL;
+	return 0;
+}
+
+static struct hstat_dumper *
+hstat_dumper_init(struct sk_buff *skb, const struct net_device *const_dev,
+		  struct net_device *dev, int *prividx)
+{
+	struct hstat_dumper *dumper;
+
+	dumper = kzalloc(sizeof(*dumper), GFP_KERNEL);
+	if (!dumper)
+		return NULL;
+	dumper->cmd_stack = kmalloc(8096, GFP_KERNEL);
+	if (!dumper->cmd_stack) {
+		kfree(dumper);
+		return NULL;
+	}
+	dumper->cmd_stack_len = 8096;
+
+	dumper->skb = skb;
+	dumper->dev = dev;
+	dumper->const_dev = const_dev;
+	if (prividx)
+		dumper->last_completed_root_grp = *prividx;
+	else
+		dumper->sizing = true;
+
+	return dumper;
+}
+
+static void hstat_dumper_destroy(struct hstat_dumper *dumper)
+{
+	kfree(dumper->cmd_stack);
+	kfree(dumper);
+}
+
+/* Dumper pushers */
+static int
+__hstat_dumper_push_cmd(struct hstat_dumper *dumper, void *data, size_t len)
+{
+	/* All structures pushed must be multiple of 8 w/ cmd as last member */
+	if (WARN_ON_ONCE(len % 8))
+		return -EINVAL;
+
+	while (dumper->cmd_stack_len - dumper->cmd_stack_top < len) {
+		void *st;
+
+		st = krealloc(dumper->cmd_stack, dumper->cmd_stack_len * 2,
+			      GFP_KERNEL);
+		if (!st)
+			return -ENOMEM;
+
+		dumper->cmd_stack = st;
+		dumper->cmd_stack_len *= 2;
+	}
+
+	memcpy(dumper->cmd_stack + dumper->cmd_stack_top, data, len);
+	dumper->cmd_stack_top += len;
+	return 0;
+}
+
+static int
+hstat_dumper_push_grp_load(struct hstat_dumper *dumper,
+			   const struct rtnl_hstat_group *grp)
+{
+	struct hstat_dumper_cmd_grp_load cmd = {
+		.cmd =		HSTAT_DCMD_GRP_LOAD,
+		.grp =		grp,
+	};
+
+	return __hstat_dumper_push_cmd(dumper, &cmd, sizeof(cmd));
+}
+
+static int
+hstat_dumper_push_dump(struct hstat_dumper *dumper,
+		       const struct rtnl_hstat_group *grp)
+{
+	struct hstat_dumper_cmd_grp_dump cmd = {
+		.cmd =		HSTAT_DCMD_GRP_DUMP,
+		.grp =		grp,
+	};
+
+	return __hstat_dumper_push_cmd(dumper, &cmd, sizeof(cmd));
+}
+
+static int
+hstat_dumper_push_grp_close(struct hstat_dumper *dumper, struct nlattr *nl_grp)
+{
+	struct hstat_dumper_cmd_grp_close cmd = {
+		.cmd =		HSTAT_DCMD_GRP_CLOSE,
+		.nl_attr =	nl_grp,
+	};
+
+	return __hstat_dumper_push_cmd(dumper, &cmd, sizeof(cmd));
+}
+
+static int
+hstat_dumper_push_root_grp_done(struct hstat_dumper *dumper)
+{
+	struct hstat_dumper_cmd_simple cmd = { HSTAT_DCMD_ROOT_GRP_DONE };
+
+	return __hstat_dumper_push_cmd(dumper, &cmd, sizeof(cmd));
+}
+
+/* Dumper actions */
+static int hstat_dumper_open_grp(struct hstat_dumper *dumper)
+{
+	struct nlattr *nl_grp;
+	int err;
+
+	if (dumper->sizing) {
+		dumper->size += nla_total_size(0); /* IFLA_HSTATS_GROUP */
+		return 0;
+	}
+
+	/* Open group nlattr and push onto the stack a close command */
+	nl_grp = nla_nest_start(dumper->skb, IFLA_HSTATS_GROUP);
+	if (!nl_grp)
+		return -EMSGSIZE;
+
+	err = hstat_dumper_push_grp_close(dumper, nl_grp);
+	if (err) {
+		nla_nest_cancel(dumper->skb, nl_grp);
+		return err;
+	}
+
+	return 0;
+}
+
+static int
+hstat_dumper_grp_put_stats(struct hstat_dumper *dumper,
+			   const struct rtnl_hstat_group *grp)
+{
+	struct rtnl_hstat_req dump_req;
+	struct nlattr *nl_stats;
+	int err;
+
+	WARN_ON_ONCE(!grp->stats_cnt != !grp->get_stats);
+
+	if (!grp->stats_cnt)
+		return 0;
+
+	if (dumper->sizing) {
+		dumper->size += nla_total_size(0);
+		dumper->size += grp->stats_cnt * nla_total_size(8);
+		return 0;
+	}
+
+	nl_stats = nla_nest_start(dumper->skb, IFLA_HSTATS_STATS);
+	if (!nl_stats)
+		return -EMSGSIZE;
+
+	memset(&dump_req, 0, sizeof(dump_req));
+	dump_req.dumper = dumper;
+	dump_req.skb = dumper->skb;
+
+	err = grp->get_stats(dumper->dev, &dump_req, grp);
+	if (err)
+		goto err_cancel_stats;
+	err = dump_req.err;
+	if (err)
+		goto err_cancel_stats;
+
+	nla_nest_end(dumper->skb, nl_stats);
+	return 0;
+
+err_cancel_stats:
+	nla_nest_cancel(dumper->skb, nl_stats);
+	return err;
+}
+
+static int
+hstat_dumper_put_qual(struct hstat_dumper *dumper, int i, u32 val)
+{
+	if (dumper->sizing) {
+		dumper->size += nla_total_size(sizeof(u32));
+		return 0;
+	}
+
+	return nla_put_u32(dumper->skb, rtnl_qual2ifla[i], val);
+}
+
+/* Dumper handlers */
+static int hstat_dumper_grp_load(struct hstat_dumper *dumper)
+{
+	struct hstat_dumper_cmd_grp_load cmd;
+	int i, err;
+
+	err = hstat_dumper_pop(dumper, &cmd, sizeof(cmd));
+	if (err)
+		return err;
+	if (dumper->err)
+		return 0;
+
+	if (dumper->current_root_grp < dumper->last_completed_root_grp)
+		return 0;
+
+	err = hstat_dumper_open_grp(dumper);
+	if (err)
+		return err;
+
+	for (i = 0; i < RTNL_HSTATS_QUAL_CNT; i++) {
+		const struct rtnl_hstat_qualifier *q;
+
+		q = &cmd.grp->qualifiers[i];
+		if (!rtnl_hstat_qualifier_present(q))
+			continue;
+
+		if (q->constant) {
+			err = hstat_dumper_put_qual(dumper, i, q->constant);
+			if (err)
+				return err;
+		}
+	}
+
+	return hstat_dumper_push_dump(dumper, cmd.grp);
+}
+
+static int hstat_dumper_grp_dump(struct hstat_dumper *dumper)
+{
+	struct hstat_dumper_cmd_grp_dump cmd;
+	int err;
+
+	err = hstat_dumper_pop(dumper, &cmd, sizeof(cmd));
+	if (err)
+		return err;
+	if (dumper->err)
+		return 0;
+
+	err = hstat_dumper_grp_put_stats(dumper, cmd.grp);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static int hstat_dumper_grp_close(struct hstat_dumper *dumper)
+{
+	struct hstat_dumper_cmd_grp_close cmd;
+	int err;
+
+	err = hstat_dumper_pop(dumper, &cmd, sizeof(cmd));
+	if (err)
+		return err;
+
+	if (!dumper->err)
+		nla_nest_end(dumper->skb, cmd.nl_attr);
+	else
+		nla_nest_cancel(dumper->skb, cmd.nl_attr);
+	return 0;
+}
+
+static int hstat_dumper_root_grp_done(struct hstat_dumper *dumper)
+{
+	int err;
+
+	err = hstat_dumper_discard(dumper, sizeof(u64));
+	if (err)
+		return err;
+	if (dumper->err)
+		return 0;
+
+	dumper->current_root_grp++;
+	return 0;
+}
+
+static int hstat_dumper_run(struct hstat_dumper *dumper)
+{
+	do {
+		int err;
+		u64 cmd;
+
+		err = hstat_dumper_error(dumper);
+		if (err)
+			return err;
+		if (hstat_dumper_done(dumper))
+			return 0;
+
+		cmd = hstat_dumper_peek_cmd(dumper);
+		switch (cmd) {
+		case HSTAT_DCMD_ROOT_GRP_DONE:
+			err = hstat_dumper_root_grp_done(dumper);
+			break;
+		case HSTAT_DCMD_GRP_LOAD:
+			err = hstat_dumper_grp_load(dumper);
+			break;
+		case HSTAT_DCMD_GRP_CLOSE:
+			err = hstat_dumper_grp_close(dumper);
+			break;
+		case HSTAT_DCMD_GRP_DUMP:
+			err = hstat_dumper_grp_dump(dumper);
+			break;
+		}
+		if (err && !dumper->err)
+			/* Record the errror hand keep invoking handlers,
+			 * handlers will see the error and only do clean up.
+			 */
+			dumper->err = err;
+	} while (true);
+
+	return dumper->err;
+}
+
+/* Driver helpers */
+void
+rtnl_hstat_add_grp(struct rtnl_hstat_req *req,
+		   const struct rtnl_hstat_group *grp)
+{
+	if (!req->err)
+		req->err = hstat_dumper_push_root_grp_done(req->dumper);
+	if (!req->err)
+		req->err = hstat_dumper_push_grp_load(req->dumper, grp);
+}
+EXPORT_SYMBOL(rtnl_hstat_add_grp);
+
+/* Stack call points */
+static size_t
+__rtnl_get_link_hstats(struct sk_buff *skb, const struct net_device *const_dev,
+		       struct net_device *dev, int *prividx)
+{
+	struct hstat_dumper *dumper;
+	struct rtnl_hstat_req req;
+	ssize_t ret;
+
+	if (!dev->netdev_ops || !dev->netdev_ops->ndo_hstat_get_groups)
+		return -ENODATA;
+
+	dumper = hstat_dumper_init(skb, const_dev, dev, prividx);
+	if (!dumper)
+		return -ENOMEM;
+
+	memset(&req, 0, sizeof(req));
+	req.dumper = dumper;
+
+	ret = dev->netdev_ops->ndo_hstat_get_groups(dev, &req);
+	if (ret < 0)
+		goto exit_dumper_destroy;
+	ret = req.err;
+	if (ret)
+		goto exit_dumper_destroy;
+
+	if (hstat_dumper_done(dumper))
+		return -ENODATA;
+
+	ret = hstat_dumper_run(dumper);
+	if (prividx) {
+		if (ret)
+			*prividx = dumper->current_root_grp;
+		else
+			*prividx = 0;
+	} else if (ret >= 0) {
+		ret = dumper->size;
+	}
+
+exit_dumper_destroy:
+	hstat_dumper_destroy(dumper);
+	return ret;
+}
+
+size_t rtnl_get_link_hstats_size(const struct net_device *dev)
+{
+	ssize_t ret;
+
+	ret = __rtnl_get_link_hstats(NULL, dev, NULL, NULL);
+	return ret;
+}
+
+size_t
+rtnl_get_link_hstats(struct sk_buff *skb, struct net_device *dev, int *prividx)
+{
+	ssize_t ret;
+
+	ret = __rtnl_get_link_hstats(skb, dev, dev, prividx);
+	return ret;
+}
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index f5a98082ac7a..a8112d0dca57 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -50,6 +50,7 @@
 #include <net/ip.h>
 #include <net/protocol.h>
 #include <net/arp.h>
+#include <net/hstats.h>
 #include <net/route.h>
 #include <net/udp.h>
 #include <net/tcp.h>
@@ -4871,6 +4872,23 @@ static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev,
 		*idxattr = 0;
 	}
 
+	if (stats_attr_valid(filter_mask, IFLA_STATS_LINK_HSTATS, *idxattr)) {
+		*idxattr = IFLA_STATS_LINK_HSTATS;
+		attr = nla_nest_start(skb, IFLA_STATS_LINK_HSTATS);
+		if (!attr)
+			goto nla_put_failure;
+
+		err = rtnl_get_link_hstats(skb, dev, prividx);
+		if (err == -ENODATA)
+			nla_nest_cancel(skb, attr);
+		else
+			nla_nest_end(skb, attr);
+
+		if (err && err != -ENODATA)
+			goto nla_put_failure;
+		*idxattr = 0;
+	}
+
 	nlmsg_end(skb, nlh);
 
 	return 0;
@@ -4946,6 +4964,9 @@ static size_t if_nlmsg_stats_size(const struct net_device *dev,
 		rcu_read_unlock();
 	}
 
+	if (stats_attr_valid(filter_mask, IFLA_STATS_LINK_HSTATS, 0))
+		size += rtnl_get_link_hstats_size(dev);
+
 	return size;
 }
 
-- 
2.19.2

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ