// SPDX-License-Identifier: GPL-2.0 #include #include #include #include "../../../../../include/linux/compiler.h" #include "../../../../../include/linux/kernel.h" #include "aolib.h" struct netstat_counter { uint64_t val; char *name; }; struct netstat { char *header_name; struct netstat *next; size_t counters_nr; struct netstat_counter *counters; }; static struct netstat *lookup_type(struct netstat *ns, const char *type, size_t len) { while (ns != NULL) { size_t cmp = max(len, strlen(ns->header_name)); if (!strncmp(ns->header_name, type, cmp)) return ns; ns = ns->next; } return NULL; } static struct netstat *lookup_get(struct netstat *ns, const char *type, const size_t len) { struct netstat *ret; ret = lookup_type(ns, type, len); if (ret != NULL) return ret; ret = malloc(sizeof(struct netstat)); if (!ret) test_error("malloc()"); ret->header_name = strndup(type, len); if (ret->header_name == NULL) test_error("strndup()"); ret->next = ns; ret->counters_nr = 0; ret->counters = NULL; return ret; } static struct netstat *lookup_get_column(struct netstat *ns, const char *line) { char *column; column = strchr(line, ':'); if (!column) test_error("can't parse netstat file"); return lookup_get(ns, line, column - line); } static void netstat_read_type(FILE *fnetstat, struct netstat **dest, char *line) { struct netstat *type = lookup_get_column(*dest, line); const char *pos = line; size_t i, nr_elems = 0; char tmp; while ((pos = strchr(pos, ' '))) { nr_elems++; pos++; } *dest = type; type->counters = reallocarray(type->counters, type->counters_nr + nr_elems, sizeof(struct netstat_counter)); if (!type->counters) test_error("reallocarray()"); pos = strchr(line, ' ') + 1; if (fscanf(fnetstat, "%[^ :]", type->header_name) == EOF) test_error("fscanf(%s)", type->header_name); if (fread(&tmp, 1, 1, fnetstat) != 1 || tmp != ':') test_error("Unexpected netstat format (%c)", tmp); for (i = type->counters_nr; i < type->counters_nr + nr_elems; i++) { struct netstat_counter *nc = &type->counters[i]; const char *new_pos = strchr(pos, ' '); const char *fmt = " %" PRIu64; if (new_pos == NULL) new_pos = strchr(pos, '\n'); nc->name = strndup(pos, new_pos - pos); if (nc->name == NULL) test_error("strndup()"); if (unlikely(!strcmp(nc->name, "MaxConn"))) fmt = " %" PRId64; /* MaxConn is signed, RFC 2012 */ if (fscanf(fnetstat, fmt, &nc->val) != 1) test_error("fscanf(%s)", nc->name); pos = new_pos + 1; } type->counters_nr += nr_elems; if (fread(&tmp, 1, 1, fnetstat) != 1 || tmp != '\n') test_error("Unexpected netstat format"); } static const char *snmp6_name = "Snmp6"; static void snmp6_read(FILE *fnetstat, struct netstat **dest) { struct netstat *type = lookup_get(*dest, snmp6_name, strlen(snmp6_name)); char *counter_name; size_t i; for (i = type->counters_nr;; i++) { struct netstat_counter *nc; uint64_t counter; if (fscanf(fnetstat, "%ms", &counter_name) == EOF) break; if (fscanf(fnetstat, "%" PRIu64, &counter) == EOF) test_error("Unexpected snmp6 format"); type->counters = reallocarray(type->counters, i + 1, sizeof(struct netstat_counter)); if (!type->counters) test_error("reallocarray()"); nc = &type->counters[i]; nc->name = counter_name; nc->val = counter; } type->counters_nr = i; *dest = type; } struct netstat *netstat_read(void) { struct netstat *ret = 0; size_t line_sz = 0; char *line = NULL; FILE *fnetstat; /* * Opening thread-self instead of /proc/net/... as the latter * points to /proc/self/net/ which instantiates thread-leader's * net-ns, see: * commit 155134fef2b6 ("Revert "proc: Point /proc/{mounts,net} at..") */ errno = 0; fnetstat = fopen("/proc/thread-self/net/netstat", "r"); if (fnetstat == NULL) test_error("failed to open /proc/net/netstat"); while (getline(&line, &line_sz, fnetstat) != -1) netstat_read_type(fnetstat, &ret, line); fclose(fnetstat); errno = 0; fnetstat = fopen("/proc/thread-self/net/snmp", "r"); if (fnetstat == NULL) test_error("failed to open /proc/net/snmp"); while (getline(&line, &line_sz, fnetstat) != -1) netstat_read_type(fnetstat, &ret, line); fclose(fnetstat); errno = 0; fnetstat = fopen("/proc/thread-self/net/snmp6", "r"); if (fnetstat == NULL) test_error("failed to open /proc/net/snmp6"); snmp6_read(fnetstat, &ret); fclose(fnetstat); free(line); return ret; } void netstat_free(struct netstat *ns) { while (ns != NULL) { struct netstat *prev = ns; size_t i; free(ns->header_name); for (i = 0; i < ns->counters_nr; i++) free(ns->counters[i].name); free(ns->counters); ns = ns->next; free(prev); } } static inline void __netstat_print_diff(uint64_t a, struct netstat *nsb, size_t i) { if (unlikely(!strcmp(nsb->header_name, "MaxConn"))) { test_print("%8s %25s: %" PRId64 " => %" PRId64, nsb->header_name, nsb->counters[i].name, a, nsb->counters[i].val); return; } test_print("%8s %25s: %" PRIu64 " => %" PRIu64, nsb->header_name, nsb->counters[i].name, a, nsb->counters[i].val); } void netstat_print_diff(struct netstat *nsa, struct netstat *nsb) { size_t i, j; while (nsb != NULL) { if (unlikely(strcmp(nsb->header_name, nsa->header_name))) { for (i = 0; i < nsb->counters_nr; i++) __netstat_print_diff(0, nsb, i); nsb = nsb->next; continue; } if (nsb->counters_nr < nsa->counters_nr) test_error("Unexpected: some counters disappeared!"); for (j = 0, i = 0; i < nsb->counters_nr; i++) { if (strcmp(nsb->counters[i].name, nsa->counters[j].name)) { __netstat_print_diff(0, nsb, i); continue; } if (nsa->counters[j].val == nsb->counters[i].val) { j++; continue; } __netstat_print_diff(nsa->counters[j].val, nsb, i); j++; } if (j != nsa->counters_nr) test_error("Unexpected: some counters disappeared!"); nsb = nsb->next; nsa = nsa->next; } } uint64_t netstat_get(struct netstat *ns, const char *name, bool *not_found) { if (not_found) *not_found = false; while (ns != NULL) { size_t i; for (i = 0; i < ns->counters_nr; i++) { if (!strcmp(name, ns->counters[i].name)) return ns->counters[i].val; } ns = ns->next; } if (not_found) *not_found = true; return 0; }