skip to content
==> src/nic.c <==
1/* Interface counters: the OS readers, the wrap-safe extension, the total, the records. */
2#include "mesh/nic.h"
3#include "mesh/buf.h"
4
5#include <arpa/inet.h>
6#include <errno.h>
7#include <ifaddrs.h>
8#include <net/if.h>
9#include <netinet/in.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <sys/socket.h>
14
15const size_t MESH_NIC_COUNTERS[8] = {MESH_NI_RX_BYTES, MESH_NI_TX_BYTES, MESH_NI_RX_PACKETS, MESH_NI_TX_PACKETS,
16                                     MESH_NI_RX_ERRS,  MESH_NI_TX_ERRS,  MESH_NI_RX_DROP,    MESH_NI_TX_DROP};
17
18mesh_nic_ext mesh_nic_ext_new(uint64_t raw) {
19    mesh_nic_ext e = {raw, raw};
20    return e;
21}
22
23uint64_t mesh_nic_ext_update(mesh_nic_ext *e, uint64_t raw, uint32_t bits) {
24    uint64_t d;
25    if (bits >= 64) d = raw >= e->last ? raw - e->last : raw;
26    else {
27        uint64_t m = (1ull << bits) - 1;
28        d = ((raw & m) - (e->last & m)) & m;
29    }
30    e->last = raw;
31    e->value += d;
32    return d;
33}
34
35static void trim(const char **s, const char **e) {
36    while (*s < *e && (**s == ' ' || **s == '\t')) (*s)++;
37    while (*e > *s && ((*e)[-1] == ' ' || (*e)[-1] == '\t' || (*e)[-1] == '\r')) (*e)--;
38}
39
40int mesh_nic_parse_proc_net_dev(const char *text, mesh_nic_raw **out, size_t *n, char **err) {
41    *out = NULL;
42    *n = 0;
43    size_t cap = 0, lineno = 0;
44    const char *l = text;
45    while (*l) {
46        const char *e = strchr(l, '\n');
47        const char *end = e ? e : l + strlen(l);
48        lineno++;
49        if (lineno > 2) {
50            const char *colon = memchr(l, ':', (size_t)(end - l));
51            if (!colon) { *err = mesh_buf_fmt("/proc/net/dev line %zu: no ':'", lineno); free(*out); *out = NULL; *n = 0; return -1; }
52            const char *ns = l, *ne = colon;
53            trim(&ns, &ne);
54            uint64_t f[32];
55            size_t k = 0;
56            const char *p = colon + 1;
57            while (p < end) {
58                while (p < end && (*p == ' ' || *p == '\t' || *p == '\r')) p++;
59                if (p >= end) break;
60                const char *w = p;
61                while (p < end && !(*p == ' ' || *p == '\t' || *p == '\r')) p++;
62                /* u64::from_str: digits only (a leading '+' allowed), else ParseIntError's words. */
63                size_t wl = (size_t)(p - w), i0 = w[0] == '+' && wl > 1;
64                int ok = 1, big = 0;
65                uint64_t x = 0;
66                for (size_t i = i0; ok && i < wl; i++) {
67                    ok = w[i] >= '0' && w[i] <= '9';
68                    if (ok && !big) {
69                        unsigned d = (unsigned)(w[i] - '0');
70                        if (x > (UINT64_MAX - d) / 10) big = 1;
71                        else x = x * 10 + d;
72                    }
73                }
74                if (ok && !big) f[k < 32 ? k : 31] = x;
75                if (!ok || big) {
76                    *err = mesh_buf_fmt("/proc/net/dev line %zu: %s", lineno, ok ? "number too large to fit in target type" : "invalid digit found in string");
77                    free(*out); *out = NULL; *n = 0;
78                    return -1;
79                }
80                k++;
81            }
82            if (k < 16) { *err = mesh_buf_fmt("/proc/net/dev line %zu: %zu fields, want 16", lineno, k); free(*out); *out = NULL; *n = 0; return -1; }
83            if (*n == cap) { cap = cap ? 2 * cap : 16; *out = mesh_xrealloc(*out, cap * sizeof **out); }
84            mesh_nic_raw *r = &(*out)[(*n)++];
85            memset(r, 0, sizeof *r);
86            size_t nl = (size_t)(ne - ns) < MESH_NIC_NAME_MAX - 1 ? (size_t)(ne - ns) : MESH_NIC_NAME_MAX - 1;
87            memcpy(r->name, ns, nl);
88            const uint64_t v[8] = {f[0], f[8], f[1], f[9], f[2], f[10], f[3], f[11]};
89            for (int i = 0; i < 8; i++) { r->v[i] = v[i]; r->bits[i] = 64; }
90        }
91        if (!e) break;
92        l = e + 1;
93    }
94    if (*n == 0) { *err = mesh_xstrdup("/proc/net/dev lists no interface"); return -1; }
95    return 0;
96}
97
98#if defined(__linux__)
99int mesh_nic_read(mesh_nic_raw **out, size_t *n, char **err) {
100    FILE *f = fopen("/proc/net/dev", "r");
101    if (!f) { *err = mesh_buf_fmt("/proc/net/dev: %s", strerror(errno)); return -1; }
102    mesh_buf b = MESH_BUF_INIT;
103    char chunk[4096];
104    size_t got;
105    while ((got = fread(chunk, 1, sizeof chunk, f)) > 0) mesh_buf_put(&b, chunk, got);
106    fclose(f);
107    int r = mesh_nic_parse_proc_net_dev(mesh_buf_str(&b), out, n, err);
108    mesh_buf_free(&b);
109    return r;
110}
111#elif defined(__APPLE__)
112#include <net/if_dl.h>
113#include <net/route.h>
114#include <sys/sysctl.h>
115/* sysctl {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0}: one RTM_IFINFO2 message
116 * (if_msghdr2, with the 64-bit if_data64) per interface. getifaddrs' AF_LINK
117 * if_data has only 32-bit counters, which wrap every 4 GiB. Transmit drops are
118 * the send queue's (ifm_snd_drops, an int). */
119int mesh_nic_read(mesh_nic_raw **out, size_t *n, char **err) {
120    int mib[6] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0};
121    char *buf = NULL;
122    size_t got = 0;
123    for (int tries = 0; tries < 4 && !buf; tries++) {
124        size_t len = 0;
125        if (sysctl(mib, 6, NULL, &len, NULL, 0) != 0) { *err = mesh_buf_fmt("sysctl NET_RT_IFLIST2 size: %s", strerror(errno)); return -1; }
126        buf = mesh_xmalloc(len + 4096);
127        got = len + 4096;
128        if (sysctl(mib, 6, buf, &got, NULL, 0) == 0) break;
129        if (errno != ENOMEM) { *err = mesh_buf_fmt("sysctl NET_RT_IFLIST2: %s", strerror(errno)); free(buf); return -1; }
130        free(buf);
131        buf = NULL;
132    }
133    if (!buf) { *err = mesh_xstrdup("sysctl NET_RT_IFLIST2: the list kept growing"); return -1; }
134    *out = NULL;
135    *n = 0;
136    size_t cap = 0, at = 0;
137    while (at + 4 <= got) {
138        size_t msglen = (size_t)((unsigned char)buf[at] | (unsigned char)buf[at + 1] << 8);
139        if (msglen == 0 || at + msglen > got) { *err = mesh_buf_fmt("sysctl NET_RT_IFLIST2: bad message length %zu at %zu", msglen, at); free(buf); free(*out); return -1; }
140        if (buf[at + 3] == RTM_IFINFO2) {
141            struct if_msghdr2 m;
142            if (msglen < sizeof m) { *err = mesh_buf_fmt("sysctl NET_RT_IFLIST2: RTM_IFINFO2 of %zu bytes", msglen); free(buf); free(*out); return -1; }
143            memcpy(&m, buf + at, sizeof m);
144            char name[IF_NAMESIZE];
145            if (if_indextoname(m.ifm_index, name)) {
146                if (*n == cap) { cap = cap ? 2 * cap : 16; *out = mesh_xrealloc(*out, cap * sizeof **out); }
147                mesh_nic_raw *r = &(*out)[(*n)++];
148                memset(r, 0, sizeof *r);
149                snprintf(r->name, sizeof r->name, "%s", name);
150                const uint64_t v[8] = {m.ifm_data.ifi_ibytes, m.ifm_data.ifi_obytes, m.ifm_data.ifi_ipackets, m.ifm_data.ifi_opackets,
151                                       m.ifm_data.ifi_ierrors, m.ifm_data.ifi_oerrors, m.ifm_data.ifi_iqdrops, (uint64_t)(uint32_t)m.ifm_snd_drops};
152                for (int i = 0; i < 8; i++) { r->v[i] = v[i]; r->bits[i] = i == 7 ? 32 : 64; }
153            }
154        }
155        at += msglen;
156    }
157    free(buf);
158    if (*n == 0) { *err = mesh_xstrdup("sysctl NET_RT_IFLIST2 lists no interface"); return -1; }
159    return 0;
160}
161#else
162int mesh_nic_read(mesh_nic_raw **out, size_t *n, char **err) {
163    *out = NULL;
164    *n = 0;
165    *err = mesh_xstrdup("interface counters: not implemented on this OS");
166    return -1;
167}
168#endif
169
170int mesh_nic_iface_of(const char *ip, char name[MESH_NIC_NAME_MAX], char **err) {
171    unsigned char want[16];
172    int fam = AF_INET;
173    if (inet_pton(AF_INET, ip, want) != 1) {
174        fam = AF_INET6;
175        if (inet_pton(AF_INET6, ip, want) != 1) { *err = mesh_buf_fmt("invalid IP address syntax: %s", ip); return -1; }
176    }
177    struct ifaddrs *list = NULL;
178    if (getifaddrs(&list) != 0) { *err = mesh_buf_fmt("getifaddrs: %s", strerror(errno)); return -1; }
179    int found = 0;
180    for (struct ifaddrs *a = list; a && !found; a = a->ifa_next) {
181        if (!a->ifa_addr || a->ifa_addr->sa_family != fam) continue;
182        const void *addr = fam == AF_INET ? (const void *)&((struct sockaddr_in *)(void *)a->ifa_addr)->sin_addr
183                                          : (const void *)&((struct sockaddr_in6 *)(void *)a->ifa_addr)->sin6_addr;
184        if (memcmp(addr, want, fam == AF_INET ? 4 : 16) == 0) {
185            snprintf(name, MESH_NIC_NAME_MAX, "%s", a->ifa_name);
186            found = 1;
187        }
188    }
189    freeifaddrs(list);
190    if (!found) {
191        char canon[64];
192        inet_ntop(fam, want, canon, sizeof canon);
193        *err = mesh_buf_fmt("no interface holds %s", canon);
194        return -1;
195    }
196    return 0;
197}
198
199void mesh_nics_sample(mesh_nics *s, const mesh_nic_raw *raw, size_t n, mesh_nic_now **now, size_t *nnow) {
200    int first = s->n == 0;
201    *now = mesh_xcalloc(n ? n : 1, sizeof **now);
202    *nnow = n;
203    uint32_t counted = 0;
204    char *seen = mesh_xcalloc(s->n ? s->n : 1, 1);
205    for (size_t i = 0; i < n; i++) {
206        const mesh_nic_raw *r = &raw[i];
207        int include = !mesh_nic_virtual((const uint8_t *)r->name, strlen(r->name));
208        counted += (uint32_t)include;
209        mesh_nic_entry_ *e = NULL;
210        for (size_t j = 0; j < s->n; j++) if (strcmp(s->ext[j].name, r->name) == 0) { e = &s->ext[j]; seen[j] = 1; break; }
211        if (e) {
212            for (int c = 0; c < 8; c++) {
213                uint64_t d = mesh_nic_ext_update(&e->e[c], r->v[c], r->bits[c]);
214                if (include) s->total[c] += d;
215            }
216        } else {
217            s->ext = mesh_xrealloc(s->ext, (s->n + 1) * sizeof *s->ext);
218            seen = mesh_xrealloc(seen, s->n + 1);
219            seen[s->n] = 1;
220            e = &s->ext[s->n++];
221            snprintf(e->name, sizeof e->name, "%s", r->name);
222            for (int c = 0; c < 8; c++) e->e[c] = mesh_nic_ext_new(r->v[c]);
223            if (include && first) for (int c = 0; c < 8; c++) s->total[c] += r->v[c];
224        }
225        snprintf((*now)[i].name, sizeof (*now)[i].name, "%s", r->name);
226        for (int c = 0; c < 8; c++) (*now)[i].v[c] = e->e[c].value;
227    }
228    /* Interfaces that vanished: forget them (a re-created one starts afresh). */
229    size_t k = 0;
230    for (size_t j = 0; j < s->n; j++) if (seen[j]) s->ext[k++] = s->ext[j];
231    s->n = k;
232    free(seen);
233    s->counted = counted;
234}
235
236const uint64_t *mesh_nic_lookup(const mesh_nic_now *now, size_t n, const char *name) {
237    for (size_t i = 0; i < n; i++) if (strcmp(now[i].name, name) == 0) return now[i].v;
238    return NULL;
239}
240
241void mesh_nics_free(mesh_nics *s) {
242    free(s->ext);
243    s->ext = NULL;
244    s->n = 0;
245}
246
247void mesh_nic_record(uint8_t out[MESH_NIC_BYTES], const char *name, const uint64_t v[8], uint32_t links, uint32_t count) {
248    memset(out, 0, MESH_NIC_BYTES);
249    size_t n = strlen(name);
250    if (n > 15) n = 15;
251    memcpy(out + MESH_NI_NAME, name, n);
252    for (int i = 0; i < 8; i++)
253        for (int b = 0; b < 8; b++) out[MESH_NIC_COUNTERS[i] + (size_t)b] = (uint8_t)(v[i] >> (8 * b));
254    for (int b = 0; b < 4; b++) {
255        out[MESH_NI_LINKS + (size_t)b] = (uint8_t)(links >> (8 * b));
256        out[MESH_NI_COUNT + (size_t)b] = (uint8_t)(count >> (8 * b));
257    }
258}