1 /*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 2003, Trent Nelson, <trent@arpa.com>.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTIFSTAT_ERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <sys/queue.h>
34 #include <sys/sysctl.h>
35 #include <net/if.h>
36 #include <net/if_mib.h>
37
38 #include <stdbool.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <err.h>
42 #include <errno.h>
43 #include <fnmatch.h>
44
45 #include "systat.h"
46 #include "extern.h"
47 #include "convtbl.h"
48
49 /* Column numbers */
50
51 #define C1 0 /* 0-19 */
52 #define C2 20 /* 20-39 */
53 #define C3 40 /* 40-59 */
54 #define C4 60 /* 60-80 */
55 #define C5 80 /* Used for label positioning. */
56
57 static const int col2 = C2;
58 static const int col3 = C3;
59 static const int col4 = C4;
60
61 static SLIST_HEAD(, if_stat) curlist;
62
63 struct if_stat {
64 SLIST_ENTRY(if_stat) link;
65 char display_name[IF_NAMESIZE];
66 char dev_name[IFNAMSIZ]; /* copied from ifmibdata */
67 struct ifmibdata if_mib;
68 struct timeval tv;
69 struct timeval tv_lastchanged;
70 uint64_t if_in_curtraffic;
71 uint64_t if_out_curtraffic;
72 uint64_t if_in_traffic_peak;
73 uint64_t if_out_traffic_peak;
74 uint64_t if_in_curpps;
75 uint64_t if_out_curpps;
76 uint64_t if_in_pps_peak;
77 uint64_t if_out_pps_peak;
78 u_int if_row; /* Index into ifmib sysctl */
79 int if_ypos; /* -1 if not being displayed */
80 bool display;
81 u_int match;
82 };
83
84 static int needclear = 0;
85 static bool displayall = false;
86
87 static void format_device_name(struct if_stat *);
88 static int getifmibdata(const int, struct ifmibdata *);
89 static void sort_interface_list(void);
90 static u_int getifnum(void);
91 static void clearifstat(void);
92
93 #define IFSTAT_ERR(n, s) do { \
94 putchar('\014'); \
95 closeifstat(wnd); \
96 err((n), (s)); \
97 } while (0)
98
99 #define TOPLINE 3
100 #define TOPLABEL \
101 " Interface Traffic Peak Total"
102
103 #define STARTING_ROW (TOPLINE + 1)
104 #define ROW_SPACING (3)
105
106 #define IN_col2 (showpps ? ifp->if_in_curpps : ifp->if_in_curtraffic)
107 #define OUT_col2 (showpps ? ifp->if_out_curpps : ifp->if_out_curtraffic)
108 #define IN_col3 (showpps ? \
109 ifp->if_in_pps_peak : ifp->if_in_traffic_peak)
110 #define OUT_col3 (showpps ? \
111 ifp->if_out_pps_peak : ifp->if_out_traffic_peak)
112 #define IN_col4 (showpps ? \
113 ifp->if_mib.ifmd_data.ifi_ipackets : ifp->if_mib.ifmd_data.ifi_ibytes)
114 #define OUT_col4 (showpps ? \
115 ifp->if_mib.ifmd_data.ifi_opackets : ifp->if_mib.ifmd_data.ifi_obytes)
116
117 #define EMPTY_COLUMN " "
118 #define CLEAR_COLUMN(y, x) mvprintw((y), (x), "%20s", EMPTY_COLUMN);
119
120 #define DOPUTRATE(c, r, d) do { \
121 CLEAR_COLUMN(r, c); \
122 if (showpps) { \
123 mvprintw(r, (c), "%10.3f %cp%s ", \
124 convert(d##_##c, curscale), \
125 *get_string(d##_##c, curscale), \
126 "/s"); \
127 } \
128 else { \
129 mvprintw(r, (c), "%10.3f %s%s ", \
130 convert(d##_##c, curscale), \
131 get_string(d##_##c, curscale), \
132 "/s"); \
133 } \
134 } while (0)
135
136 #define DOPUTTOTAL(c, r, d) do { \
137 CLEAR_COLUMN((r), (c)); \
138 if (showpps) { \
139 mvprintw((r), (c), "%12.3f %cp ", \
140 convert(d##_##c, SC_AUTO), \
141 *get_string(d##_##c, SC_AUTO)); \
142 } \
143 else { \
144 mvprintw((r), (c), "%12.3f %s ", \
145 convert(d##_##c, SC_AUTO), \
146 get_string(d##_##c, SC_AUTO)); \
147 } \
148 } while (0)
149
150 #define PUTRATE(c, r) do { \
151 DOPUTRATE(c, (r), IN); \
152 DOPUTRATE(c, (r)+1, OUT); \
153 } while (0)
154
155 #define PUTTOTAL(c, r) do { \
156 DOPUTTOTAL(c, (r), IN); \
157 DOPUTTOTAL(c, (r)+1, OUT); \
158 } while (0)
159
160 #define PUTNAME(p) do { \
161 mvprintw(p->if_ypos, 0, "%s", p->display_name); \
162 mvprintw(p->if_ypos, col2-3, "%s", (const char *)"in"); \
163 mvprintw(p->if_ypos+1, col2-3, "%s", (const char *)"out"); \
164 } while (0)
165
166 WINDOW *
openifstat(void)167 openifstat(void)
168 {
169 return (subwin(stdscr, LINES-3-1, 0, MAINWIN_ROW, 0));
170 }
171
172 void
closeifstat(WINDOW * w)173 closeifstat(WINDOW *w)
174 {
175 struct if_stat *node = NULL;
176
177 while (!SLIST_EMPTY(&curlist)) {
178 node = SLIST_FIRST(&curlist);
179 SLIST_REMOVE_HEAD(&curlist, link);
180 free(node);
181 }
182
183 if (w != NULL) {
184 wclear(w);
185 wrefresh(w);
186 delwin(w);
187 }
188
189 return;
190 }
191
192 void
labelifstat(void)193 labelifstat(void)
194 {
195
196 wmove(wnd, TOPLINE, 0);
197 wclrtoeol(wnd);
198 mvprintw(TOPLINE, 0, "%s", TOPLABEL);
199
200 return;
201 }
202
203 void
showifstat(void)204 showifstat(void)
205 {
206 struct if_stat *ifp = NULL;
207
208 SLIST_FOREACH(ifp, &curlist, link) {
209 if (ifp->if_ypos < LINES - 3 && ifp->if_ypos != -1) {
210 if (!ifp->display || ifp->match == 0) {
211 wmove(wnd, ifp->if_ypos, 0);
212 wclrtoeol(wnd);
213 wmove(wnd, ifp->if_ypos + 1, 0);
214 wclrtoeol(wnd);
215 } else {
216 PUTNAME(ifp);
217 PUTRATE(col2, ifp->if_ypos);
218 PUTRATE(col3, ifp->if_ypos);
219 PUTTOTAL(col4, ifp->if_ypos);
220 }
221 }
222 }
223
224 return;
225 }
226
227 int
initifstat(void)228 initifstat(void)
229 {
230 struct if_stat *p = NULL;
231 u_int n, i;
232
233 n = getifnum();
234 if (n <= 0)
235 return (-1);
236
237 SLIST_INIT(&curlist);
238
239 for (i = 0; i < n; i++) {
240 p = (struct if_stat *)calloc(1, sizeof(struct if_stat));
241 if (p == NULL)
242 IFSTAT_ERR(1, "out of memory");
243 p->if_row = i+1;
244 if (getifmibdata(p->if_row, &p->if_mib) == -1) {
245 free(p);
246 continue;
247 }
248 SLIST_INSERT_HEAD(&curlist, p, link);
249 format_device_name(p);
250 p->match = 1;
251
252 /*
253 * Initially, we only display interfaces that have
254 * received some traffic unless display-all is on.
255 */
256 if (displayall || p->if_mib.ifmd_data.ifi_ibytes != 0)
257 p->display = true;
258 }
259
260 sort_interface_list();
261
262 return (1);
263 }
264
265 void
fetchifstat(void)266 fetchifstat(void)
267 {
268 struct if_stat *ifp = NULL, *temp_var;
269 struct timeval tv, new_tv, old_tv;
270 double elapsed = 0.0;
271 uint64_t new_inb, new_outb, old_inb, old_outb = 0;
272 uint64_t new_inp, new_outp, old_inp, old_outp = 0;
273
274 SLIST_FOREACH_SAFE(ifp, &curlist, link, temp_var) {
275 /*
276 * Grab a copy of the old input/output values before we
277 * call getifmibdata().
278 */
279 old_inb = ifp->if_mib.ifmd_data.ifi_ibytes;
280 old_outb = ifp->if_mib.ifmd_data.ifi_obytes;
281 old_inp = ifp->if_mib.ifmd_data.ifi_ipackets;
282 old_outp = ifp->if_mib.ifmd_data.ifi_opackets;
283 ifp->tv_lastchanged = ifp->if_mib.ifmd_data.ifi_lastchange;
284
285 (void)gettimeofday(&new_tv, NULL);
286 if (getifmibdata(ifp->if_row, &ifp->if_mib) == -1 ) {
287 /* if a device was removed */
288 SLIST_REMOVE(&curlist, ifp, if_stat, link);
289 free(ifp);
290 needsort = 1;
291 continue;
292 } else if (strcmp(ifp->dev_name, ifp->if_mib.ifmd_name) != 0 ) {
293 /* a device was removed and another one was added */
294 format_device_name(ifp);
295 /* clear to the current value for the new device */
296 old_inb = ifp->if_mib.ifmd_data.ifi_ibytes;
297 old_outb = ifp->if_mib.ifmd_data.ifi_obytes;
298 old_inp = ifp->if_mib.ifmd_data.ifi_ipackets;
299 old_outp = ifp->if_mib.ifmd_data.ifi_opackets;
300 needsort = 1;
301 }
302
303 new_inb = ifp->if_mib.ifmd_data.ifi_ibytes;
304 new_outb = ifp->if_mib.ifmd_data.ifi_obytes;
305 new_inp = ifp->if_mib.ifmd_data.ifi_ipackets;
306 new_outp = ifp->if_mib.ifmd_data.ifi_opackets;
307
308 /* Display interface if it's received some traffic. */
309 if (!ifp->display && new_inb > 0 && old_inb == 0) {
310 ifp->display = true;
311 needsort = 1;
312 }
313
314 /*
315 * The rest is pretty trivial. Calculate the new values
316 * for our current traffic rates, and while we're there,
317 * see if we have new peak rates.
318 */
319 old_tv = ifp->tv;
320 timersub(&new_tv, &old_tv, &tv);
321 elapsed = tv.tv_sec + (tv.tv_usec * 1e-6);
322
323 ifp->if_in_curtraffic = new_inb - old_inb;
324 ifp->if_out_curtraffic = new_outb - old_outb;
325
326 ifp->if_in_curpps = new_inp - old_inp;
327 ifp->if_out_curpps = new_outp - old_outp;
328
329 /*
330 * Rather than divide by the time specified on the comm-
331 * and line, we divide by ``elapsed'' as this is likely
332 * to be more accurate.
333 */
334 ifp->if_in_curtraffic /= elapsed;
335 ifp->if_out_curtraffic /= elapsed;
336 ifp->if_in_curpps /= elapsed;
337 ifp->if_out_curpps /= elapsed;
338
339 if (ifp->if_in_curtraffic > ifp->if_in_traffic_peak)
340 ifp->if_in_traffic_peak = ifp->if_in_curtraffic;
341
342 if (ifp->if_out_curtraffic > ifp->if_out_traffic_peak)
343 ifp->if_out_traffic_peak = ifp->if_out_curtraffic;
344
345 if (ifp->if_in_curpps > ifp->if_in_pps_peak)
346 ifp->if_in_pps_peak = ifp->if_in_curpps;
347
348 if (ifp->if_out_curpps > ifp->if_out_pps_peak)
349 ifp->if_out_pps_peak = ifp->if_out_curpps;
350
351 ifp->tv.tv_sec = new_tv.tv_sec;
352 ifp->tv.tv_usec = new_tv.tv_usec;
353
354 }
355
356 if (needsort)
357 sort_interface_list();
358
359 return;
360 }
361
362 /*
363 * We want to right justify our interface names against the first column
364 * (first sixteen or so characters), so we need to do some alignment.
365 * We save original name so that we can find a same spot is take by a
366 * different device.
367 */
368 static void
format_device_name(struct if_stat * ifp)369 format_device_name(struct if_stat *ifp)
370 {
371
372 if (ifp != NULL ) {
373 snprintf(ifp->display_name, IF_NAMESIZE, "%*s", IF_NAMESIZE-1,
374 ifp->if_mib.ifmd_name);
375 strcpy(ifp->dev_name, ifp->if_mib.ifmd_name);
376 }
377 }
378
379 static int
check_match(const char * ifname)380 check_match(const char *ifname)
381 {
382 char *p = matchline, *ch, t;
383 int match = 0, mlen;
384
385 if (matchline == NULL)
386 return (0);
387
388 /* Strip leading whitespaces */
389 while (*p == ' ')
390 p ++;
391
392 ch = p;
393 while ((mlen = strcspn(ch, " ;,")) != 0) {
394 p = ch + mlen;
395 t = *p;
396 if (p - ch > 0) {
397 *p = '\0';
398 if (fnmatch(ch, ifname, FNM_CASEFOLD) == 0) {
399 *p = t;
400 return (1);
401 }
402 *p = t;
403 ch = p + strspn(p, " ;,");
404 }
405 else {
406 ch = p + strspn(p, " ;,");
407 }
408 }
409
410 return (match);
411 }
412
413 /*
414 * This function iterates through our list of interfaces, identifying
415 * those that are to be displayed (ifp->display = 1). For each interf-
416 * rface that we're displaying, we generate an appropriate position for
417 * it on the screen (ifp->if_ypos).
418 *
419 * This function is called any time a change is made to an interface's
420 * ``display'' state.
421 */
422 void
sort_interface_list(void)423 sort_interface_list(void)
424 {
425 struct if_stat *ifp = NULL;
426 u_int y = 0;
427
428 y = STARTING_ROW;
429 SLIST_FOREACH(ifp, &curlist, link) {
430 if (matchline && !check_match(ifp->if_mib.ifmd_name))
431 ifp->match = 0;
432 else
433 ifp->match = 1;
434 if (ifp->display && ifp->match) {
435 ifp->if_ypos = y;
436 y += ROW_SPACING;
437 }
438 else
439 ifp->if_ypos = -1;
440 }
441
442 needsort = 0;
443 needclear = 1;
444 }
445
446 static
447 unsigned int
getifnum(void)448 getifnum(void)
449 {
450 u_int data = 0;
451 size_t datalen = 0;
452 static int name[] = { CTL_NET,
453 PF_LINK,
454 NETLINK_GENERIC,
455 IFMIB_SYSTEM,
456 IFMIB_IFCOUNT };
457
458 datalen = sizeof(data);
459 if (sysctl(name, 5, (void *)&data, (size_t *)&datalen, (void *)NULL,
460 (size_t)0) != 0)
461 IFSTAT_ERR(1, "sysctl error");
462 return (data);
463 }
464
465 static int
getifmibdata(int row,struct ifmibdata * data)466 getifmibdata(int row, struct ifmibdata *data)
467 {
468 int ret = 0;
469 size_t datalen = 0;
470 static int name[] = { CTL_NET,
471 PF_LINK,
472 NETLINK_GENERIC,
473 IFMIB_IFDATA,
474 0,
475 IFDATA_GENERAL };
476 datalen = sizeof(*data);
477 name[4] = row;
478
479 ret = sysctl(name, 6, (void *)data, (size_t *)&datalen, (void *)NULL,
480 (size_t)0);
481 if ((ret != 0) && (errno != ENOENT))
482 IFSTAT_ERR(2, "sysctl error getting interface data");
483
484 return (ret);
485 }
486
487 int
cmdifstat(const char * cmd,const char * args)488 cmdifstat(const char *cmd, const char *args)
489 {
490 int retval = 0;
491
492 retval = ifcmd(cmd, args);
493 /* ifcmd() returns 1 on success */
494 if (retval == 1) {
495 if (needclear)
496 clearifstat();
497 }
498 else if (prefix(cmd, "all")) {
499 retval = 1;
500 displayall = true;
501 }
502 return (retval);
503 }
504
505 static void
clearifstat(void)506 clearifstat(void)
507 {
508
509 showifstat();
510 refresh();
511 werase(wnd);
512 labelifstat();
513 needclear = 0;
514 }
515