From patchwork Thu Oct 4 19:08:28 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Suryaputra X-Patchwork-Id: 979121 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="TR86xn0P"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42R2VJ6pG8z9s9m for ; Fri, 5 Oct 2018 05:08:40 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727504AbeJECDO (ORCPT ); Thu, 4 Oct 2018 22:03:14 -0400 Received: from mail-qt1-f195.google.com ([209.85.160.195]:37699 "EHLO mail-qt1-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727293AbeJECDO (ORCPT ); Thu, 4 Oct 2018 22:03:14 -0400 Received: by mail-qt1-f195.google.com with SMTP id d14-v6so2534961qto.4 for ; Thu, 04 Oct 2018 12:08:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id; bh=7q+zrWWCn0Zl25iaF/zvmhzhTQo+xyz6dy7IbZR+0yQ=; b=TR86xn0PMTgqojivnDKueLRz4BdqPtkmy7iCbZHy3VD7ZrHUH/GHlx2XwKroIT2Sm1 RsiYJ6RIUS9JK9SFHuaqRWHR2bJvKOykPIbaObDwOgoxKQY4irWbCQOg8QgbCncudNrM 6RGv0Nh1BF+RZVeO0dHJuNdtpinQew069+BXyMkRpsEGjdIB6ylz0bQwJkwbFLyBuN6Z mXSYy8E11FrqroRd6IUjERrkuFWbQ1x6wkyjpUUM0mbm3TRcmOCy4SKdmxEKWBftqv1g xTGTR50QbGUUvZhoKz4JztMwfHyR0/FzG02OpEPKc8BLcFBwrWCPz4V7VO49RvPwqjF2 PQmg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=7q+zrWWCn0Zl25iaF/zvmhzhTQo+xyz6dy7IbZR+0yQ=; b=Ch1ydeS+5HMOQzEzX1a+alk7cSQHA41Mn6oMgQaJxlPeI/fLuIc2tiNLWA4PNgfseE HqzF7xAzxIYW0Hii+6i9uKFeAEEnXtgBCDa/hu+6TAKiCPGscEXkvXq+XlfEtjB0ORNM JRW+BX8tHalImvqBNwDKSSXV8XlRbUVAD5uBqtjKhoWuqCKpURAeE8lbz5SZOdoiYi8Y E+Qo4dKNCeqnBvMJqclpENrMR1/wMiuxPNhN4IdzJOnKiu9+Gpwi5SO0o/KbILZDmm17 HGqkbWM2J5oFwF+F3KGGkqXY0+rh/bGJH+Jv2LrjPRiLmg8ZRENRM4EFl94N+ZaFKHCW 7nag== X-Gm-Message-State: ABuFfoi232jrRG5jeogy3FRwFfj9QUghbl66wSD/wAgsf9zUctfskJ/K OPSTpf8cH76wKgOTeL/NXNsDl64= X-Google-Smtp-Source: ACcGV618L+RPtpVuaRX5SqE8OjfzcITcbQw9KsRQKVe1ExcCiA+xIYkbgIU5iYcmqgemkRbO7hY3OQ== X-Received: by 2002:a0c:9e2a:: with SMTP id p42-v6mr6509248qve.54.1538680115399; Thu, 04 Oct 2018 12:08:35 -0700 (PDT) Received: from ubuntu.extremenetworks.com ([12.38.14.10]) by smtp.gmail.com with ESMTPSA id 76-v6sm3940849qkc.41.2018.10.04.12.08.34 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 04 Oct 2018 12:08:34 -0700 (PDT) From: Stephen Suryaputra To: netdev@vger.kernel.org Cc: Stephen Suryaputra Subject: [PATCH net-next,v2] IPv6 ifstats separation Date: Thu, 4 Oct 2018 15:08:28 -0400 Message-Id: <20181004190828.26536-1-ssuryaextr@gmail.com> X-Mailer: git-send-email 2.17.1 Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org 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. The separation minimizes percpu data. It reduces from 288 bytes per cpu to 56 bytes. When the other stats are disabled, then it adds 4144 bytes saving per netdev. The stats that are hit on fast path are percpu and the existing macro that increments them is added with a constant check that should be optimized by the preprocessor. This is done based on some feedbacks in the past to make IPv6 stats optional. It doesn't fully accomplish that but it enables all but 6 counters be optional. Those optional counters however are still enabled by default to preserve the current behavior. Changes from v1: - More elaborate changelog (per Eric Dumazet) Signed-off-by: Stephen Suryaputra --- 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; }