274 lines
6.2 KiB
C
274 lines
6.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <inttypes.h>
|
|
#include <pthread.h>
|
|
#include <stdio.h>
|
|
#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;
|
|
}
|