1/* $NetBSD: ifstat.c,v 1.4 2016/08/05 07:22:17 christos Exp $ */ 2 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 * $FreeBSD: releng/10.1/usr.bin/systat/ifstat.c 247037 2013-02-20 14:19:09Z melifaro $ 31 */ 32 33#include <sys/cdefs.h> 34#ifndef lint 35__RCSID("$NetBSD: ifstat.c,v 1.4 2016/08/05 07:22:17 christos Exp $"); 36#endif /* not lint */ 37 38#include <sys/types.h> 39#include <sys/socket.h> 40#include <sys/sysctl.h> 41#include <sys/time.h> 42#include <net/if.h> 43 44#include <ifaddrs.h> 45#include <stdlib.h> 46#include <string.h> 47#include <err.h> 48#include <errno.h> 49#include <fnmatch.h> 50 51#include "systat.h" 52#include "extern.h" 53#include "convtbl.h" 54 55 /* Column numbers */ 56 57#define C1 0 /* 0-19 */ 58#define C2 20 /* 20-39 */ 59#define C3 40 /* 40-59 */ 60#define C4 60 /* 60-80 */ 61#define C5 80 /* Used for label positioning. */ 62 63static const int col2 = C2; 64static const int col3 = C3; 65static const int col4 = C4; 66 67SLIST_HEAD(, if_stat) curlist; 68 69struct if_stat { 70 SLIST_ENTRY(if_stat) link; 71 char if_name[IF_NAMESIZE]; 72 struct ifdatareq if_mib; 73 struct timeval tv; 74 struct timeval tv_lastchanged; 75 u_long if_in_curtraffic; 76 u_long if_out_curtraffic; 77 u_long if_in_traffic_peak; 78 u_long if_out_traffic_peak; 79 u_long if_in_curpps; 80 u_long if_out_curpps; 81 u_long if_in_pps_peak; 82 u_long if_out_pps_peak; 83 u_int if_row; /* Index into ifmib sysctl */ 84 u_int if_ypos; /* 0 if not being displayed */ 85 u_int display; 86 u_int match; 87}; 88 89extern int curscale; 90extern char *matchline; 91extern int showpps; 92extern int needsort; 93 94static int needclear = 0; 95 96static void right_align_string(struct if_stat *); 97static void getifmibdata(const int, struct ifdatareq *); 98static void sort_interface_list(void); 99static u_int getifnum(void); 100 101#define IFSTAT_ERR(n, s) do { \ 102 putchar('\014'); \ 103 closeifstat(wnd); \ 104 err((n), (s)); \ 105} while (0) 106 107#define TOPLINE 5 108#define TOPLABEL \ 109" Interface Traffic Peak Total" 110 111#define STARTING_ROW (TOPLINE + 1) 112#define ROW_SPACING (3) 113 114#define IN_col2 (showpps ? ifp->if_in_curpps : ifp->if_in_curtraffic) 115#define OUT_col2 (showpps ? ifp->if_out_curpps : ifp->if_out_curtraffic) 116#define IN_col3 (showpps ? \ 117 ifp->if_in_pps_peak : ifp->if_in_traffic_peak) 118#define OUT_col3 (showpps ? \ 119 ifp->if_out_pps_peak : ifp->if_out_traffic_peak) 120#define IN_col4 (showpps ? \ 121 ifp->if_mib.ifdr_data.ifi_ipackets : ifp->if_mib.ifdr_data.ifi_ibytes) 122#define OUT_col4 (showpps ? \ 123 ifp->if_mib.ifdr_data.ifi_opackets : ifp->if_mib.ifdr_data.ifi_obytes) 124 125#define EMPTY_COLUMN " " 126#define CLEAR_COLUMN(y, x) mvprintw((y), (x), "%20s", EMPTY_COLUMN); 127 128#define DOPUTRATE(c, r, d) do { \ 129 CLEAR_COLUMN(r, c); \ 130 if (showpps) { \ 131 mvprintw(r, (c), "%10.3f %cp%s ", \ 132 convert(d##_##c, curscale), \ 133 *get_string(d##_##c, curscale), \ 134 "/s"); \ 135 } \ 136 else { \ 137 mvprintw(r, (c), "%10.3f %s%s ", \ 138 convert(d##_##c, curscale), \ 139 get_string(d##_##c, curscale), \ 140 "/s"); \ 141 } \ 142} while (0) 143 144#define DOPUTTOTAL(c, r, d) do { \ 145 CLEAR_COLUMN((r), (c)); \ 146 if (showpps) { \ 147 mvprintw((r), (c), "%12.3f %cp ", \ 148 convert(d##_##c, SC_AUTO), \ 149 *get_string(d##_##c, SC_AUTO)); \ 150 } \ 151 else { \ 152 mvprintw((r), (c), "%12.3f %s ", \ 153 convert(d##_##c, SC_AUTO), \ 154 get_string(d##_##c, SC_AUTO)); \ 155 } \ 156} while (0) 157 158#define PUTRATE(c, r) do { \ 159 DOPUTRATE(c, (r), IN); \ 160 DOPUTRATE(c, (r)+1, OUT); \ 161} while (0) 162 163#define PUTTOTAL(c, r) do { \ 164 DOPUTTOTAL(c, (r), IN); \ 165 DOPUTTOTAL(c, (r)+1, OUT); \ 166} while (0) 167 168#define PUTNAME(p) do { \ 169 mvprintw(p->if_ypos, 0, "%s", p->if_name); \ 170 mvprintw(p->if_ypos, col2-3, "%s", (const char *)"in"); \ 171 mvprintw(p->if_ypos+1, col2-3, "%s", (const char *)"out"); \ 172} while (0) 173 174WINDOW * 175openifstat(void) 176{ 177 return (subwin(stdscr, -1, 0, 5, 0)); 178} 179 180void 181closeifstat(WINDOW *w) 182{ 183 struct if_stat *node = NULL; 184 185 while (!SLIST_EMPTY(&curlist)) { 186 node = SLIST_FIRST(&curlist); 187 SLIST_REMOVE_HEAD(&curlist, link); 188 free(node); 189 } 190 191 if (w != NULL) { 192 wclear(w); 193 wrefresh(w); 194 delwin(w); 195 } 196 197 return; 198} 199 200void 201labelifstat(void) 202{ 203 204 wmove(wnd, TOPLINE, 0); 205 wclrtoeol(wnd); 206 mvprintw(TOPLINE, 0, "%s", TOPLABEL); 207 208 return; 209} 210 211void 212showifstat(void) 213{ 214 struct if_stat *ifp = NULL; 215 216 SLIST_FOREACH(ifp, &curlist, link) { 217 if (ifp->display == 0 || (ifp->match == 0) || 218 ifp->if_ypos > (u_int)(LINES - 3 - 1)) 219 continue; 220 PUTNAME(ifp); 221 PUTRATE(col2, ifp->if_ypos); 222 PUTRATE(col3, ifp->if_ypos); 223 PUTTOTAL(col4, ifp->if_ypos); 224 } 225 226 return; 227} 228 229int 230initifstat(void) 231{ 232 struct if_stat *p = NULL; 233 u_int n = 0, i = 0; 234 235 n = getifnum(); 236 if (n <= 0) 237 return (-1); 238 239 SLIST_INIT(&curlist); 240 241 for (i = 0; i < n; i++) { 242 p = (struct if_stat *)calloc(1, sizeof(struct if_stat)); 243 if (p == NULL) 244 IFSTAT_ERR(1, "out of memory"); 245 SLIST_INSERT_HEAD(&curlist, p, link); 246 p->if_row = i+1; 247 getifmibdata(p->if_row, &p->if_mib); 248 right_align_string(p); 249 p->match = 1; 250 251 /* 252 * Initially, we only display interfaces that have 253 * received some traffic. 254 */ 255 if (p->if_mib.ifdr_data.ifi_ibytes != 0) 256 p->display = 1; 257 } 258 259 sort_interface_list(); 260 261 return (1); 262} 263 264void 265fetchifstat(void) 266{ 267 struct if_stat *ifp = NULL; 268 struct timeval tv, new_tv, old_tv; 269 double elapsed = 0.0; 270 u_int new_inb, new_outb, old_inb, old_outb = 0; 271 u_int new_inp, new_outp, old_inp, old_outp = 0; 272 273 SLIST_FOREACH(ifp, &curlist, link) { 274 /* 275 * Grab a copy of the old input/output values before we 276 * call getifmibdata(). 277 */ 278 old_inb = ifp->if_mib.ifdr_data.ifi_ibytes; 279 old_outb = ifp->if_mib.ifdr_data.ifi_obytes; 280 old_inp = ifp->if_mib.ifdr_data.ifi_ipackets; 281 old_outp = ifp->if_mib.ifdr_data.ifi_opackets; 282 TIMESPEC_TO_TIMEVAL(&ifp->tv_lastchanged, &ifp->if_mib.ifdr_data.ifi_lastchange); 283 284 (void)gettimeofday(&new_tv, NULL); 285 (void)getifmibdata(ifp->if_row, &ifp->if_mib); 286 287 new_inb = ifp->if_mib.ifdr_data.ifi_ibytes; 288 new_outb = ifp->if_mib.ifdr_data.ifi_obytes; 289 new_inp = ifp->if_mib.ifdr_data.ifi_ipackets; 290 new_outp = ifp->if_mib.ifdr_data.ifi_opackets; 291 292 /* Display interface if it's received some traffic. */ 293 if (new_inb > 0 && old_inb == 0) { 294 ifp->display = 1; 295 needsort = 1; 296 } 297 298 /* 299 * The rest is pretty trivial. Calculate the new values 300 * for our current traffic rates, and while we're there, 301 * see if we have new peak rates. 302 */ 303 old_tv = ifp->tv; 304 timersub(&new_tv, &old_tv, &tv); 305 elapsed = tv.tv_sec + (tv.tv_usec * 1e-6); 306 307 ifp->if_in_curtraffic = new_inb - old_inb; 308 ifp->if_out_curtraffic = new_outb - old_outb; 309 310 ifp->if_in_curpps = new_inp - old_inp; 311 ifp->if_out_curpps = new_outp - old_outp; 312 313 /* 314 * Rather than divide by the time specified on the comm- 315 * and line, we divide by ``elapsed'' as this is likely 316 * to be more accurate. 317 */ 318 ifp->if_in_curtraffic /= elapsed; 319 ifp->if_out_curtraffic /= elapsed; 320 ifp->if_in_curpps /= elapsed; 321 ifp->if_out_curpps /= elapsed; 322 323 if (ifp->if_in_curtraffic > ifp->if_in_traffic_peak) 324 ifp->if_in_traffic_peak = ifp->if_in_curtraffic; 325 326 if (ifp->if_out_curtraffic > ifp->if_out_traffic_peak) 327 ifp->if_out_traffic_peak = ifp->if_out_curtraffic; 328 329 if (ifp->if_in_curpps > ifp->if_in_pps_peak) 330 ifp->if_in_pps_peak = ifp->if_in_curpps; 331 332 if (ifp->if_out_curpps > ifp->if_out_pps_peak) 333 ifp->if_out_pps_peak = ifp->if_out_curpps; 334 335 ifp->tv.tv_sec = new_tv.tv_sec; 336 ifp->tv.tv_usec = new_tv.tv_usec; 337 338 } 339 340 if (needsort) 341 sort_interface_list(); 342 343 return; 344} 345 346/* 347 * We want to right justify our interface names against the first column 348 * (first sixteen or so characters), so we need to do some alignment. 349 */ 350static void 351right_align_string(struct if_stat *ifp) 352{ 353 if (ifp == NULL || ifp->if_mib.ifdr_name[0] == '\0') 354 return; 355 356 snprintf(ifp->if_name, IF_NAMESIZE, "%*s", IF_NAMESIZE - 1, 357 ifp->if_mib.ifdr_name); 358} 359 360static int 361check_match(const char *ifname) 362{ 363 char *p = matchline, *c, t; 364 int match = 0, mlen; 365 366 if (matchline == NULL) 367 return (0); 368 369 /* Strip leading whitespaces */ 370 while (*p == ' ') 371 p ++; 372 373 c = p; 374 while ((mlen = strcspn(c, " ;,")) != 0) { 375 p = c + mlen; 376 t = *p; 377 if (p - c > 0) { 378 *p = '\0'; 379 if (fnmatch(c, ifname, FNM_CASEFOLD) == 0) { 380 *p = t; 381 return (1); 382 } 383 *p = t; 384 c = p + strspn(p, " ;,"); 385 } 386 else { 387 c = p + strspn(p, " ;,"); 388 } 389 } 390 391 return (match); 392} 393 394/* 395 * This function iterates through our list of interfaces, identifying 396 * those that are to be displayed (ifp->display = 1). For each interf- 397 * rface that we're displaying, we generate an appropriate position for 398 * it on the screen (ifp->if_ypos). 399 * 400 * This function is called any time a change is made to an interface's 401 * ``display'' state. 402 */ 403void 404sort_interface_list(void) 405{ 406 struct if_stat *ifp = NULL; 407 u_int y = STARTING_ROW; 408 409 SLIST_FOREACH(ifp, &curlist, link) { 410 if (matchline && !check_match(ifp->if_mib.ifdr_name)) 411 ifp->match = 0; 412 else 413 ifp->match = 1; 414 if (ifp->display && ifp->match) { 415 ifp->if_ypos = y; 416 y += ROW_SPACING; 417 } 418 } 419 420 needsort = 0; 421 needclear = 1; 422} 423 424static 425unsigned int 426getifnum(void) 427{ 428 struct ifaddrs *ifaddrs = NULL; 429 struct ifaddrs *ifa = NULL; 430 int num = 0; 431 432 if (getifaddrs(&ifaddrs) == 0) { 433 for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { 434 if (ifa->ifa_addr && 435 ifa->ifa_addr->sa_family == AF_LINK) 436 num++; 437 } 438 439 freeifaddrs(ifaddrs); 440 } 441 442 return num; 443} 444 445static void 446getifmibdata(int row, struct ifdatareq *data) 447{ 448 struct ifaddrs *ifaddrs = NULL; 449 struct ifaddrs *ifa = NULL; 450 int found = 0; 451 int num = 0; 452 453 if (getifaddrs(&ifaddrs) != 0) { 454 IFSTAT_ERR(2, "getifmibdata() error getting interface data"); 455 return; 456 } 457 458 for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { 459 if (ifa->ifa_addr && 460 ifa->ifa_addr->sa_family == AF_LINK) { 461 num++; 462 463 /* 464 * expecting rows to start with 1 not 0, 465 * see freebsd "man ifmib" 466 */ 467 if (num == row) { 468 found = 1; 469 data->ifdr_data = *(struct if_data *)ifa->ifa_data; 470 strncpy(data->ifdr_name, ifa->ifa_name, IF_NAMESIZE); 471 break; 472 } 473 } 474 } 475 476 freeifaddrs(ifaddrs); 477 478 if (!found) { 479 IFSTAT_ERR(2, "getifmibdata() error finding row"); 480 } 481} 482 483int 484cmdifstat(const char *cmd, const char *args) 485{ 486 int retval = 0; 487 488 retval = ifcmd(cmd, args); 489 /* ifcmd() returns 1 on success */ 490 if (retval == 1) { 491 showifstat(); 492 refresh(); 493 if (needclear) { 494 werase(wnd); 495 labelifstat(); 496 needclear = 0; 497 } 498 } 499 500 return (retval); 501} 502 503void 504ifstat_scale(char* args) 505{ 506 cmdifstat("scale", args ? args : ""); 507} 508 509void 510ifstat_pps(char* args) 511{ 512 cmdifstat("pps", ""); 513} 514 515void 516ifstat_match(char* args) 517{ 518 cmdifstat("match", args ? args : ""); 519 520 /* 521 * force erase after match command because it is possible for 522 * another command to be sent in the interval before the window 523 * finishes redrawing completely. That stale data remains in window 524 * and never gets overwritten because there are fewer interfaces 525 * being drawn on screen. Only an issue for match command because 526 * pps and scale don't change the number of interfaces being drawn. 527 */ 528 werase(wnd); 529 labelifstat(); 530} 531