diff options
Diffstat (limited to 'src/pmdas/linux/proc_net_dev.c')
-rw-r--r-- | src/pmdas/linux/proc_net_dev.c | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/src/pmdas/linux/proc_net_dev.c b/src/pmdas/linux/proc_net_dev.c new file mode 100644 index 0000000..93e3057 --- /dev/null +++ b/src/pmdas/linux/proc_net_dev.c @@ -0,0 +1,444 @@ +/* + * Linux /proc/net/dev metrics cluster + * + * Copyright (c) 2013-2014 Red Hat. + * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include "indom.h" +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <net/if.h> +#include <ctype.h> +#include "proc_net_dev.h" + +static int +refresh_inet_socket() +{ + static int netfd = -1; + if (netfd < 0) + netfd = socket(AF_INET, SOCK_DGRAM, 0); + return netfd; +} + +static int +refresh_net_dev_ioctl(char *name, net_interface_t *netip) +{ + struct ethtool_cmd ecmd = { 0 }; + /* + * Note: + * Initialization of ecmd is not really needed. If the ioctl()s + * work, ecmd is filled in ... but valgrind (at least up to + * version 3.9.0) does not know about the SIOCETHTOOL ioctl() + * and thinks the use of ecmd after this call propagates + * uninitialized data in to ioc.speed and ioc.duplex, causing + * failures for qa/957 + * - Ken McDonell, 11 Apr 2014 + */ + struct ifreq ifr; + int fd; + + if ((fd = refresh_inet_socket()) < 0) + return 0; + + ecmd.cmd = ETHTOOL_GSET; + ifr.ifr_data = (caddr_t)&ecmd; + strncpy(ifr.ifr_name, name, IF_NAMESIZE); + ifr.ifr_name[IF_NAMESIZE-1] = '\0'; + if (!(ioctl(fd, SIOCGIFMTU, &ifr) < 0)) + netip->ioc.mtu = ifr.ifr_mtu; + + ecmd.cmd = ETHTOOL_GSET; + ifr.ifr_data = (caddr_t)&ecmd; + strncpy(ifr.ifr_name, name, IF_NAMESIZE); + ifr.ifr_name[IF_NAMESIZE-1] = '\0'; + if (!(ioctl(fd, SIOCGIFFLAGS, &ifr) < 0)) { + netip->ioc.linkup = !!(ifr.ifr_flags & IFF_UP); + netip->ioc.running = !!(ifr.ifr_flags & IFF_RUNNING); + } + /* ETHTOOL ioctl -> non-root permissions issues for old kernels */ + ecmd.cmd = ETHTOOL_GSET; + ifr.ifr_data = (caddr_t)&ecmd; + strncpy(ifr.ifr_name, name, IF_NAMESIZE); + ifr.ifr_name[IF_NAMESIZE-1] = '\0'; + if (!(ioctl(fd, SIOCETHTOOL, &ifr) < 0)) { + /* + * speed is defined in ethtool.h and returns the speed in + * Mbps, so 100 for 100Mbps, 1000 for 1Gbps, etc + */ + netip->ioc.speed = ecmd.speed; + netip->ioc.duplex = ecmd.duplex + 1; + return 0; + } + return -ENOSYS; /* caller should try ioctl alternatives */ +} + +static void +refresh_net_ipv4_addr(char *name, net_addr_t *addr) +{ + struct ifreq ifr; + int fd; + + if ((fd = refresh_inet_socket()) < 0) + return; + strncpy(ifr.ifr_name, name, IF_NAMESIZE); + ifr.ifr_name[IF_NAMESIZE-1] = '\0'; + ifr.ifr_addr.sa_family = AF_INET; + if (ioctl(fd, SIOCGIFADDR, &ifr) >= 0) { + struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr; + if (inet_ntop(AF_INET, &sin->sin_addr, addr->inet, INET_ADDRSTRLEN)) + addr->has_inet = 1; + } +} + +/* + * No ioctl support or no permissions (more likely), so we + * fall back to grovelling about in /sys/class/net in a last + * ditch attempt to find the ethtool interface data (duplex + * and speed). + */ +static char * +read_oneline(const char *path, char *buffer) +{ + FILE *fp = fopen(path, "r"); + + if (fp) { + int i = fscanf(fp, "%63s", buffer); + fclose(fp); + if (i == 1) + return buffer; + } + return ""; +} + +static void +refresh_net_dev_sysfs(char *name, net_interface_t *netip) +{ + char path[MAXPATHLEN]; + char line[64]; + char *duplex; + + snprintf(path, sizeof(path), "%s/sys/class/net/%s/speed", linux_statspath, name); + path[sizeof(path)-1] = '\0'; + netip->ioc.speed = atoi(read_oneline(path, line)); + + snprintf(path, sizeof(path), "%s/sys/class/net/%s/duplex", linux_statspath, name); + path[sizeof(path)-1] = '\0'; + duplex = read_oneline(path, line); + + if (strcmp(duplex, "full") == 0) + netip->ioc.duplex = 2; + else if (strcmp(duplex, "half") == 0) + netip->ioc.duplex = 1; + else /* eh? */ + netip->ioc.duplex = 0; +} + +static void +refresh_net_hw_addr(char *name, net_addr_t *netip) +{ + char path[MAXPATHLEN]; + char line[64]; + char *value; + + snprintf(path, sizeof(path), "%s/sys/class/net/%s/address", linux_statspath, name); + path[sizeof(path)-1] = '\0'; + + value = read_oneline(path, line); + + if (value[0] != '\0') + netip->has_hw = 1; + strncpy(netip->hw_addr, value, sizeof(netip->hw_addr)); + netip->hw_addr[sizeof(netip->hw_addr)-1] = '\0'; +} + +int +refresh_proc_net_dev(pmInDom indom) +{ + char buf[1024]; + FILE *fp; + unsigned long long llval; + char *p, *v; + int j, sts; + net_interface_t *netip; + + static uint64_t gen; /* refresh generation number */ + static uint32_t cache_err; + + if ((fp = linux_statsfile("/proc/net/dev", buf, sizeof(buf))) == NULL) + return -oserror(); + + if (gen == 0) { + /* + * first time, reload cache from external file, and force any + * subsequent changes to be saved + */ + pmdaCacheOp(indom, PMDA_CACHE_LOAD); + } + gen++; + + /* +Inter-| Receive | Transmit + face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed + lo: 4060748 39057 0 0 0 0 0 0 4060748 39057 0 0 0 0 0 0 + eth0: 0 337614 0 0 0 0 0 0 0 267537 0 0 0 27346 62 0 + */ + + pmdaCacheOp(indom, PMDA_CACHE_INACTIVE); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + if ((p = v = strchr(buf, ':')) == NULL) + continue; + *p = '\0'; + for (p=buf; *p && isspace((int)*p); p++) {;} + + sts = pmdaCacheLookupName(indom, p, NULL, (void **)&netip); + if (sts == PM_ERR_INST || (sts >= 0 && netip == NULL)) { + /* first time since re-loaded, else new one */ + netip = (net_interface_t *)calloc(1, sizeof(net_interface_t)); +#if PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + fprintf(stderr, "refresh_proc_net_dev: initialize \"%s\"\n", p); + } +#endif + } + else if (sts < 0) { + if (cache_err++ < 10) { + fprintf(stderr, "refresh_proc_net_dev: pmdaCacheLookupName(%s, %s, ...) failed: %s\n", + pmInDomStr(indom), p, pmErrStr(sts)); + } + continue; + } + if (netip->last_gen != gen-1) { + /* + * rediscovered one that went away and has returned + * + * kernel counters are reset, so clear last_counters to + * avoid false overflows + */ + for (j=0; j < PROC_DEV_COUNTERS_PER_LINE; j++) { + netip->last_counters[j] = 0; + } + } + netip->last_gen = gen; + if ((sts = pmdaCacheStore(indom, PMDA_CACHE_ADD, p, (void *)netip)) < 0) { + if (cache_err++ < 10) { + fprintf(stderr, "refresh_proc_net_dev: pmdaCacheStore(%s, PMDA_CACHE_ADD, %s, " PRINTF_P_PFX "%p) failed: %s\n", + pmInDomStr(indom), p, netip, pmErrStr(sts)); + } + continue; + } + + /* Issue ioctls for remaining data, not exported through proc */ + memset(&netip->ioc, 0, sizeof(netip->ioc)); + if (refresh_net_dev_ioctl(p, netip) < 0) + refresh_net_dev_sysfs(p, netip); + + for (p=v, j=0; j < PROC_DEV_COUNTERS_PER_LINE; j++) { + for (; !isdigit((int)*p); p++) {;} + sscanf(p, "%llu", &llval); + if (llval >= netip->last_counters[j]) { + netip->counters[j] += + llval - netip->last_counters[j]; + } + else { + /* 32bit counter has wrapped */ + netip->counters[j] += + llval + (UINT_MAX - netip->last_counters[j]); + } + netip->last_counters[j] = llval; + for (; !isspace((int)*p); p++) {;} + } + } + + pmdaCacheOp(indom, PMDA_CACHE_SAVE); + + /* success */ + fclose(fp); + return 0; +} + +static int +refresh_net_dev_ipv4_addr(pmInDom indom) +{ + int n, fd, sts, numreqs = 30; + struct ifconf ifc; + struct ifreq *ifr; + net_addr_t *netip; + static uint32_t cache_err; + + if ((fd = refresh_inet_socket()) < 0) + return fd; + + ifc.ifc_buf = NULL; + for (;;) { + ifc.ifc_len = sizeof(struct ifreq) * numreqs; + ifc.ifc_buf = realloc(ifc.ifc_buf, ifc.ifc_len); + + if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) { + free(ifc.ifc_buf); + return -oserror(); + } + if (ifc.ifc_len == sizeof(struct ifreq) * numreqs) { + /* assume it overflowed and try again */ + numreqs *= 2; + continue; + } + break; + } + + for (n = 0, ifr = ifc.ifc_req; + n < ifc.ifc_len; + n += sizeof(struct ifreq), ifr++) { + sts = pmdaCacheLookupName(indom, ifr->ifr_name, NULL, (void **)&netip); + if (sts == PM_ERR_INST || (sts >= 0 && netip == NULL)) { + /* first time since re-loaded, else new one */ + netip = (net_addr_t *)calloc(1, sizeof(net_addr_t)); + } + else if (sts < 0) { + if (cache_err++ < 10) { + fprintf(stderr, "refresh_net_dev_ipv4_addr: " + "pmdaCacheLookupName(%s, %s, ...) failed: %s\n", + pmInDomStr(indom), ifr->ifr_name, pmErrStr(sts)); + } + continue; + } + if ((sts = pmdaCacheStore(indom, PMDA_CACHE_ADD, ifr->ifr_name, (void *)netip)) < 0) { + if (cache_err++ < 10) { + fprintf(stderr, "refresh_net_dev_ipv4_addr: " + "pmdaCacheStore(%s, PMDA_CACHE_ADD, %s, " + PRINTF_P_PFX "%p) failed: %s\n", + pmInDomStr(indom), ifr->ifr_name, netip, pmErrStr(sts)); + } + continue; + } + + refresh_net_ipv4_addr(ifr->ifr_name, netip); + refresh_net_hw_addr(ifr->ifr_name, netip); + } + free(ifc.ifc_buf); + return 0; +} + +static int +refresh_net_dev_ipv6_addr(pmInDom indom) +{ + FILE *fp; + char addr6p[8][5]; + char addr6[40], devname[20+1]; + char addr[INET6_ADDRSTRLEN]; + char buf[MAXPATHLEN]; + struct sockaddr_in6 sin6; + int sts, plen, scope, dad_status, if_idx; + net_addr_t *netip; + static uint32_t cache_err; + + if ((fp = linux_statsfile("/proc/net/if_inet6", buf, sizeof(buf))) == NULL) + return 0; + + while (fscanf(fp, "%4s%4s%4s%4s%4s%4s%4s%4s %02x %02x %02x %02x %20s\n", + addr6p[0], addr6p[1], addr6p[2], addr6p[3], + addr6p[4], addr6p[5], addr6p[6], addr6p[7], + &if_idx, &plen, &scope, &dad_status, devname) != EOF) { + sts = pmdaCacheLookupName(indom, devname, NULL, (void **)&netip); + if (sts == PM_ERR_INST || (sts >= 0 && netip == NULL)) { + /* first time since re-loaded, else new one */ + netip = (net_addr_t *)calloc(1, sizeof(net_addr_t)); + } + else if (sts < 0) { + if (cache_err++ < 10) { + fprintf(stderr, "refresh_net_dev_ipv6_addr: " + "pmdaCacheLookupName(%s, %s, ...) failed: %s\n", + pmInDomStr(indom), devname, pmErrStr(sts)); + } + continue; + } + if ((sts = pmdaCacheStore(indom, PMDA_CACHE_ADD, devname, (void *)netip)) < 0) { + if (cache_err++ < 10) { + fprintf(stderr, "refresh_net_dev_ipv6_addr: " + "pmdaCacheStore(%s, PMDA_CACHE_ADD, %s, " + PRINTF_P_PFX "%p) failed: %s\n", + pmInDomStr(indom), devname, netip, pmErrStr(sts)); + } + continue; + } + + sprintf(addr6, "%s:%s:%s:%s:%s:%s:%s:%s", + addr6p[0], addr6p[1], addr6p[2], addr6p[3], + addr6p[4], addr6p[5], addr6p[6], addr6p[7]); + if (inet_pton(AF_INET6, addr6, sin6.sin6_addr.s6_addr) != 1) + continue; + + sin6.sin6_family = AF_INET6; + sin6.sin6_port = 0; + if (!inet_ntop(AF_INET6, &sin6.sin6_addr, addr, INET6_ADDRSTRLEN)) + continue; + snprintf(netip->ipv6, sizeof(netip->ipv6), "%s/%d", addr, plen); + netip->ipv6scope = (uint16_t)scope; + netip->has_ipv6 = 1; + + refresh_net_hw_addr(devname, netip); + } + fclose(fp); + return 0; +} + +/* + * This separate indom provides the addresses for all interfaces including + * aliases (e.g. eth0, eth0:0, eth0:1, etc) - this is what ifconfig does. + */ +int +refresh_net_dev_addr(pmInDom indom) +{ + int sts = 0; + net_addr_t*p; + + for (pmdaCacheOp(indom, PMDA_CACHE_WALK_REWIND);;) { + if ((sts = pmdaCacheOp(indom, PMDA_CACHE_WALK_NEXT)) < 0) + break; + if (!pmdaCacheLookup(indom, sts, NULL, (void **)&p) || !p) + continue; + p->has_inet = 0; + p->has_ipv6 = 0; + p->has_hw = 0; + } + + pmdaCacheOp(indom, PMDA_CACHE_INACTIVE); + + sts |= refresh_net_dev_ipv4_addr(indom); + sts |= refresh_net_dev_ipv6_addr(indom); + + pmdaCacheOp(indom, PMDA_CACHE_SAVE); + return sts; +} + +char * +lookup_ipv6_scope(int scope) +{ + switch (scope) { + case IPV6_ADDR_ANY: + return "Global"; + case IPV6_ADDR_LINKLOCAL: + return "Link"; + case IPV6_ADDR_SITELOCAL: + return "Site"; + case IPV6_ADDR_COMPATv4: + return "Compat"; + case IPV6_ADDR_LOOPBACK: + return "Host"; + } + return "Unknown"; +} |