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