==> 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}