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