From: Josef Moellers
Subject: Retrieve error counters for the EtherLike-MIB
References: bnc#629073
Patch-upstream: r18258, r18267

Retrieve error counters for the EtherLike-MIB.

Signed-off-by: Leonardo Chiquitto <lchiquitto@suse.de>

---
 agent/mibgroup/etherlike-mib/dot3StatsTable/dot3StatsTable_data_access.c |    2 
 agent/mibgroup/etherlike-mib/data_access/dot3stats_linux.c               |  530 +++++++++-
 2 files changed, 499 insertions(+), 33 deletions(-)

Index: agent/mibgroup/etherlike-mib/dot3StatsTable/dot3StatsTable_data_access.c
===================================================================
--- agent/mibgroup/etherlike-mib/dot3StatsTable/dot3StatsTable_data_access.c.orig
+++ agent/mibgroup/etherlike-mib/dot3StatsTable/dot3StatsTable_data_access.c
@@ -336,6 +336,8 @@ dot3StatsTable_container_load(netsnmp_co
 
 	interface_sysclassnet_dot3stats_get(rowreq_ctx, p->name);
 
+	interface_dot3stats_get_errorcounters(rowreq_ctx, p->name);
+
         /*
          * insert into table container
          */
Index: agent/mibgroup/etherlike-mib/data_access/dot3stats_linux.c
===================================================================
--- agent/mibgroup/etherlike-mib/data_access/dot3stats_linux.c.orig
+++ agent/mibgroup/etherlike-mib/data_access/dot3stats_linux.c
@@ -135,6 +135,503 @@ dot3stats_interface_ioctl_ifindex_get (i
 #endif /* SIOCGIFINDEX */
 }
 
+# if HAVE_LINUX_RTNETLINK_H /* { NETLINK */
+/*
+ * The following code is based upon code I got from Stephen Hemminger
+ */
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_link.h>
+#include <linux/if_addr.h>
+#include <linux/neighbour.h>
+#include <errno.h>
+
+struct rtnl_handle {
+    int                     fd;
+    struct sockaddr_nl      local;
+    struct sockaddr_nl      peer;
+    __u32                   seq;
+    __u32                   dump;
+};
+
+struct ifstat_ent {
+    struct ifstat_ent *next;
+    const char *name;
+    int ifindex;
+    struct rtnl_link_stats stats;
+};
+
+typedef int (*rtnl_filter_t)(const struct sockaddr_nl *, struct nlmsghdr *n, void *);
+
+struct rtnl_dump_filter_arg
+{
+    rtnl_filter_t filter;
+    void *arg1;
+    rtnl_filter_t junk;
+    void *arg2;
+};
+
+static struct ifstat_ent *kern_db;
+static const int rcvbuf_size = 1024 * 1024;
+
+static int
+rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, int protocol)
+{
+    socklen_t addr_len;
+    int sndbuf = 32768;
+
+    memset(rth, 0, sizeof(*rth));
+
+    rth->fd = socket(AF_NETLINK, SOCK_RAW, protocol);
+    if (rth->fd < 0) {
+        snmp_log(LOG_ERR, "Cannot open netlink socket");
+        return -1;
+    }
+
+    if (setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf)) < 0) {
+        snmp_log(LOG_ERR, "SO_SNDBUF");
+        return -1;
+    }
+
+    if (setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf_size,sizeof(rcvbuf_size)) < 0) {
+        snmp_log(LOG_ERR, "SO_RCVBUF");
+        return -1;
+    }
+
+    memset(&rth->local, 0, sizeof(rth->local));
+    rth->local.nl_family = AF_NETLINK;
+    rth->local.nl_groups = subscriptions;
+
+    if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)) < 0) {
+        snmp_log(LOG_ERR, "Cannot bind netlink socket");
+        return -1;
+    }
+    addr_len = sizeof(rth->local);
+    if (getsockname(rth->fd, (struct sockaddr*)&rth->local, &addr_len) < 0) {
+        snmp_log(LOG_ERR, "Cannot getsockname");
+        return -1;
+    }
+    if (addr_len != sizeof(rth->local)) {
+        snmp_log(LOG_ERR, "Wrong address length %d\n", addr_len);
+        return -1;
+    }
+    if (rth->local.nl_family != AF_NETLINK) {
+        snmp_log(LOG_ERR, "Wrong address family %d\n", rth->local.nl_family);
+        return -1;
+    }
+    rth->seq = time(NULL);
+    return 0;
+}
+
+static int
+rtnl_open(struct rtnl_handle *rth, unsigned subscriptions)
+{
+    return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);
+}
+
+static void
+rtnl_close(struct rtnl_handle *rth)
+{
+    if (rth->fd != -1)
+        close(rth->fd);
+    rth->fd = -1;
+
+    return;
+}
+
+static int
+rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type)
+{
+    struct {
+        struct nlmsghdr nlh;
+        struct rtgenmsg g;
+    } req;
+
+    memset(&req, 0, sizeof(req));
+    req.nlh.nlmsg_len = sizeof(req);
+    req.nlh.nlmsg_type = type;
+    req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
+    req.nlh.nlmsg_pid = 0;
+    req.nlh.nlmsg_seq = rth->dump = ++rth->seq;
+    req.g.rtgen_family = family;
+
+    return send(rth->fd, (void*)&req, sizeof(req), 0);
+}
+
+static int
+parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
+{
+    memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
+    while (RTA_OK(rta, len))
+    {
+        if (rta->rta_type <= max)
+            tb[rta->rta_type] = rta;
+        rta = RTA_NEXT(rta,len);
+    }
+
+    if (len)
+        snmp_log(LOG_ERR, "parse_rtattr: !!!Deficit %d, rta_len=%d\n", len, rta->rta_len);
+
+    return 0;
+}
+
+static int
+get_nlmsg(const struct sockaddr_nl *who, struct nlmsghdr *m, void *arg)
+{
+    struct ifinfomsg *ifi = NLMSG_DATA(m);
+    struct rtattr * tb[IFLA_MAX+1];
+    int len = m->nlmsg_len;
+    struct ifstat_ent *n;
+
+    if (m->nlmsg_type != RTM_NEWLINK)
+        return 0;
+
+    len -= NLMSG_LENGTH(sizeof(*ifi));
+    if (len < 0)
+        return -1;
+
+    parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+    if (tb[IFLA_IFNAME] == NULL || tb[IFLA_STATS] == NULL)
+        return 0;
+
+    n = malloc(sizeof(*n));
+    memset(n, 0, sizeof(*n));
+
+    n->ifindex = ifi->ifi_index;
+    n->name = strdup(RTA_DATA(tb[IFLA_IFNAME]));
+    memcpy(&n->stats, RTA_DATA(tb[IFLA_STATS]), sizeof(n->stats));
+    n->next = kern_db;
+    kern_db = n;
+    return 0;
+}
+
+static int
+rtnl_dump_filter_l(struct rtnl_handle *rth,
+                   const struct rtnl_dump_filter_arg *arg)
+{
+    struct sockaddr_nl nladdr;
+    struct iovec iov;
+    struct msghdr msg = {
+        .msg_name = &nladdr,
+        .msg_namelen = sizeof(nladdr),
+        .msg_iov = &iov,
+        .msg_iovlen = 1,
+    };
+    char buf[16384];
+
+    iov.iov_base = buf;
+    while (1) {
+        int status;
+        const struct rtnl_dump_filter_arg *a;
+
+        iov.iov_len = sizeof(buf);
+        status = recvmsg(rth->fd, &msg, 0);
+
+        if (status < 0) {
+            if (errno == EINTR || errno == EAGAIN)
+                continue;
+            fprintf(stderr, "netlink receive error %s (%d)\n",
+                            strerror(errno), errno);
+            return -1;
+        }
+
+        if (status == 0) {
+            fprintf(stderr, "EOF on netlink\n");
+            return -1;
+        }
+
+        for (a = arg; a->filter; a++) {
+            struct nlmsghdr *h = (struct nlmsghdr*)buf;
+
+            while (NLMSG_OK(h, status)) {
+                int err;
+
+                if (nladdr.nl_pid != 0 ||
+                     h->nlmsg_pid != rth->local.nl_pid ||
+                     h->nlmsg_seq != rth->dump) {
+                    if (a->junk) {
+                        err = a->junk(&nladdr, h, a->arg2);
+                        if (err < 0)
+                            return err;
+                    }
+                    goto skip_it;
+                }
+
+                if (h->nlmsg_type == NLMSG_DONE)
+                    return 0;
+                if (h->nlmsg_type == NLMSG_ERROR) {
+                    struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h);
+                    if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
+                        fprintf(stderr, "ERROR truncated\n");
+                    } else {
+                        errno = -err->error;
+                        perror("RTNETLINK answers");
+                    }
+                    return -1;
+                }
+                err = a->filter(&nladdr, h, a->arg1);
+                if (err < 0)
+                    return err;
+
+skip_it:
+                h = NLMSG_NEXT(h, status);
+            }
+        } while (0);
+        if (msg.msg_flags & MSG_TRUNC) {
+            fprintf(stderr, "Message truncated\n");
+            continue;
+        }
+        if (status) {
+            fprintf(stderr, "!!!Remnant of size %d\n", status);
+            exit(1);
+        }
+    }
+}
+
+static int
+rtnl_dump_filter(struct rtnl_handle *rth,
+                 rtnl_filter_t filter,
+                 void *arg1,
+                 rtnl_filter_t junk,
+                 void *arg2)
+{
+    const struct rtnl_dump_filter_arg a[2] = {
+        { .filter = filter, .arg1 = arg1, .junk = junk, .arg2 = arg2 },
+        { .filter = NULL,   .arg1 = NULL, .junk = NULL, .arg2 = NULL }
+    };
+
+    return rtnl_dump_filter_l(rth, a);
+}
+
+int
+_dot3Stats_netlink_get_errorcntrs(dot3StatsTable_rowreq_ctx *rowreq_ctx, const char *name)
+{
+    struct rtnl_handle rth;
+    struct ifstat_ent *ke;
+    int done;
+
+    if (rtnl_open(&rth, 0) < 0)
+    {
+        snmp_log(LOG_ERR, "_dot3Stats_netlink_get_errorcntrs: rtnl_open() failed\n");
+        return 1;
+    }
+
+    if (rtnl_wilddump_request(&rth, AF_INET, RTM_GETLINK) < 0)
+    {
+        snmp_log(LOG_ERR, "_dot3Stats_netlink_get_errorcntrs: Cannot send dump request");
+        rtnl_close(&rth);
+        return 1;
+    }
+
+    if (rtnl_dump_filter(&rth, get_nlmsg, NULL, NULL, NULL) < 0)
+    {
+        snmp_log(LOG_ERR, "_dot3Stats_netlink_get_errorcntrs: Dump terminated\n");
+        rtnl_close(&rth);
+        return 1;
+    }
+
+    rtnl_close(&rth);
+
+    /*
+     * Now scan kern_db for this if's data
+     * While doing so, we'll throw away the kern db.
+     */
+    done = 0;
+
+    while ((ke = kern_db) != NULL)
+    {
+        if (strcmp(ke->name, name) == 0)
+        {
+            dot3StatsTable_data *data = &rowreq_ctx->data;
+
+            snmp_log(LOG_ERR, "IFLA_STATS for %s\n", name);
+
+            data->dot3StatsFCSErrors = ke->stats.rx_crc_errors;
+            rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSFCSERRORS_FLAG;
+
+            data->dot3StatsDeferredTransmissions = ke->stats.tx_dropped;
+            rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSDEFERREDTRANSMISSIONS_FLAG;
+
+            data->dot3StatsInternalMacTransmitErrors = ke->stats.tx_fifo_errors;
+            rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSINTERNALMACTRANSMITERRORS_FLAG;
+
+            data->dot3StatsCarrierSenseErrors = ke->stats.tx_carrier_errors;
+            rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSCARRIERSENSEERRORS_FLAG;
+
+            data->dot3StatsFrameTooLongs = ke->stats.rx_frame_errors;
+            rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSFRAMETOOLONGS_FLAG;
+
+            data->dot3StatsInternalMacReceiveErrors = ke->stats.rx_fifo_errors;
+            rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSINTERNALMACRECEIVEERRORS_FLAG;
+
+            done = 1;
+        }
+        kern_db = ke->next;
+        free(ke);
+    }
+
+    return !done;
+}
+# else /* }{ */
+int
+_dot3Stats_netlink_get_errorcntrs(dot3StatsTable_rowreq_ctx *rowreq_ctx, const char *name)
+{
+    return 1;
+}
+# endif /* } */
+
+
+/*
+ * NAME: getulongfromsysclassnetstatistics
+ * PURPOSE: To get a single statistics value from /sys/class/net/<ifname>/statistics/<ctrname>
+ * ARGUMENTS: ifname: interface name
+ *      ctrname: counter name
+ *      valuep: where to store value
+ * RETURNS: 0 if value not available
+ *      non-0 if value available
+ */
+static int
+getulongfromsysclassnetstatistics(const char *ifname, char *ctrname, u_long *valuep)
+{
+    char path[256];
+    FILE *fp;
+    int rv;
+
+    if (ifname == NULL || ctrname == NULL || valuep == NULL)
+        return 0;
+
+    snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/%s", ifname, ctrname);
+    fp = fopen(path, "rt");
+    if (fp == NULL)
+        return 0;
+
+    rv = 1;
+    if (fscanf(fp, "%lu", valuep) != 1)
+        rv = 0;
+
+    fclose(fp);
+
+    return rv;
+}
+
+/*
+ * NAME: interface_dot3stats_get_errorcounters
+ * PURPOSE: To get ethernet error statistics
+ * ARGUMENTS: rowreq_ctx: where to store the value(s)
+ *      name: interface name
+ * RETURNS: nothing. fields not set if data not available
+ */
+void
+interface_dot3stats_get_errorcounters (dot3StatsTable_rowreq_ctx *rowreq_ctx, const char *name)
+{
+    u_long value;
+    dot3StatsTable_data *data = &rowreq_ctx->data;
+    FILE *dev;
+    const char NETDEV_FILE[] = "/proc/net/dev";
+
+    if (_dot3Stats_netlink_get_errorcntrs(rowreq_ctx, name) == 0)
+    {
+        snmp_log(LOG_NOTICE, "interface_dot3stats_get_errorcounters: got data from IFLA_STATS\n");
+        return;
+    }
+
+    if ((dev = fopen(NETDEV_FILE, "r")) != NULL)
+    {
+        char line[256], *lp, *next;
+        size_t namelen = strlen(name);
+        unsigned int value;
+        unsigned int column;
+
+        while (fgets(line, sizeof(line), dev) != NULL)
+        {
+            /*    br0:68395635 1038214    0    0    0     0          0    939411 25626606   90708    0    0    0     0       0          0 */
+            lp = line;
+            while (*lp == ' ' || *lp == '\t')
+                lp++;
+            if (strncmp(lp, name, namelen) != 0 || lp[namelen] != ':')
+                continue;
+            lp += namelen + 1;         /* Skip name and colon */
+
+            column = 1;
+            while (1)
+            {
+                value = strtoul(lp, &next, 10);
+                if (next == lp)
+                    break;             /* no more data */
+                switch (column)
+                {
+                case 3:
+                    data->dot3StatsFCSErrors = value;
+                    rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSFCSERRORS_FLAG;
+                    break;
+                case 12:
+                    data->dot3StatsDeferredTransmissions = value;
+                    rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSDEFERREDTRANSMISSIONS_FLAG;
+                    break;
+                case 13:
+                    data->dot3StatsInternalMacTransmitErrors = value;
+                    rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSINTERNALMACTRANSMITERRORS_FLAG;
+                    break;
+                case 15:
+                    data->dot3StatsCarrierSenseErrors = value;
+                    rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSCARRIERSENSEERRORS_FLAG;
+                    break;
+                case 6:
+                    data->dot3StatsFrameTooLongs = value;
+                    rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSFRAMETOOLONGS_FLAG;
+                    break;
+                case 5:
+                    data->dot3StatsInternalMacReceiveErrors = value;
+                    rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSINTERNALMACRECEIVEERRORS_FLAG;
+                    break;
+                case 14:
+                    data->dot3StatsSingleCollisionFrames = value;
+                    rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSSINGLECOLLISIONFRAMES_FLAG;
+                    break;
+                }
+                column++;
+                lp = next;
+            }
+            break;
+        }
+
+        fclose(dev);
+    }
+
+    if (!(rowreq_ctx->column_exists_flags & COLUMN_DOT3STATSFCSERRORS_FLAG) &&
+          getulongfromsysclassnetstatistics(name, "rx_errors", &value)) {
+        data->dot3StatsFCSErrors = value;
+        rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSFCSERRORS_FLAG;
+    }
+    if (!(rowreq_ctx->column_exists_flags & COLUMN_DOT3STATSDEFERREDTRANSMISSIONS_FLAG) &&
+          getulongfromsysclassnetstatistics(name, "tx_dropped", &value)) {
+        data->dot3StatsDeferredTransmissions = value;
+        rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSDEFERREDTRANSMISSIONS_FLAG;
+    }
+    if (!(rowreq_ctx->column_exists_flags & COLUMN_DOT3STATSINTERNALMACTRANSMITERRORS_FLAG) &&
+          getulongfromsysclassnetstatistics(name, "tx_fifo_errors", &value)) {
+        data->dot3StatsInternalMacTransmitErrors = value;
+        rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSINTERNALMACTRANSMITERRORS_FLAG;
+    }
+    if (!(rowreq_ctx->column_exists_flags & COLUMN_DOT3STATSCARRIERSENSEERRORS_FLAG) &&
+          getulongfromsysclassnetstatistics(name, "tx_carrier_errors", &value)) {
+        data->dot3StatsCarrierSenseErrors = value;
+        rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSCARRIERSENSEERRORS_FLAG;
+    }
+    if (!(rowreq_ctx->column_exists_flags & COLUMN_DOT3STATSFRAMETOOLONGS_FLAG) &&
+          getulongfromsysclassnetstatistics(name, "rx_frame_errors", &value)) {
+        data->dot3StatsFrameTooLongs = value;
+        rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSFRAMETOOLONGS_FLAG;
+    }
+    if (!(rowreq_ctx->column_exists_flags & COLUMN_DOT3STATSINTERNALMACRECEIVEERRORS_FLAG) &&
+          getulongfromsysclassnetstatistics(name, "rx_fifo_errors", &value)) {
+        data->dot3StatsInternalMacReceiveErrors = value;
+        rowreq_ctx->column_exists_flags |= COLUMN_DOT3STATSINTERNALMACRECEIVEERRORS_FLAG;
+    }
+
+    return;
+}
+
 /*
  * @retval  0 success
  * @retval -1 cannot get ETHTOOL_DRVINFO failed 
@@ -334,39 +831,6 @@ interface_ioctl_dot3stats_duplex_get(dot
 }
 
 /*
- * NAME: getulongfromsysclassnetstatistics
- * PURPOSE: To get a single statistics value from /sys/class/net/<ifname>/statistics/<ctrname>
- * ARGUMENTS: ifname: interface name
- *	ctrname: counter name
- *	valuep: where to store value
- * RETURNS: 0 if value not available
- *	non-0 if value available
- */
-static int
-getulongfromsysclassnetstatistics(const char *ifname, char *ctrname, u_long *valuep)
-{
-    char path[256];
-    FILE *fp;
-    int rv;
-
-    if (ifname == NULL || ctrname == NULL || valuep == NULL)
-	return 0;
-
-    snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/%s", ifname, ctrname);
-    fp = fopen(path, "rt");
-    if (fp == NULL)
-	return 0;
-
-    rv = 1;
-    if (fscanf(fp, "%lu", valuep) != 1)
-	rv = 0;
-
-    fclose(fp);
-
-    return rv;
-}
-
-/*
  * NAME: interface_sysclassnet_dot3stats_get
  * PURPOSE: To get ethernet statistics from /sys/class/net/...
  * ARGUMENTS: rowreq_ctx: where to store the value(s)
