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
57static const int col2 = C2;
58static const int col3 = C3;
59static const int col4 = C4;
60
61static SLIST_HEAD(, if_stat)		curlist;
62
63struct 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
84static	 int needclear = 0;
85static	 bool displayall = false;
86
87static	 void  format_device_name(struct if_stat *);
88static	 int   getifmibdata(const int, struct ifmibdata *);
89static	 void  sort_interface_list(void);
90static	 u_int getifnum(void);
91static	 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
166WINDOW *
167openifstat(void)
168{
169	return (subwin(stdscr, LINES-3-1, 0, MAINWIN_ROW, 0));
170}
171
172void
173closeifstat(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
192void
193labelifstat(void)
194{
195
196	wmove(wnd, TOPLINE, 0);
197	wclrtoeol(wnd);
198	mvprintw(TOPLINE, 0, "%s", TOPLABEL);
199
200	return;
201}
202
203void
204showifstat(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
227int
228initifstat(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
265void
266fetchifstat(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 */
368static void
369format_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
379static int
380check_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 */
422void
423sort_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
446static
447unsigned int
448getifnum(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
465static int
466getifmibdata(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
487int
488cmdifstat(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
505static void
506clearifstat(void)
507{
508
509	showifstat();
510	refresh();
511	werase(wnd);
512	labelifstat();
513	needclear = 0;
514}
515