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