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