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 29 #include <sys/types.h> 30 #include <sys/socket.h> 31 #include <sys/sysctl.h> 32 #include <net/if.h> 33 #include <net/if_mib.h> 34 35 #include <stdlib.h> 36 #include <string.h> 37 #include <unistd.h> 38 #include <err.h> 39 #include <errno.h> 40 41 #include <net/altq/altq.h> 42 #include <net/altq/altq_cbq.h> 43 #include <net/altq/altq_priq.h> 44 #include <net/altq/altq_hfsc.h> 45 #include <net/altq/altq_fairq.h> 46 47 #include <net/pf/pfvar.h> 48 49 #include "systat.h" 50 #include "extern.h" 51 #include "convtbl.h" 52 53 static SLIST_HEAD(, qcol) qcols; 54 static SLIST_HEAD(, if_stat) curlist; 55 static int pf_fd = -1; 56 static int qccols; 57 static int TopSection1; 58 static int TopSection2; 59 static int TopSection3; 60 61 struct qcol { 62 SLIST_ENTRY(qcol) link; 63 char *qname; 64 int col; 65 }; 66 67 typedef struct qcol qcol_t; 68 69 union class_stats { 70 class_stats_t cbq; 71 struct priq_classstats priq; 72 struct hfsc_classstats hfsc; 73 struct fairq_classstats fairq; 74 }; 75 76 struct queue_stats { 77 SLIST_ENTRY(queue_stats) link; 78 struct pfioc_altq pa; 79 struct pfioc_qstats pq; 80 qcol_t *qc; 81 union class_stats ostats; 82 union class_stats nstats; 83 }; 84 85 typedef struct queue_stats queue_stats_t; 86 87 struct if_stat { 88 SLIST_ENTRY(if_stat) link; 89 SLIST_HEAD(, queue_stats) queues; 90 char if_name[IF_NAMESIZE]; 91 struct ifmibdata if_mib; 92 struct timeval tv; 93 struct timeval tv_lastchanged; 94 u_long if_in_curtraffic; 95 u_long if_out_curtraffic; 96 u_long if_in_traffic_peak; 97 u_long if_out_traffic_peak; 98 u_int if_row; /* Index into ifmib sysctl */ 99 u_int row; /* display row (relative) */ 100 u_int display; 101 }; 102 103 typedef struct if_stat if_stat_t; 104 105 extern u_int curscale; 106 107 static void load_altqs(void); 108 static void print_altq(if_stat_t *p, queue_stats_t *q); 109 static void right_align_string(if_stat_t *); 110 static void getifmibdata(const int, struct ifmibdata *); 111 static void sort_interface_list(void); 112 static u_int getifnum(void); 113 114 #define IFSTAT_ERR(n, s) do { \ 115 putchar(''); \ 116 closealtqs(wnd); \ 117 err((n), (s)); \ 118 } while (0) 119 120 #define TOPLINE 1 121 #define TOPQSTART 20 122 #define TOPQWIDTH 10 123 124 WINDOW * 125 openaltqs(void) 126 { 127 if_stat_t *p = NULL; 128 u_int n = 0, i = 0; 129 130 pf_fd = open("/dev/pf", O_RDONLY); 131 132 n = getifnum(); /* NOTE: can return < 0 */ 133 134 SLIST_INIT(&curlist); 135 SLIST_INIT(&qcols); 136 for (i = 0; i < n; i++) { 137 p = (if_stat_t *)calloc(1, sizeof(if_stat_t)); 138 if (p == NULL) 139 IFSTAT_ERR(1, "out of memory"); 140 SLIST_INSERT_HEAD(&curlist, p, link); 141 SLIST_INIT(&p->queues); 142 p->if_row = i+1; 143 getifmibdata(p->if_row, &p->if_mib); 144 right_align_string(p); 145 146 /* 147 * Initially, we only display interfaces that have 148 * received some traffic. 149 */ 150 if (p->if_mib.ifmd_data.ifi_ibytes != 0) 151 p->display = 1; 152 } 153 load_altqs(); 154 155 sort_interface_list(); 156 157 return (subwin(stdscr, LINES-1-5, 0, 5, 0)); 158 } 159 160 void 161 closealtqs(WINDOW *w) 162 { 163 if_stat_t *node = NULL; 164 queue_stats_t *q; 165 166 while (!SLIST_EMPTY(&curlist)) { 167 node = SLIST_FIRST(&curlist); 168 SLIST_REMOVE_HEAD(&curlist, link); 169 while ((q = SLIST_FIRST(&node->queues)) != NULL) { 170 SLIST_REMOVE_HEAD(&node->queues, link); 171 free(q); 172 } 173 free(node); 174 } 175 while (!SLIST_EMPTY(&qcols)) { 176 qcol_t *qc = SLIST_FIRST(&qcols); 177 SLIST_REMOVE_HEAD(&qcols, link); 178 free(qc->qname); 179 free(qc); 180 } 181 qccols = 0; 182 183 if (w != NULL) { 184 wclear(w); 185 wrefresh(w); 186 delwin(w); 187 } 188 189 if (pf_fd >= 0) { 190 close(pf_fd); 191 pf_fd = -1; 192 } 193 194 return; 195 } 196 197 void 198 labelaltqs(void) 199 { 200 wmove(wnd, TOPLINE, 0); 201 wclrtoeol(wnd); 202 } 203 204 void 205 showaltqs(void) 206 { 207 if_stat_t *p = NULL; 208 queue_stats_t *q; 209 qcol_t *qc; 210 211 mvprintw(TopSection1, 0, " PACKETS"); 212 mvprintw(TopSection2, 0, " BYTES"); 213 mvprintw(TopSection3, 0, " DROPS/QLEN"); 214 SLIST_FOREACH(qc, &qcols, link) { 215 mvprintw(TopSection1, TOPQSTART + TOPQWIDTH * qc->col, 216 "%9s", qc->qname); 217 mvprintw(TopSection2, TOPQSTART + TOPQWIDTH * qc->col, 218 "%9s", qc->qname); 219 mvprintw(TopSection3, TOPQSTART + TOPQWIDTH * qc->col, 220 "%9s", qc->qname); 221 } 222 223 SLIST_FOREACH(p, &curlist, link) { 224 if (p->display == 0) 225 continue; 226 mvprintw(TopSection1 + p->row, 0, "%s", p->if_name); 227 mvprintw(TopSection2 + p->row, 0, "%s", p->if_name); 228 mvprintw(TopSection3 + p->row, 0, "%s", p->if_name); 229 SLIST_FOREACH(q, &p->queues, link) { 230 print_altq(p, q); 231 } 232 } 233 } 234 235 int 236 initaltqs(void) 237 { 238 TopSection1 = TOPLINE; 239 240 return 1; 241 } 242 243 void 244 fetchaltqs(void) 245 { 246 struct if_stat *ifp = NULL; 247 struct timeval tv, new_tv, old_tv; 248 double elapsed = 0.0; 249 u_int new_inb, new_outb, old_inb, old_outb = 0; 250 u_int we_need_to_sort_interface_list = 0; 251 252 SLIST_FOREACH(ifp, &curlist, link) { 253 /* 254 * Grab a copy of the old input/output values before we 255 * call getifmibdata(). 256 */ 257 old_inb = ifp->if_mib.ifmd_data.ifi_ibytes; 258 old_outb = ifp->if_mib.ifmd_data.ifi_obytes; 259 ifp->tv_lastchanged = ifp->if_mib.ifmd_data.ifi_lastchange; 260 261 if (gettimeofday(&new_tv, NULL) != 0) 262 IFSTAT_ERR(2, "error getting time of day"); 263 (void)getifmibdata(ifp->if_row, &ifp->if_mib); 264 265 266 new_inb = ifp->if_mib.ifmd_data.ifi_ibytes; 267 new_outb = ifp->if_mib.ifmd_data.ifi_obytes; 268 269 /* Display interface if it's received some traffic. */ 270 if (new_inb > 0 && old_inb == 0) { 271 ifp->display = 1; 272 we_need_to_sort_interface_list++; 273 } 274 275 /* 276 * The rest is pretty trivial. Calculate the new values 277 * for our current traffic rates, and while we're there, 278 * see if we have new peak rates. 279 */ 280 old_tv = ifp->tv; 281 timersub(&new_tv, &old_tv, &tv); 282 elapsed = tv.tv_sec + (tv.tv_usec * 1e-6); 283 284 ifp->if_in_curtraffic = new_inb - old_inb; 285 ifp->if_out_curtraffic = new_outb - old_outb; 286 287 /* 288 * Rather than divide by the time specified on the comm- 289 * and line, we divide by ``elapsed'' as this is likely 290 * to be more accurate. 291 */ 292 ifp->if_in_curtraffic /= elapsed; 293 ifp->if_out_curtraffic /= elapsed; 294 295 if (ifp->if_in_curtraffic > ifp->if_in_traffic_peak) 296 ifp->if_in_traffic_peak = ifp->if_in_curtraffic; 297 298 if (ifp->if_out_curtraffic > ifp->if_out_traffic_peak) 299 ifp->if_out_traffic_peak = ifp->if_out_curtraffic; 300 301 ifp->tv.tv_sec = new_tv.tv_sec; 302 ifp->tv.tv_usec = new_tv.tv_usec; 303 304 } 305 306 load_altqs(); 307 308 if (we_need_to_sort_interface_list) 309 sort_interface_list(); 310 311 return; 312 } 313 314 /* 315 * We want to right justify our interface names against the first column 316 * (first sixteen or so characters), so we need to do some alignment. 317 */ 318 static void 319 right_align_string(if_stat_t *ifp) 320 { 321 int str_len = 0, pad_len = 0; 322 char *newstr = NULL, *ptr = NULL; 323 324 if (ifp == NULL || ifp->if_mib.ifmd_name == NULL) 325 return; 326 else { 327 /* string length + '\0' */ 328 str_len = strlen(ifp->if_mib.ifmd_name)+1; 329 pad_len = IF_NAMESIZE-(str_len); 330 331 newstr = ifp->if_name; 332 ptr = newstr + pad_len; 333 (void)memset((void *)newstr, (int)' ', IF_NAMESIZE); 334 (void)strncpy(ptr, (const char *)&ifp->if_mib.ifmd_name, 335 str_len); 336 } 337 338 return; 339 } 340 341 /* 342 * This function iterates through our list of interfaces, identifying 343 * those that are to be displayed (ifp->display = 1). For each interf- 344 * rface that we're displaying, we generate an appropriate position for 345 * it on the screen (ifp->row). 346 * 347 * This function is called any time a change is made to an interface's 348 * ``display'' state. 349 */ 350 void 351 sort_interface_list(void) 352 { 353 if_stat_t *ifp; 354 u_int y; 355 356 y = 1; 357 SLIST_FOREACH(ifp, &curlist, link) { 358 if (ifp->display) 359 ifp->row = ++y; 360 } 361 TopSection2 = TopSection1 + y + 4; 362 TopSection3 = TopSection2 + y + 4; 363 } 364 365 static 366 unsigned int 367 getifnum(void) 368 { 369 u_int data = 0; 370 size_t datalen = 0; 371 static int name[] = { CTL_NET, 372 PF_LINK, 373 NETLINK_GENERIC, 374 IFMIB_SYSTEM, 375 IFMIB_IFCOUNT }; 376 377 datalen = sizeof(data); 378 if (sysctl(name, 5, (void *)&data, (size_t *)&datalen, NULL, 379 (size_t)0) != 0) 380 IFSTAT_ERR(1, "sysctl error"); 381 return data; 382 } 383 384 static void 385 getifmibdata(int row, struct ifmibdata *data) 386 { 387 size_t datalen = 0; 388 static int name[] = { CTL_NET, 389 PF_LINK, 390 NETLINK_GENERIC, 391 IFMIB_IFDATA, 392 0, 393 IFDATA_GENERAL }; 394 datalen = sizeof(*data); 395 name[4] = row; 396 397 if ((sysctl(name, 6, (void *)data, (size_t *)&datalen, NULL, 398 (size_t)0) != 0) && (errno != ENOENT)) 399 IFSTAT_ERR(2, "sysctl error getting interface data"); 400 } 401 402 static void 403 load_altqs(void) 404 { 405 struct pfioc_altq pa; 406 struct pfioc_qstats pq; 407 if_stat_t *p; 408 queue_stats_t *q; 409 qcol_t *qc; 410 int i; 411 int n; 412 413 bzero(&pa, sizeof(pa)); 414 bzero(&pq, sizeof(pq)); 415 416 if (ioctl(pf_fd, DIOCGETALTQS, &pa)) 417 return; 418 n = pa.nr; 419 for (i = 0; i < n; ++i) { 420 pa.nr = i; 421 if (ioctl(pf_fd, DIOCGETALTQ, &pa)) 422 return; 423 if (pa.altq.qid <= 0) 424 continue; 425 426 SLIST_FOREACH(p, &curlist, link) { 427 if (strcmp(pa.altq.ifname, p->if_mib.ifmd_name) == 0) 428 break; 429 } 430 if (p == NULL) 431 continue; 432 SLIST_FOREACH(q, &p->queues, link) { 433 if (strcmp(pa.altq.qname, q->pa.altq.qname) == 0) 434 break; 435 } 436 if (q == NULL) { 437 q = calloc(1, sizeof(*q)); 438 q->pa = pa; 439 SLIST_INSERT_HEAD(&p->queues, q, link); 440 } else { 441 q->pa.ticket = pa.ticket; 442 } 443 q->ostats = q->nstats; 444 q->pq.nr = i; 445 q->pq.ticket = q->pa.ticket; 446 q->pq.buf = &q->nstats; 447 q->pq.nbytes = sizeof(q->nstats); 448 if (ioctl(pf_fd, DIOCGETQSTATS, &q->pq) < 0) { 449 SLIST_REMOVE(&p->queues, q, queue_stats, link); 450 free(q); 451 } 452 SLIST_FOREACH(qc, &qcols, link) { 453 if (strcmp(q->pa.altq.qname, qc->qname) == 0) 454 break; 455 } 456 if (qc == NULL) { 457 qc = calloc(1, sizeof(*qc)); 458 qc->qname = strdup(q->pa.altq.qname); 459 qc->col = qccols++; 460 SLIST_INSERT_HEAD(&qcols, qc, link); 461 } 462 q->qc = qc; 463 } 464 } 465 466 static 467 void 468 print_altq(if_stat_t *p, queue_stats_t *q) 469 { 470 uint64_t xmit_pkts; 471 uint64_t xmit_bytes; 472 uint64_t drop_pkts; 473 uint64_t drop_bytes __unused; 474 uint64_t qlen; 475 476 switch(q->pa.altq.scheduler) { 477 case ALTQT_CBQ: 478 xmit_pkts = q->nstats.cbq.xmit_cnt.packets; 479 xmit_bytes = q->nstats.cbq.xmit_cnt.bytes; 480 drop_pkts = q->nstats.cbq.drop_cnt.packets; 481 drop_bytes = q->nstats.cbq.drop_cnt.bytes; 482 xmit_pkts -= q->ostats.cbq.xmit_cnt.packets; 483 xmit_bytes -= q->ostats.cbq.xmit_cnt.bytes; 484 drop_pkts -= q->ostats.cbq.drop_cnt.packets; 485 drop_bytes -= q->ostats.cbq.drop_cnt.bytes; 486 qlen = 0; 487 break; 488 case ALTQT_PRIQ: 489 xmit_pkts = q->nstats.priq.xmitcnt.packets; 490 xmit_bytes = q->nstats.priq.xmitcnt.bytes; 491 drop_pkts = q->nstats.priq.dropcnt.packets; 492 drop_bytes = q->nstats.priq.dropcnt.bytes; 493 xmit_pkts -= q->ostats.priq.xmitcnt.packets; 494 xmit_bytes -= q->ostats.priq.xmitcnt.bytes; 495 drop_pkts -= q->ostats.priq.dropcnt.packets; 496 drop_bytes -= q->ostats.priq.dropcnt.bytes; 497 qlen = q->nstats.priq.qlength; 498 break; 499 case ALTQT_HFSC: 500 xmit_pkts = q->nstats.hfsc.xmit_cnt.packets; 501 xmit_bytes = q->nstats.hfsc.xmit_cnt.bytes; 502 drop_pkts = q->nstats.hfsc.drop_cnt.packets; 503 drop_bytes = q->nstats.hfsc.drop_cnt.bytes; 504 xmit_pkts -= q->ostats.hfsc.xmit_cnt.packets; 505 xmit_bytes -= q->ostats.hfsc.xmit_cnt.bytes; 506 drop_pkts -= q->ostats.hfsc.drop_cnt.packets; 507 drop_bytes -= q->ostats.hfsc.drop_cnt.bytes; 508 qlen = q->nstats.hfsc.qlength; 509 break; 510 case ALTQT_FAIRQ: 511 xmit_pkts = q->nstats.fairq.xmit_cnt.packets; 512 xmit_bytes = q->nstats.fairq.xmit_cnt.bytes; 513 drop_pkts = q->nstats.fairq.drop_cnt.packets; 514 drop_bytes = q->nstats.fairq.drop_cnt.bytes; 515 xmit_pkts -= q->ostats.fairq.xmit_cnt.packets; 516 xmit_bytes -= q->ostats.fairq.xmit_cnt.bytes; 517 drop_pkts -= q->ostats.fairq.drop_cnt.packets; 518 drop_bytes -= q->ostats.fairq.drop_cnt.bytes; 519 qlen = q->nstats.fairq.qlength; 520 break; 521 default: 522 xmit_pkts = 0; 523 xmit_bytes = 0; 524 drop_pkts = 0; 525 drop_bytes = 0; 526 qlen = 0; 527 break; 528 } 529 if (xmit_pkts == 0) 530 mvprintw(TopSection1 + p->row, 531 TOPQSTART + q->qc->col * TOPQWIDTH - 1, 532 "%10s", ""); 533 else 534 mvprintw(TopSection1 + p->row, 535 TOPQSTART + q->qc->col * TOPQWIDTH - 1, 536 "%10jd", (intmax_t)xmit_pkts); 537 538 if (xmit_bytes == 0) 539 mvprintw(TopSection2 + p->row, 540 TOPQSTART + q->qc->col * TOPQWIDTH - 1, 541 "%10s", ""); 542 else 543 mvprintw(TopSection2 + p->row, 544 TOPQSTART + q->qc->col * TOPQWIDTH - 1, 545 "%10jd", (intmax_t)xmit_bytes); 546 if (drop_pkts) 547 mvprintw(TopSection3 + p->row, 548 TOPQSTART + q->qc->col * TOPQWIDTH - 1, 549 "%10jd", (intmax_t)drop_pkts); 550 else if (qlen) 551 mvprintw(TopSection3 + p->row, 552 TOPQSTART + q->qc->col * TOPQWIDTH - 1, 553 "%9jdQ", (intmax_t)qlen); 554 else 555 mvprintw(TopSection3 + p->row, 556 TOPQSTART + q->qc->col * TOPQWIDTH - 1, 557 "%10s", ""); 558 } 559 560 int 561 cmdaltqs(const char *cmd, char *args) 562 { 563 int retval = 0; 564 565 retval = ifcmd(cmd, args); 566 /* ifcmd() returns 1 on success */ 567 if (retval == 1) { 568 showaltqs(); 569 refresh(); 570 } 571 572 return retval; 573 } 574