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-next>] [day] [month] [year] [list]
Date:   Thu,  4 Oct 2018 12:40:59 -0400
From:   Stephen Suryaputra <ssuryaextr@...il.com>
To:     netdev@...r.kernel.org
Cc:     Stephen Suryaputra <ssuryaextr@...il.com>
Subject: [PATCH net-next] IPv6 ifstats separation

Separate IPv6 ifstats into the ones that are hit on fast path and
the ones that aren't. The ones that are not can be removed as needed
using sysctls.

Signed-off-by: Stephen Suryaputra <ssuryaextr@...il.com>
---
 include/linux/ipv6.h      |   3 +
 include/net/if_inet6.h    |   3 +-
 include/net/ipv6.h        |  28 ++-
 include/net/snmp.h        |  22 +++
 include/uapi/linux/ipv6.h |   3 +
 include/uapi/linux/snmp.h |   3 +-
 net/ipv6/addrconf.c       | 380 +++++++++++++++++++++++++++++++++++---
 net/ipv6/addrconf_core.c  |   3 +-
 net/ipv6/proc.c           |  57 +++++-
 9 files changed, 462 insertions(+), 40 deletions(-)

diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
index 495e834c1367..c477960d57c2 100644
--- a/include/linux/ipv6.h
+++ b/include/linux/ipv6.h
@@ -74,6 +74,9 @@ struct ipv6_devconf {
 	__u32		addr_gen_mode;
 	__s32		disable_policy;
 	__s32           ndisc_tclass;
+	__s32		extended_ipstats;
+	__s32		icmpstats;
+	__s32		icmpmsgstats;
 
 	struct ctl_table_header *sysctl_header;
 };
diff --git a/include/net/if_inet6.h b/include/net/if_inet6.h
index d7578cf49c3a..62757829a992 100644
--- a/include/net/if_inet6.h
+++ b/include/net/if_inet6.h
@@ -158,7 +158,8 @@ struct ifacaddr6 {
 
 struct ipv6_devstat {
 	struct proc_dir_entry	*proc_dir_entry;
-	DEFINE_SNMP_STAT(struct ipstats_mib, ipv6);
+	DEFINE_SNMP_STAT(struct ipstats_mib_device_fast, ipv6dev_fast);
+	DEFINE_SNMP_STAT_ATOMIC(struct ipstats_mib_device, ipv6dev);
 	DEFINE_SNMP_STAT_ATOMIC(struct icmpv6_mib_device, icmpv6dev);
 	DEFINE_SNMP_STAT_ATOMIC(struct icmpv6msg_mib_device, icmpv6msgdev);
 };
diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index ff33f498c137..4064d88d7b9d 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -166,8 +166,12 @@ extern int sysctl_mld_qrv;
 #define _DEVINC(net, statname, mod, idev, field)			\
 ({									\
 	struct inet6_dev *_idev = (idev);				\
-	if (likely(_idev != NULL))					\
-		mod##SNMP_INC_STATS64((_idev)->stats.statname, (field));\
+	if (likely(_idev != NULL)) {					\
+		if (field < __IPSTATS_MIB_FAST_MAX)			\
+			mod##SNMP_INC_STATS64((_idev)->stats.statname##dev_fast, (field));	\
+		else if (likely((_idev)->stats.statname##dev != NULL))		\
+			SNMP_INC_STATS_ATOMIC_LONG((_idev)->stats.statname##dev, (field));	\
+	} \
 	mod##SNMP_INC_STATS64((net)->mib.statname##_statistics, (field));\
 })
 
@@ -175,7 +179,7 @@ extern int sysctl_mld_qrv;
 #define _DEVINCATOMIC(net, statname, mod, idev, field)			\
 ({									\
 	struct inet6_dev *_idev = (idev);				\
-	if (likely(_idev != NULL))					\
+	if (likely(_idev != NULL && (_idev)->stats.statname##dev != NULL))	\
 		SNMP_INC_STATS_ATOMIC_LONG((_idev)->stats.statname##dev, (field)); \
 	mod##SNMP_INC_STATS((net)->mib.statname##_statistics, (field));\
 })
@@ -184,7 +188,7 @@ extern int sysctl_mld_qrv;
 #define _DEVINC_ATOMIC_ATOMIC(net, statname, idev, field)		\
 ({									\
 	struct inet6_dev *_idev = (idev);				\
-	if (likely(_idev != NULL))					\
+	if (likely(_idev != NULL && (_idev)->stats.statname##dev != NULL))	\
 		SNMP_INC_STATS_ATOMIC_LONG((_idev)->stats.statname##dev, (field)); \
 	SNMP_INC_STATS_ATOMIC_LONG((net)->mib.statname##_statistics, (field));\
 })
@@ -192,16 +196,24 @@ extern int sysctl_mld_qrv;
 #define _DEVADD(net, statname, mod, idev, field, val)			\
 ({									\
 	struct inet6_dev *_idev = (idev);				\
-	if (likely(_idev != NULL))					\
-		mod##SNMP_ADD_STATS((_idev)->stats.statname, (field), (val)); \
+	if (likely(_idev != NULL)) {					\
+		if (field < __IPSTATS_MIB_FAST_MAX)			\
+			mod##SNMP_ADD_STATS((_idev)->stats.statname##dev_fast, (field), (val));	\
+		else if (likely((_idev)->stats.statname##dev != NULL))		\
+			SNMP_ADD_STATS_ATOMIC_LONG((_idev)->stats.statname##dev, (field), (val));	\
+	} \
 	mod##SNMP_ADD_STATS((net)->mib.statname##_statistics, (field), (val));\
 })
 
 #define _DEVUPD(net, statname, mod, idev, field, val)			\
 ({									\
 	struct inet6_dev *_idev = (idev);				\
-	if (likely(_idev != NULL))					\
-		mod##SNMP_UPD_PO_STATS((_idev)->stats.statname, field, (val)); \
+	if (likely(_idev != NULL)) {					\
+		if (field##PKTS < __IPSTATS_MIB_FAST_MAX)			\
+			mod##SNMP_UPD_PO_STATS((_idev)->stats.statname##dev_fast, field, (val)); \
+		else if (likely((_idev)->stats.statname##dev != NULL))		\
+			SNMP_UPD_PO_STATS_ATOMIC_LONG((_idev)->stats.statname##dev, field, (val));	\
+	} \
 	mod##SNMP_UPD_PO_STATS((net)->mib.statname##_statistics, field, (val));\
 })
 
diff --git a/include/net/snmp.h b/include/net/snmp.h
index c9228ad7ee91..0b85ccdc493d 100644
--- a/include/net/snmp.h
+++ b/include/net/snmp.h
@@ -53,12 +53,25 @@ struct snmp_mib {
 
 /* IPstats */
 #define IPSTATS_MIB_MAX	__IPSTATS_MIB_MAX
+#define IPSTATS_MIB_FAST_MAX	__IPSTATS_MIB_FAST_MAX
 struct ipstats_mib {
 	/* mibs[] must be first field of struct ipstats_mib */
 	u64		mibs[IPSTATS_MIB_MAX];
 	struct u64_stats_sync syncp;
 };
 
+/* Fast per device IPstats */
+struct ipstats_mib_device_fast {
+	/* mibs[] must be first field of struct ipstats_mib_device_fast */
+	u64		mibs[IPSTATS_MIB_FAST_MAX];
+	struct u64_stats_sync syncp;
+};
+
+/* Slow per device IPstats */
+struct ipstats_mib_device {
+	atomic_long_t	mibs[IPSTATS_MIB_MAX];
+};
+
 /* ICMP */
 #define ICMP_MIB_MAX	__ICMP_MIB_MAX
 struct icmp_mib {
@@ -140,6 +153,10 @@ struct linux_xfrm_mib {
 
 #define SNMP_ADD_STATS(mib, field, addend)	\
 			this_cpu_add(mib->mibs[field], addend)
+
+#define SNMP_ADD_STATS_ATOMIC_LONG(mib, field, addend)	\
+			atomic_long_add(addend, &mib->mibs[field])
+
 #define SNMP_UPD_PO_STATS(mib, basefield, addend)	\
 	do { \
 		__typeof__((mib->mibs) + 0) ptr = mib->mibs;	\
@@ -152,6 +169,11 @@ struct linux_xfrm_mib {
 		__this_cpu_inc(ptr[basefield##PKTS]);		\
 		__this_cpu_add(ptr[basefield##OCTETS], addend);	\
 	} while (0)
+#define SNMP_UPD_PO_STATS_ATOMIC_LONG(mib, basefield, addend)	\
+	do { \
+		atomic_long_inc(&mib->mibs[basefield##PKTS]);		\
+		atomic_long_add(addend, &mib->mibs[basefield##OCTETS]);	\
+	} while (0)
 
 
 #if BITS_PER_LONG==32
diff --git a/include/uapi/linux/ipv6.h b/include/uapi/linux/ipv6.h
index 9c0f4a92bcff..5864f4c8afbd 100644
--- a/include/uapi/linux/ipv6.h
+++ b/include/uapi/linux/ipv6.h
@@ -187,6 +187,9 @@ enum {
 	DEVCONF_DISABLE_POLICY,
 	DEVCONF_ACCEPT_RA_RT_INFO_MIN_PLEN,
 	DEVCONF_NDISC_TCLASS,
+	DEVCONF_EXTENDED_IPSTATS,
+	DEVCONF_ICMPSTATS,
+	DEVCONF_ICMPMSGSTATS,
 	DEVCONF_MAX
 };
 
diff --git a/include/uapi/linux/snmp.h b/include/uapi/linux/snmp.h
index f80135e5feaa..eb689ecf21a6 100644
--- a/include/uapi/linux/snmp.h
+++ b/include/uapi/linux/snmp.h
@@ -26,8 +26,9 @@ enum
 	IPSTATS_MIB_OUTFORWDATAGRAMS,		/* OutForwDatagrams */
 	IPSTATS_MIB_OUTPKTS,			/* OutRequests */
 	IPSTATS_MIB_OUTOCTETS,			/* OutOctets */
+	__IPSTATS_MIB_FAST_MAX,
 /* other fields */
-	IPSTATS_MIB_INHDRERRORS,		/* InHdrErrors */
+	IPSTATS_MIB_INHDRERRORS = __IPSTATS_MIB_FAST_MAX, /* InHdrErrors */
 	IPSTATS_MIB_INTOOBIGERRORS,		/* InTooBigErrors */
 	IPSTATS_MIB_INNOROUTES,			/* InNoRoutes */
 	IPSTATS_MIB_INADDRERRORS,		/* InAddrErrors */
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index a9a317322388..d8c15c713224 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -239,6 +239,9 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = {
 	.enhanced_dad           = 1,
 	.addr_gen_mode		= IN6_ADDR_GEN_MODE_EUI64,
 	.disable_policy		= 0,
+	.extended_ipstats	= 1,
+	.icmpstats		= 1,
+	.icmpmsgstats		= 1,
 };
 
 static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
@@ -293,6 +296,9 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
 	.enhanced_dad           = 1,
 	.addr_gen_mode		= IN6_ADDR_GEN_MODE_EUI64,
 	.disable_policy		= 0,
+	.extended_ipstats	= 1,
+	.icmpstats		= 1,
+	.icmpmsgstats		= 1,
 };
 
 /* Check if link is ready: is it up and is a valid qdisc available */
@@ -333,33 +339,45 @@ static int snmp6_alloc_dev(struct inet6_dev *idev)
 {
 	int i;
 
-	idev->stats.ipv6 = alloc_percpu(struct ipstats_mib);
-	if (!idev->stats.ipv6)
-		goto err_ip;
+	idev->stats.ipv6dev_fast = alloc_percpu(struct ipstats_mib_device_fast);
+	if (!idev->stats.ipv6dev_fast)
+		goto err_ip_fast;
 
 	for_each_possible_cpu(i) {
-		struct ipstats_mib *addrconf_stats;
-		addrconf_stats = per_cpu_ptr(idev->stats.ipv6, i);
+		struct ipstats_mib_device_fast *addrconf_stats;
+		addrconf_stats = per_cpu_ptr(idev->stats.ipv6dev_fast, i);
 		u64_stats_init(&addrconf_stats->syncp);
 	}
 
 
-	idev->stats.icmpv6dev = kzalloc(sizeof(struct icmpv6_mib_device),
-					GFP_KERNEL);
-	if (!idev->stats.icmpv6dev)
-		goto err_icmp;
-	idev->stats.icmpv6msgdev = kzalloc(sizeof(struct icmpv6msg_mib_device),
-					   GFP_KERNEL);
-	if (!idev->stats.icmpv6msgdev)
-		goto err_icmpmsg;
+	if (idev->cnf.extended_ipstats) {
+		idev->stats.ipv6dev = kzalloc(sizeof(struct ipstats_mib_device),
+					      GFP_KERNEL);
+		if (!idev->stats.ipv6dev)
+			goto err_ip;
+	}
+	if (idev->cnf.icmpstats) {
+		idev->stats.icmpv6dev = kzalloc(sizeof(struct icmpv6_mib_device),
+						GFP_KERNEL);
+		if (!idev->stats.icmpv6dev)
+			goto err_icmp;
+	}
+	if (idev->cnf.icmpmsgstats) {
+		idev->stats.icmpv6msgdev = kzalloc(sizeof(struct icmpv6msg_mib_device),
+						   GFP_KERNEL);
+		if (!idev->stats.icmpv6msgdev)
+			goto err_icmpmsg;
+	}
 
 	return 0;
 
 err_icmpmsg:
 	kfree(idev->stats.icmpv6dev);
 err_icmp:
-	free_percpu(idev->stats.ipv6);
+	kfree(idev->stats.ipv6dev);
 err_ip:
+	free_percpu(idev->stats.ipv6dev_fast);
+err_ip_fast:
 	return -ENOMEM;
 }
 
@@ -5263,6 +5281,9 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf,
 	array[DEVCONF_ADDR_GEN_MODE] = cnf->addr_gen_mode;
 	array[DEVCONF_DISABLE_POLICY] = cnf->disable_policy;
 	array[DEVCONF_NDISC_TCLASS] = cnf->ndisc_tclass;
+	array[DEVCONF_EXTENDED_IPSTATS] = cnf->extended_ipstats;
+	array[DEVCONF_ICMPSTATS] = cnf->icmpstats;
+	array[DEVCONF_ICMPMSGSTATS] = cnf->icmpmsgstats;
 }
 
 static inline size_t inet6_ifla6_size(void)
@@ -5297,14 +5318,16 @@ static inline void __snmp6_fill_statsdev(u64 *stats, atomic_long_t *mib,
 
 	/* Use put_unaligned() because stats may not be aligned for u64. */
 	put_unaligned(ICMP6_MIB_MAX, &stats[0]);
-	for (i = 1; i < ICMP6_MIB_MAX; i++)
-		put_unaligned(atomic_long_read(&mib[i]), &stats[i]);
+	if (mib) {
+		for (i = 1; i < ICMP6_MIB_MAX; i++)
+			put_unaligned(atomic_long_read(&mib[i]), &stats[i]);
+	}
 
 	memset(&stats[ICMP6_MIB_MAX], 0, pad);
 }
 
-static inline void __snmp6_fill_stats64(u64 *stats, void __percpu *mib,
-					int bytes, size_t syncpoff)
+static inline void __snmp6_fill_stats64(u64 *stats, void __percpu *mib_fast,
+					atomic_long_t *mib, int bytes, size_t syncpoff)
 {
 	int i, c;
 	u64 buff[IPSTATS_MIB_MAX];
@@ -5316,10 +5339,13 @@ static inline void __snmp6_fill_stats64(u64 *stats, void __percpu *mib,
 	buff[0] = IPSTATS_MIB_MAX;
 
 	for_each_possible_cpu(c) {
-		for (i = 1; i < IPSTATS_MIB_MAX; i++)
-			buff[i] += snmp_get_cpu_field64(mib, c, i, syncpoff);
+		for (i = 1; i < IPSTATS_MIB_FAST_MAX; i++)
+			buff[i] += snmp_get_cpu_field64(mib_fast, c, i, syncpoff);
+	}
+	if (mib) {
+		for (; i < IPSTATS_MIB_MAX; i++)
+			buff[i] = atomic_long_read(&mib[i]);
 	}
-
 	memcpy(stats, buff, IPSTATS_MIB_MAX * sizeof(u64));
 	memset(&stats[IPSTATS_MIB_MAX], 0, pad);
 }
@@ -5329,11 +5355,14 @@ static void snmp6_fill_stats(u64 *stats, struct inet6_dev *idev, int attrtype,
 {
 	switch (attrtype) {
 	case IFLA_INET6_STATS:
-		__snmp6_fill_stats64(stats, idev->stats.ipv6, bytes,
-				     offsetof(struct ipstats_mib, syncp));
+		__snmp6_fill_stats64(stats, idev->stats.ipv6dev_fast,
+				     idev->stats.ipv6dev ? idev->stats.ipv6dev->mibs : NULL,
+				     bytes, offsetof(struct ipstats_mib_device_fast, syncp));
 		break;
 	case IFLA_INET6_ICMP6STATS:
-		__snmp6_fill_statsdev(stats, idev->stats.icmpv6dev->mibs, bytes);
+		__snmp6_fill_statsdev(stats,
+				      idev->stats.icmpv6dev ? idev->stats.icmpv6dev->mibs : NULL,
+				      bytes);
 		break;
 	}
 }
@@ -6205,6 +6234,288 @@ int addrconf_sysctl_disable_policy(struct ctl_table *ctl, int write,
 	return ret;
 }
 
+static
+void free_ipv6dev_rcu(struct rcu_head *head)
+{
+	struct inet6_dev *idev = container_of(head, struct inet6_dev, rcu);
+
+	kfree(idev->stats.ipv6dev);
+	idev->stats.ipv6dev = NULL;
+}
+
+static
+int addrconf_extended_ipstats(struct ctl_table *ctl, int *valp, int val)
+{
+	struct inet6_dev *idev;
+	struct net *net;
+
+	if (!rtnl_trylock())
+		return restart_syscall();
+
+	net = (struct net *)ctl->extra2;
+	if (valp == &net->ipv6.devconf_dflt->extended_ipstats) {
+		*valp = val;
+		rtnl_unlock();
+		return 0;
+	}
+
+	if (valp == &net->ipv6.devconf_all->extended_ipstats)  {
+		struct net_device *dev;
+		bool undo = 0;
+
+loop:
+		for_each_netdev(net, dev) {
+			idev = __in6_dev_get(dev);
+			if (!idev)
+				continue;
+			if (val && !idev->stats.ipv6dev) {
+				idev->stats.ipv6dev = kzalloc(sizeof(struct ipstats_mib_device),
+							      GFP_KERNEL);
+				if (!idev->stats.ipv6dev) {
+					undo = 1;
+					val = 0;
+					goto loop;
+				}
+			} else if (!val && idev->stats.ipv6dev) {
+				call_rcu(&idev->rcu, free_ipv6dev_rcu);
+			}
+		}
+		if (undo) {
+			rtnl_unlock();
+			return -ENOMEM;
+		}
+	} else {
+		idev = (struct inet6_dev *)ctl->extra1;
+		if (val && !idev->stats.ipv6dev) {
+			idev->stats.ipv6dev = kzalloc(sizeof(struct ipstats_mib_device),
+						      GFP_KERNEL);
+			if (!idev->stats.ipv6dev) {
+				rtnl_unlock();
+				return -ENOMEM;
+			}
+		} else if (!val && !idev->stats.ipv6dev) {
+			call_rcu(&idev->rcu, free_ipv6dev_rcu);
+		}
+	}
+
+	*valp = val;
+
+	rtnl_unlock();
+	return 0;
+}
+
+static
+int addrconf_sysctl_extended_ipstats(struct ctl_table *ctl, int write,
+				     void __user *buffer, size_t *lenp,
+				     loff_t *ppos)
+{
+	int *valp = ctl->data;
+	int val = *valp;
+	loff_t pos = *ppos;
+	struct ctl_table lctl;
+	int ret;
+
+	lctl = *ctl;
+	lctl.data = &val;
+	ret = proc_dointvec(&lctl, write, buffer, lenp, ppos);
+
+	if (write && (*valp != val))
+		ret = addrconf_extended_ipstats(ctl, valp, val);
+
+	if (ret)
+		*ppos = pos;
+
+	return ret;
+}
+
+static
+void free_icmpv6dev_rcu(struct rcu_head *head)
+{
+	struct inet6_dev *idev = container_of(head, struct inet6_dev, rcu);
+
+	kfree(idev->stats.icmpv6dev);
+	idev->stats.icmpv6dev = NULL;
+}
+
+static
+int addrconf_icmpstats(struct ctl_table *ctl, int *valp, int val)
+{
+	struct inet6_dev *idev;
+	struct net *net;
+
+	if (!rtnl_trylock())
+		return restart_syscall();
+
+	net = (struct net *)ctl->extra2;
+	if (valp == &net->ipv6.devconf_dflt->icmpstats) {
+		*valp = val;
+		rtnl_unlock();
+		return 0;
+	}
+
+	if (valp == &net->ipv6.devconf_all->icmpstats)  {
+		struct net_device *dev;
+		bool undo = 0;
+
+loop:
+		for_each_netdev(net, dev) {
+			idev = __in6_dev_get(dev);
+			if (!idev)
+				continue;
+			if (val && !idev->stats.icmpv6dev) {
+				idev->stats.icmpv6dev = kzalloc(sizeof(struct icmpv6_mib_device),
+								GFP_KERNEL);
+				if (!idev->stats.icmpv6dev) {
+					undo = 1;
+					val = 0;
+					goto loop;
+				}
+			} else if (!val && idev->stats.icmpv6dev) {
+				call_rcu(&idev->rcu, free_icmpv6dev_rcu);
+			}
+		}
+		if (undo) {
+			rtnl_unlock();
+			return -ENOMEM;
+		}
+	} else {
+		idev = (struct inet6_dev *)ctl->extra1;
+		if (val && !idev->stats.icmpv6dev) {
+			idev->stats.icmpv6dev = kzalloc(sizeof(struct icmpv6_mib_device),
+							GFP_KERNEL);
+			if (!idev->stats.icmpv6dev) {
+				rtnl_unlock();
+				return -ENOMEM;
+			}
+		} else if (!val && idev->stats.icmpv6dev) {
+			call_rcu(&idev->rcu, free_icmpv6dev_rcu);
+		}
+	}
+
+	*valp = val;
+
+	rtnl_unlock();
+	return 0;
+}
+
+static
+int addrconf_sysctl_icmpstats(struct ctl_table *ctl, int write,
+			      void __user *buffer, size_t *lenp,
+			      loff_t *ppos)
+{
+	int *valp = ctl->data;
+	int val = *valp;
+	loff_t pos = *ppos;
+	struct ctl_table lctl;
+	int ret;
+
+	lctl = *ctl;
+	lctl.data = &val;
+	ret = proc_dointvec(&lctl, write, buffer, lenp, ppos);
+
+	if (write && (*valp != val))
+		ret = addrconf_icmpstats(ctl, valp, val);
+
+	if (ret)
+		*ppos = pos;
+
+	return ret;
+}
+
+static
+void free_icmpv6msgdev_rcu(struct rcu_head *head)
+{
+	struct inet6_dev *idev = container_of(head, struct inet6_dev, rcu);
+
+	kfree(idev->stats.icmpv6msgdev);
+	idev->stats.icmpv6msgdev = NULL;
+}
+
+static
+int addrconf_icmpmsgstats(struct ctl_table *ctl, int *valp, int val)
+{
+	struct inet6_dev *idev;
+	struct net *net;
+
+	if (!rtnl_trylock())
+		return restart_syscall();
+
+	net = (struct net *)ctl->extra2;
+	if (valp == &net->ipv6.devconf_dflt->icmpmsgstats) {
+		*valp = val;
+		rtnl_unlock();
+		return 0;
+	}
+
+	if (valp == &net->ipv6.devconf_all->icmpmsgstats)  {
+		struct net_device *dev;
+		bool undo = 0;
+
+loop:
+		for_each_netdev(net, dev) {
+			idev = __in6_dev_get(dev);
+			if (!idev)
+				continue;
+			if (val && !idev->stats.icmpv6msgdev) {
+				idev->stats.icmpv6msgdev = kzalloc(sizeof(struct icmpv6msg_mib_device),
+								   GFP_KERNEL);
+				if (!idev->stats.icmpv6msgdev) {
+					undo = 1;
+					val = 0;
+					goto loop;
+				}
+			} else if (!val && idev->stats.icmpv6msgdev) {
+				call_rcu(&idev->rcu, free_icmpv6msgdev_rcu);
+			}
+		}
+		if (undo) {
+			rtnl_unlock();
+			return -ENOMEM;
+		}
+	} else {
+		idev = (struct inet6_dev *)ctl->extra1;
+		if (val && !idev->stats.icmpv6msgdev) {
+			idev->stats.icmpv6msgdev = kzalloc(sizeof(struct icmpv6msg_mib_device),
+							   GFP_KERNEL);
+			if (!idev->stats.icmpv6msgdev) {
+				rtnl_unlock();
+				return -ENOMEM;
+			}
+		} else if (!val && idev->stats.icmpv6msgdev) {
+			call_rcu(&idev->rcu, free_icmpv6msgdev_rcu);
+		}
+	}
+
+	*valp = val;
+
+	rtnl_unlock();
+	return 0;
+}
+
+static
+int addrconf_sysctl_icmpmsgstats(struct ctl_table *ctl, int write,
+				 void __user *buffer, size_t *lenp,
+				 loff_t *ppos)
+{
+	int *valp = ctl->data;
+	int val = *valp;
+	loff_t pos = *ppos;
+	struct ctl_table lctl;
+	int ret;
+
+	lctl = *ctl;
+	lctl.data = &val;
+	ret = proc_dointvec(&lctl, write, buffer, lenp, ppos);
+
+	if (write && (*valp != val))
+		ret = addrconf_icmpmsgstats(ctl, valp, val);
+
+	if (ret)
+		*ppos = pos;
+
+	return ret;
+}
+
 static int minus_one = -1;
 static const int zero = 0;
 static const int one = 1;
@@ -6586,6 +6897,27 @@ static const struct ctl_table addrconf_sysctl[] = {
 		.extra1		= (void *)&zero,
 		.extra2		= (void *)&two_five_five,
 	},
+	{
+		.procname	= "extended_ipstats",
+		.data		= &ipv6_devconf.extended_ipstats,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= addrconf_sysctl_extended_ipstats,
+	},
+	{
+		.procname	= "icmpstats",
+		.data		= &ipv6_devconf.icmpstats,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= addrconf_sysctl_icmpstats,
+	},
+	{
+		.procname	= "icmpmsgstats",
+		.data		= &ipv6_devconf.icmpmsgstats,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= addrconf_sysctl_icmpmsgstats,
+	},
 	{
 		/* sentinel */
 	}
diff --git a/net/ipv6/addrconf_core.c b/net/ipv6/addrconf_core.c
index 5cd0029d930e..f143d7e2264c 100644
--- a/net/ipv6/addrconf_core.c
+++ b/net/ipv6/addrconf_core.c
@@ -198,7 +198,8 @@ static void snmp6_free_dev(struct inet6_dev *idev)
 {
 	kfree(idev->stats.icmpv6msgdev);
 	kfree(idev->stats.icmpv6dev);
-	free_percpu(idev->stats.ipv6);
+	kfree(idev->stats.ipv6dev);
+	free_percpu(idev->stats.ipv6dev_fast);
 }
 
 static void in6_dev_finish_destroy_rcu(struct rcu_head *head)
diff --git a/net/ipv6/proc.c b/net/ipv6/proc.c
index 2356b4af7309..c641c05af1b3 100644
--- a/net/ipv6/proc.c
+++ b/net/ipv6/proc.c
@@ -91,6 +91,47 @@ static const struct snmp_mib snmp6_ipstats_list[] = {
 	SNMP_MIB_SENTINEL
 };
 
+static const struct snmp_mib snmp6_ipstats_device_fast_list[] = {
+	SNMP_MIB_ITEM("Ip6InReceives", IPSTATS_MIB_INPKTS),
+	SNMP_MIB_ITEM("Ip6InOctets", IPSTATS_MIB_INOCTETS),
+	SNMP_MIB_ITEM("Ip6InDelivers", IPSTATS_MIB_INDELIVERS),
+	SNMP_MIB_ITEM("Ip6OutForwDatagrams", IPSTATS_MIB_OUTFORWDATAGRAMS),
+	SNMP_MIB_ITEM("Ip6OutRequests", IPSTATS_MIB_OUTPKTS),
+	SNMP_MIB_ITEM("Ip6OutOctets", IPSTATS_MIB_OUTOCTETS),
+	SNMP_MIB_SENTINEL
+};
+
+static const struct snmp_mib snmp6_ipstats_device_list[] = {
+	SNMP_MIB_ITEM("Ip6InHdrErrors", IPSTATS_MIB_INHDRERRORS),
+	SNMP_MIB_ITEM("Ip6InTooBigErrors", IPSTATS_MIB_INTOOBIGERRORS),
+	SNMP_MIB_ITEM("Ip6InNoRoutes", IPSTATS_MIB_INNOROUTES),
+	SNMP_MIB_ITEM("Ip6InAddrErrors", IPSTATS_MIB_INADDRERRORS),
+	SNMP_MIB_ITEM("Ip6InUnknownProtos", IPSTATS_MIB_INUNKNOWNPROTOS),
+	SNMP_MIB_ITEM("Ip6InTruncatedPkts", IPSTATS_MIB_INTRUNCATEDPKTS),
+	SNMP_MIB_ITEM("Ip6InDiscards", IPSTATS_MIB_INDISCARDS),
+	SNMP_MIB_ITEM("Ip6OutDiscards", IPSTATS_MIB_OUTDISCARDS),
+	SNMP_MIB_ITEM("Ip6OutNoRoutes", IPSTATS_MIB_OUTNOROUTES),
+	SNMP_MIB_ITEM("Ip6ReasmTimeout", IPSTATS_MIB_REASMTIMEOUT),
+	SNMP_MIB_ITEM("Ip6ReasmReqds", IPSTATS_MIB_REASMREQDS),
+	SNMP_MIB_ITEM("Ip6ReasmOKs", IPSTATS_MIB_REASMOKS),
+	SNMP_MIB_ITEM("Ip6ReasmFails", IPSTATS_MIB_REASMFAILS),
+	SNMP_MIB_ITEM("Ip6FragOKs", IPSTATS_MIB_FRAGOKS),
+	SNMP_MIB_ITEM("Ip6FragFails", IPSTATS_MIB_FRAGFAILS),
+	SNMP_MIB_ITEM("Ip6FragCreates", IPSTATS_MIB_FRAGCREATES),
+	SNMP_MIB_ITEM("Ip6InMcastPkts", IPSTATS_MIB_INMCASTPKTS),
+	SNMP_MIB_ITEM("Ip6OutMcastPkts", IPSTATS_MIB_OUTMCASTPKTS),
+	SNMP_MIB_ITEM("Ip6InMcastOctets", IPSTATS_MIB_INMCASTOCTETS),
+	SNMP_MIB_ITEM("Ip6OutMcastOctets", IPSTATS_MIB_OUTMCASTOCTETS),
+	SNMP_MIB_ITEM("Ip6InBcastOctets", IPSTATS_MIB_INBCASTOCTETS),
+	SNMP_MIB_ITEM("Ip6OutBcastOctets", IPSTATS_MIB_OUTBCASTOCTETS),
+	/* IPSTATS_MIB_CSUMERRORS is not relevant in IPv6 (no checksum) */
+	SNMP_MIB_ITEM("Ip6InNoECTPkts", IPSTATS_MIB_NOECTPKTS),
+	SNMP_MIB_ITEM("Ip6InECT1Pkts", IPSTATS_MIB_ECT1PKTS),
+	SNMP_MIB_ITEM("Ip6InECT0Pkts", IPSTATS_MIB_ECT0PKTS),
+	SNMP_MIB_ITEM("Ip6InCEPkts", IPSTATS_MIB_CEPKTS),
+	SNMP_MIB_SENTINEL
+};
+
 static const struct snmp_mib snmp6_icmp6_list[] = {
 /* icmpv6 mib according to RFC 2466 */
 	SNMP_MIB_ITEM("Icmp6InMsgs", ICMP6_MIB_INMSGS),
@@ -235,11 +276,17 @@ static int snmp6_dev_seq_show(struct seq_file *seq, void *v)
 	struct inet6_dev *idev = (struct inet6_dev *)seq->private;
 
 	seq_printf(seq, "%-32s\t%u\n", "ifIndex", idev->dev->ifindex);
-	snmp6_seq_show_item64(seq, idev->stats.ipv6,
-			    snmp6_ipstats_list, offsetof(struct ipstats_mib, syncp));
-	snmp6_seq_show_item(seq, NULL, idev->stats.icmpv6dev->mibs,
-			    snmp6_icmp6_list);
-	snmp6_seq_show_icmpv6msg(seq, idev->stats.icmpv6msgdev->mibs);
+	snmp6_seq_show_item64(seq, idev->stats.ipv6dev_fast,
+			      snmp6_ipstats_device_fast_list,
+			      offsetof(struct ipstats_mib_device_fast, syncp));
+	if (idev->stats.ipv6dev)
+		snmp6_seq_show_item(seq, NULL, idev->stats.ipv6dev->mibs,
+				    snmp6_ipstats_device_list);
+	if (idev->stats.icmpv6dev)
+		snmp6_seq_show_item(seq, NULL, idev->stats.icmpv6dev->mibs,
+				    snmp6_icmp6_list);
+	if (idev->stats.icmpv6msgdev)
+		snmp6_seq_show_icmpv6msg(seq, idev->stats.icmpv6msgdev->mibs);
 	return 0;
 }
 
-- 
2.17.1

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ