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 <ssuryae...@gmail.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

Reply via email to