From: Nick Hainke <vincent@systemli.org>
Date: Mon, 7 Dec 2020 19:29:54 +0100
Subject: [PATCH] snmp6: add ipv6 statistics

ChangeLog: snmp6 plugin: Add plugin for parsing IPv6 statistics

We would like to have pure ipv6 interface statistics. To get them,
we parse the snmp6 interface.

Signed-off-by: Nick Hainke <vincent@systemli.org>
---
 Makefile.am          |   8 +++
 README               |   4 ++
 configure.ac         |   2 +
 src/collectd.conf.in |   6 ++
 src/snmp6.c          | 135 +++++++++++++++++++++++++++++++++++++++++++
 src/types.db         |   2 +
 6 files changed, 157 insertions(+)
 create mode 100644 src/snmp6.c

--- a/Makefile.am
+++ b/Makefile.am
@@ -1964,6 +1964,14 @@ TESTS += test_plugin_snmp_agent
 
 endif
 
+if BUILD_PLUGIN_SNMP6
+pkglib_LTLIBRARIES += snmp6.la
+snmp6_la_SOURCES = src/snmp6.c
+snmp6_la_CFLAGS = $(AM_CFLAGS)
+snmp6_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+snmp6_la_LIBADD = libignorelist.la
+endif # BUILD_PLUGIN_SNMP6
+
 if BUILD_PLUGIN_STATSD
 pkglib_LTLIBRARIES += statsd.la
 statsd_la_SOURCES = src/statsd.c
--- a/README
+++ b/README
@@ -422,6 +422,10 @@ Features
       network devices such as switches, routers, thermometers, rack monitoring
       servers, etc. See collectd-snmp(5).
 
+    - snmp6
+      Read values from SNMP6 (Simple Network Management Protocol). Supports pure
+      IPv6 interface statistics.
+
     - statsd
       Acts as a StatsD server, reading values sent over the network from StatsD
       clients and calculating rates and other aggregates out of these values.
--- a/configure.ac
+++ b/configure.ac
@@ -7162,6 +7162,7 @@ AC_PLUGIN([slurm],               [$with_
 AC_PLUGIN([smart],               [$plugin_smart],             [SMART statistics])
 AC_PLUGIN([snmp],                [$with_libnetsnmp],          [SNMP querying plugin])
 AC_PLUGIN([snmp_agent],          [$with_libnetsnmpagent],     [SNMP agent plugin])
+AC_PLUGIN([snmp6],               [yes],                       [IPv6 Interface traffic statistics via snmp6])
 AC_PLUGIN([statsd],              [yes],                       [StatsD plugin])
 AC_PLUGIN([swap],                [$plugin_swap],              [Swap usage statistics])
 AC_PLUGIN([synproxy],            [$plugin_synproxy],          [Synproxy stats plugin])
@@ -7611,6 +7612,7 @@ AC_MSG_RESULT([    slurm . . . . . . . .
 AC_MSG_RESULT([    smart . . . . . . . . $enable_smart])
 AC_MSG_RESULT([    snmp  . . . . . . . . $enable_snmp])
 AC_MSG_RESULT([    snmp_agent  . . . . . $enable_snmp_agent])
+AC_MSG_RESULT([    snmp6 . . . . . . . . $enable_snmp6])
 AC_MSG_RESULT([    statsd  . . . . . . . $enable_statsd])
 AC_MSG_RESULT([    swap  . . . . . . . . $enable_swap])
 AC_MSG_RESULT([    synproxy  . . . . . . $enable_synproxy])
--- a/src/collectd.conf.in
+++ b/src/collectd.conf.in
@@ -207,6 +207,7 @@
 #@BUILD_PLUGIN_SMART_TRUE@LoadPlugin smart
 #@BUILD_PLUGIN_SNMP_TRUE@LoadPlugin snmp
 #@BUILD_PLUGIN_SNMP_AGENT_TRUE@LoadPlugin snmp_agent
+#@BUILD_PLUGIN_SNMP6_TRUE@LoadPlugin snmp6
 #@BUILD_PLUGIN_STATSD_TRUE@LoadPlugin statsd
 #@BUILD_PLUGIN_SWAP_TRUE@LoadPlugin swap
 #@BUILD_PLUGIN_SYSEVENT_TRUE@LoadPlugin sysevent
@@ -1718,6 +1719,11 @@
 #  </Table>
 #</Plugin>
 
+#<Plugin snmp6>
+#	Interface "eth0"
+#	IgnoreSelected false
+#</Plugin>
+
 #<Plugin statsd>
 #  Host "::"
 #  Port "8125"
--- /dev/null
+++ b/src/snmp6.c
@@ -0,0 +1,135 @@
+/*
+  This Plugin is based opn the interface.c Plugin.
+*/
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <net/if.h>
+#include <sys/types.h>
+#include <ifaddrs.h>
+
+#include "plugin.h"
+#include "utils/cmds/putval.h"
+#include "utils/common/common.h"
+#include "utils/ignorelist/ignorelist.h"
+
+static const char *config_keys[] = {
+    "Interface",
+    "IgnoreSelected",
+};
+static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
+
+static ignorelist_t *ignorelist;
+
+static int snmp6_config(const char *key, const char *value) {
+  if (ignorelist == NULL)
+    ignorelist = ignorelist_create(/* invert = */ 1);
+
+  if (strcasecmp(key, "Interface") == 0) {
+    ignorelist_add(ignorelist, value);
+  } else if (strcasecmp(key, "IgnoreSelected") == 0) {
+    int invert = 1;
+    if (IS_TRUE(value))
+      invert = 0;
+    ignorelist_set_invert(ignorelist, invert);
+  }
+
+  return 0;
+}
+
+/* Copied from interface.c */
+static void snmp6_submit(const char *dev, const char *type, derive_t rx,
+                         derive_t tx) {
+  value_list_t vl = VALUE_LIST_INIT;
+  value_t values[] = {
+      {.derive = rx},
+      {.derive = tx},
+  };
+
+  vl.values = values;
+  vl.values_len = STATIC_ARRAY_SIZE(values);
+  sstrncpy(vl.plugin, "snmp6", sizeof(vl.plugin));
+  sstrncpy(vl.plugin_instance, dev, sizeof(vl.plugin_instance));
+  sstrncpy(vl.type, type, sizeof(vl.type));
+
+  plugin_dispatch_values(&vl);
+} /* void if_submit */
+
+int snmp_read(char *ifname) {
+  FILE *fh;
+  char buffer[1024];
+  char *fields[2];
+  int numfields;
+  int currline = 0;
+  derive_t data[76];
+  char procpath[1024];
+  int offset = 0;
+
+  if (ignorelist_match(ignorelist, ifname) != 0)
+    return 0;
+
+  if (strncmp("all", ifname, strlen("all")) == 0) {
+    snprintf(procpath, 1024, "/proc/net/snmp6");
+    offset = 1;
+  } else {
+    snprintf(procpath, 1024, "/proc/net/dev_snmp6/%s", ifname);
+  }
+
+  if ((fh = fopen(procpath, "r")) == NULL) {
+    WARNING("snmp6 plugin: try opening %s : fopen: %s", procpath, STRERRNO);
+    return -1;
+  }
+
+  while (fgets(buffer, 1024, fh) != NULL) {
+    numfields = strsplit(buffer, fields, 2);
+
+    if (numfields < 2)
+      return -1;
+
+    data[currline++] = atoll(fields[1]);
+  }
+
+  fclose(fh);
+
+  if (currline < 28) {
+    return -1;
+  }
+
+  snmp6_submit(ifname, "if_octets", data[23 - offset], data[24 - offset]);
+  snmp6_submit(ifname, "if_octets_mcast", data[25 - offset], data[26 - offset]);
+  snmp6_submit(ifname, "if_octets_bcast", data[27 - offset], data[28 - offset]);
+  return 0;
+}
+
+int read_all_interfaces(void) {
+#ifndef HAVE_IFADDRS_H
+  return -1;
+#else
+
+  // getifaddrs is not working all the time (e.g. wireguard interfaces)
+  // instead we use if_nameindex() syscall as suggested in:
+  // https://stackoverflow.com/a/45796495/8474618
+  struct if_nameindex *if_nidxs, *intf;
+
+  if_nidxs = if_nameindex();
+
+  if (if_nidxs != NULL) {
+    for (intf = if_nidxs; intf->if_index != 0 || intf->if_name != NULL; intf++) {
+      snmp_read(intf->if_name);
+    }
+    if_freenameindex(if_nidxs);
+  }
+
+  snmp_read("all");
+  return 0;
+#endif
+}
+
+void module_register(void) {
+  plugin_register_config("snmp6", snmp6_config, config_keys, config_keys_num);
+  plugin_register_read("snmp6", read_all_interfaces);
+} /* void module_register */
--- a/src/types.db
+++ b/src/types.db
@@ -132,6 +132,8 @@ if_dropped              rx:DERIVE:0:U, t
 if_errors               rx:DERIVE:0:U, tx:DERIVE:0:U
 if_multicast            value:DERIVE:0:U
 if_octets               rx:DERIVE:0:U, tx:DERIVE:0:U
+if_octets_mcast         rx:DERIVE:0:U, tx:DERIVE:0:U
+if_octets_bcast         rx:DERIVE:0:U, tx:DERIVE:0:U
 if_packets              rx:DERIVE:0:U, tx:DERIVE:0:U
 if_rx_dropped           value:DERIVE:0:U
 if_rx_errors            value:DERIVE:0:U
