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 * 167 openifstat(void) 168 { 169 return (subwin(stdscr, LINES-3-1, 0, MAINWIN_ROW, 0)); 170 } 171 172 void 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 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 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 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 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 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 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 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 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 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 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 506 clearifstat(void) 507 { 508 509 showifstat(); 510 refresh(); 511 werase(wnd); 512 labelifstat(); 513 needclear = 0; 514 } 515