1 // SPDX-License-Identifier: GPL-2.0
2 #include <inttypes.h>
3 #include <pthread.h>
4 #include <stdio.h>
5 #include "../../../../../include/linux/compiler.h"
6 #include "../../../../../include/linux/kernel.h"
7 #include "aolib.h"
8
9 struct netstat_counter {
10 uint64_t val;
11 char *name;
12 };
13
14 struct netstat {
15 char *header_name;
16 struct netstat *next;
17 size_t counters_nr;
18 struct netstat_counter *counters;
19 };
20
lookup_type(struct netstat * ns,const char * type,size_t len)21 static struct netstat *lookup_type(struct netstat *ns,
22 const char *type, size_t len)
23 {
24 while (ns != NULL) {
25 size_t cmp = max(len, strlen(ns->header_name));
26
27 if (!strncmp(ns->header_name, type, cmp))
28 return ns;
29 ns = ns->next;
30 }
31 return NULL;
32 }
33
lookup_get(struct netstat * ns,const char * type,const size_t len)34 static struct netstat *lookup_get(struct netstat *ns,
35 const char *type, const size_t len)
36 {
37 struct netstat *ret;
38
39 ret = lookup_type(ns, type, len);
40 if (ret != NULL)
41 return ret;
42
43 ret = malloc(sizeof(struct netstat));
44 if (!ret)
45 test_error("malloc()");
46
47 ret->header_name = strndup(type, len);
48 if (ret->header_name == NULL)
49 test_error("strndup()");
50 ret->next = ns;
51 ret->counters_nr = 0;
52 ret->counters = NULL;
53
54 return ret;
55 }
56
lookup_get_column(struct netstat * ns,const char * line)57 static struct netstat *lookup_get_column(struct netstat *ns, const char *line)
58 {
59 char *column;
60
61 column = strchr(line, ':');
62 if (!column)
63 test_error("can't parse netstat file");
64
65 return lookup_get(ns, line, column - line);
66 }
67
netstat_read_type(FILE * fnetstat,struct netstat ** dest,char * line)68 static void netstat_read_type(FILE *fnetstat, struct netstat **dest, char *line)
69 {
70 struct netstat *type = lookup_get_column(*dest, line);
71 const char *pos = line;
72 size_t i, nr_elems = 0;
73 char tmp;
74
75 while ((pos = strchr(pos, ' '))) {
76 nr_elems++;
77 pos++;
78 }
79
80 *dest = type;
81 type->counters = reallocarray(type->counters,
82 type->counters_nr + nr_elems,
83 sizeof(struct netstat_counter));
84 if (!type->counters)
85 test_error("reallocarray()");
86
87 pos = strchr(line, ' ') + 1;
88
89 if (fscanf(fnetstat, "%[^ :]", type->header_name) == EOF)
90 test_error("fscanf(%s)", type->header_name);
91 if (fread(&tmp, 1, 1, fnetstat) != 1 || tmp != ':')
92 test_error("Unexpected netstat format (%c)", tmp);
93
94 for (i = type->counters_nr; i < type->counters_nr + nr_elems; i++) {
95 struct netstat_counter *nc = &type->counters[i];
96 const char *new_pos = strchr(pos, ' ');
97 const char *fmt = " %" PRIu64;
98
99 if (new_pos == NULL)
100 new_pos = strchr(pos, '\n');
101
102 nc->name = strndup(pos, new_pos - pos);
103 if (nc->name == NULL)
104 test_error("strndup()");
105
106 if (unlikely(!strcmp(nc->name, "MaxConn")))
107 fmt = " %" PRId64; /* MaxConn is signed, RFC 2012 */
108 if (fscanf(fnetstat, fmt, &nc->val) != 1)
109 test_error("fscanf(%s)", nc->name);
110 pos = new_pos + 1;
111 }
112 type->counters_nr += nr_elems;
113
114 if (fread(&tmp, 1, 1, fnetstat) != 1 || tmp != '\n')
115 test_error("Unexpected netstat format");
116 }
117
118 static const char *snmp6_name = "Snmp6";
snmp6_read(FILE * fnetstat,struct netstat ** dest)119 static void snmp6_read(FILE *fnetstat, struct netstat **dest)
120 {
121 struct netstat *type = lookup_get(*dest, snmp6_name, strlen(snmp6_name));
122 char *counter_name;
123 size_t i;
124
125 for (i = type->counters_nr;; i++) {
126 struct netstat_counter *nc;
127 uint64_t counter;
128
129 if (fscanf(fnetstat, "%ms", &counter_name) == EOF)
130 break;
131 if (fscanf(fnetstat, "%" PRIu64, &counter) == EOF)
132 test_error("Unexpected snmp6 format");
133 type->counters = reallocarray(type->counters, i + 1,
134 sizeof(struct netstat_counter));
135 if (!type->counters)
136 test_error("reallocarray()");
137 nc = &type->counters[i];
138 nc->name = counter_name;
139 nc->val = counter;
140 }
141 type->counters_nr = i;
142 *dest = type;
143 }
144
netstat_read(void)145 struct netstat *netstat_read(void)
146 {
147 struct netstat *ret = 0;
148 size_t line_sz = 0;
149 char *line = NULL;
150 FILE *fnetstat;
151
152 /*
153 * Opening thread-self instead of /proc/net/... as the latter
154 * points to /proc/self/net/ which instantiates thread-leader's
155 * net-ns, see:
156 * commit 155134fef2b6 ("Revert "proc: Point /proc/{mounts,net} at..")
157 */
158 errno = 0;
159 fnetstat = fopen("/proc/thread-self/net/netstat", "r");
160 if (fnetstat == NULL)
161 test_error("failed to open /proc/net/netstat");
162
163 while (getline(&line, &line_sz, fnetstat) != -1)
164 netstat_read_type(fnetstat, &ret, line);
165 fclose(fnetstat);
166
167 errno = 0;
168 fnetstat = fopen("/proc/thread-self/net/snmp", "r");
169 if (fnetstat == NULL)
170 test_error("failed to open /proc/net/snmp");
171
172 while (getline(&line, &line_sz, fnetstat) != -1)
173 netstat_read_type(fnetstat, &ret, line);
174 fclose(fnetstat);
175
176 errno = 0;
177 fnetstat = fopen("/proc/thread-self/net/snmp6", "r");
178 if (fnetstat == NULL)
179 test_error("failed to open /proc/net/snmp6");
180
181 snmp6_read(fnetstat, &ret);
182 fclose(fnetstat);
183
184 free(line);
185 return ret;
186 }
187
netstat_free(struct netstat * ns)188 void netstat_free(struct netstat *ns)
189 {
190 while (ns != NULL) {
191 struct netstat *prev = ns;
192 size_t i;
193
194 free(ns->header_name);
195 for (i = 0; i < ns->counters_nr; i++)
196 free(ns->counters[i].name);
197 free(ns->counters);
198 ns = ns->next;
199 free(prev);
200 }
201 }
202
203 static inline void
__netstat_print_diff(uint64_t a,struct netstat * nsb,size_t i)204 __netstat_print_diff(uint64_t a, struct netstat *nsb, size_t i)
205 {
206 if (unlikely(!strcmp(nsb->header_name, "MaxConn"))) {
207 test_print("%8s %25s: %" PRId64 " => %" PRId64,
208 nsb->header_name, nsb->counters[i].name,
209 a, nsb->counters[i].val);
210 return;
211 }
212
213 test_print("%8s %25s: %" PRIu64 " => %" PRIu64, nsb->header_name,
214 nsb->counters[i].name, a, nsb->counters[i].val);
215 }
216
netstat_print_diff(struct netstat * nsa,struct netstat * nsb)217 void netstat_print_diff(struct netstat *nsa, struct netstat *nsb)
218 {
219 size_t i, j;
220
221 while (nsb != NULL) {
222 if (unlikely(strcmp(nsb->header_name, nsa->header_name))) {
223 for (i = 0; i < nsb->counters_nr; i++)
224 __netstat_print_diff(0, nsb, i);
225 nsb = nsb->next;
226 continue;
227 }
228
229 if (nsb->counters_nr < nsa->counters_nr)
230 test_error("Unexpected: some counters disappeared!");
231
232 for (j = 0, i = 0; i < nsb->counters_nr; i++) {
233 if (strcmp(nsb->counters[i].name, nsa->counters[j].name)) {
234 __netstat_print_diff(0, nsb, i);
235 continue;
236 }
237
238 if (nsa->counters[j].val == nsb->counters[i].val) {
239 j++;
240 continue;
241 }
242
243 __netstat_print_diff(nsa->counters[j].val, nsb, i);
244 j++;
245 }
246 if (j != nsa->counters_nr)
247 test_error("Unexpected: some counters disappeared!");
248
249 nsb = nsb->next;
250 nsa = nsa->next;
251 }
252 }
253
netstat_get(struct netstat * ns,const char * name,bool * not_found)254 uint64_t netstat_get(struct netstat *ns, const char *name, bool *not_found)
255 {
256 if (not_found)
257 *not_found = false;
258
259 while (ns != NULL) {
260 size_t i;
261
262 for (i = 0; i < ns->counters_nr; i++) {
263 if (!strcmp(name, ns->counters[i].name))
264 return ns->counters[i].val;
265 }
266
267 ns = ns->next;
268 }
269
270 if (not_found)
271 *not_found = true;
272 return 0;
273 }
274