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;
 }
 
