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