1 /* 2 * nstat.c handy utility to read counters /proc/net/netstat and snmp 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License 6 * as published by the Free Software Foundation; either version 7 * 2 of the License, or (at your option) any later version. 8 * 9 * Authors: Alexey Kuznetsov, <kuznet (at) ms2.inr.ac.ru> 10 */ 11 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <unistd.h> 15 #include <fcntl.h> 16 #include <string.h> 17 #include <errno.h> 18 #include <time.h> 19 #include <sys/time.h> 20 #include <fnmatch.h> 21 #include <sys/file.h> 22 #include <sys/socket.h> 23 #include <sys/un.h> 24 #include <sys/poll.h> 25 #include <sys/wait.h> 26 #include <sys/stat.h> 27 #include <signal.h> 28 #include <math.h> 29 #include <getopt.h> 30 31 #include <json_writer.h> 32 #include <SNAPSHOT.h> 33 34 int dump_zeros = 0; 35 int reset_history = 0; 36 int ignore_history = 0; 37 int no_output = 0; 38 int json_output = 0; 39 int pretty = 0; 40 int no_update = 0; 41 int scan_interval = 0; 42 int time_constant = 0; 43 double W; 44 char **patterns; 45 int npatterns; 46 47 char info_source[128]; 48 int source_mismatch; 49 50 static int generic_proc_open(const char *env, char *name) 51 { 52 char store[128]; 53 char *p = getenv(env); 54 if (!p) { 55 p = getenv("PROC_ROOT") ? : "/proc"; 56 snprintf(store, sizeof(store)-1, "%s/%s", p, name); 57 p = store; 58 } 59 return open(p, O_RDONLY); 60 } 61 62 static int net_netstat_open(void) 63 { 64 return generic_proc_open("PROC_NET_NETSTAT", "net/netstat"); 65 } 66 67 static int net_snmp_open(void) 68 { 69 return generic_proc_open("PROC_NET_SNMP", "net/snmp"); 70 } 71 72 static int net_snmp6_open(void) 73 { 74 return generic_proc_open("PROC_NET_SNMP6", "net/snmp6"); 75 } 76 77 struct nstat_ent 78 { 79 struct nstat_ent *next; 80 char *id; 81 unsigned long long val; 82 double rate; 83 }; 84 85 struct nstat_ent *kern_db; 86 struct nstat_ent *hist_db; 87 88 static const char *useless_numbers[] = { 89 "IpForwarding", "IpDefaultTTL", 90 "TcpRtoAlgorithm", "TcpRtoMin", "TcpRtoMax", 91 "TcpMaxConn", "TcpCurrEstab" 92 }; 93 94 static int useless_number(const char *id) 95 { 96 int i; 97 for (i=0; i<sizeof(useless_numbers)/sizeof(*useless_numbers); i++) 98 if (strcmp(id, useless_numbers[i]) == 0) 99 return 1; 100 return 0; 101 } 102 103 static int match(const char *id) 104 { 105 int i; 106 107 if (npatterns == 0) 108 return 1; 109 110 for (i=0; i<npatterns; i++) { 111 if (!fnmatch(patterns[i], id, 0)) 112 return 1; 113 } 114 return 0; 115 } 116 117 static void load_good_table(FILE *fp) 118 { 119 char buf[4096]; 120 struct nstat_ent *db = NULL; 121 struct nstat_ent *n; 122 123 while (fgets(buf, sizeof(buf), fp) != NULL) { 124 int nr; 125 unsigned long long val; 126 double rate; 127 char idbuf[sizeof(buf)]; 128 if (buf[0] == '#') { 129 buf[strlen(buf)-1] = 0; 130 if (info_source[0] && strcmp(info_source, buf+1)) 131 source_mismatch = 1; 132 info_source[0] = 0; 133 strncat(info_source, buf+1, sizeof(info_source)-1); 134 continue; 135 } 136 /* idbuf is as big as buf, so this is safe */ 137 nr = sscanf(buf, "%s%llu%lg", idbuf, &val, &rate); 138 if (nr < 2) 139 abort(); 140 if (nr < 3) 141 rate = 0; 142 if (useless_number(idbuf)) 143 continue; 144 if ((n = malloc(sizeof(*n))) == NULL) 145 abort(); 146 n->id = strdup(idbuf); 147 n->val = val; 148 n->rate = rate; 149 n->next = db; 150 db = n; 151 } 152 153 while (db) { 154 n = db; 155 db = db->next; 156 n->next = kern_db; 157 kern_db = n; 158 } 159 } 160 161 static int count_spaces(const char *line) 162 { 163 int count = 0; 164 char c; 165 166 while ((c = *line++) != 0) 167 count += c == ' ' || c == '\n'; 168 return count; 169 } 170 171 static void load_ugly_table(FILE *fp) 172 { 173 char buf[4096]; 174 struct nstat_ent *db = NULL; 175 struct nstat_ent *n; 176 177 while (fgets(buf, sizeof(buf), fp) != NULL) { 178 char idbuf[sizeof(buf)]; 179 int off; 180 char *p; 181 int count1, count2, skip = 0; 182 183 p = strchr(buf, ':'); 184 if (!p) 185 abort(); 186 count1 = count_spaces(buf); 187 *p = 0; 188 idbuf[0] = 0; 189 strncat(idbuf, buf, sizeof(idbuf) - 1); 190 off = p - buf; 191 p += 2; 192 193 while (*p) { 194 char *next; 195 if ((next = strchr(p, ' ')) != NULL) 196 *next++ = 0; 197 else if ((next = strchr(p, '\n')) != NULL) 198 *next++ = 0; 199 if (off < sizeof(idbuf)) { 200 idbuf[off] = 0; 201 strncat(idbuf, p, sizeof(idbuf) - off - 1); 202 } 203 n = malloc(sizeof(*n)); 204 if (!n) 205 abort(); 206 n->id = strdup(idbuf); 207 n->rate = 0; 208 n->next = db; 209 db = n; 210 p = next; 211 } 212 n = db; 213 if (fgets(buf, sizeof(buf), fp) == NULL) 214 abort(); 215 count2 = count_spaces(buf); 216 if (count2 > count1) 217 skip = count2 - count1; 218 do { 219 p = strrchr(buf, ' '); 220 if (!p) 221 abort(); 222 *p = 0; 223 if (sscanf(p+1, "%llu", &n->val) != 1) 224 abort(); 225 /* Trick to skip "dummy" trailing ICMP MIB in 2.4 */ 226 if (skip) 227 skip--; 228 else 229 n = n->next; 230 } while (p > buf + off + 2); 231 } 232 233 while (db) { 234 n = db; 235 db = db->next; 236 if (useless_number(n->id)) { 237 free(n->id); 238 free(n); 239 } else { 240 n->next = kern_db; 241 kern_db = n; 242 } 243 } 244 } 245 246 static void load_snmp(void) 247 { 248 FILE *fp = fdopen(net_snmp_open(), "r"); 249 if (fp) { 250 load_ugly_table(fp); 251 fclose(fp); 252 } 253 } 254 255 static void load_snmp6(void) 256 { 257 FILE *fp = fdopen(net_snmp6_open(), "r"); 258 if (fp) { 259 load_good_table(fp); 260 fclose(fp); 261 } 262 } 263 264 static void load_netstat(void) 265 { 266 FILE *fp = fdopen(net_netstat_open(), "r"); 267 if (fp) { 268 load_ugly_table(fp); 269 fclose(fp); 270 } 271 } 272 273 274 static void dump_kern_db(FILE *fp, int to_hist) 275 { 276 json_writer_t *jw = json_output ? jsonw_new(fp) : NULL; 277 struct nstat_ent *n, *h; 278 279 h = hist_db; 280 if (jw) { 281 jsonw_pretty(jw, pretty); 282 jsonw_name(jw, info_source); 283 jsonw_start_object(jw); 284 } else 285 fprintf(fp, "#%s\n", info_source); 286 287 for (n=kern_db; n; n=n->next) { 288 unsigned long long val = n->val; 289 if (!dump_zeros && !val && !n->rate) 290 continue; 291 if (!match(n->id)) { 292 struct nstat_ent *h1; 293 if (!to_hist) 294 continue; 295 for (h1 = h; h1; h1 = h1->next) { 296 if (strcmp(h1->id, n->id) == 0) { 297 val = h1->val; 298 h = h1->next; 299 break; 300 } 301 } 302 } 303 304 if (jw) 305 jsonw_uint_field(jw, n->id, val); 306 else 307 fprintf(fp, "%-32s%-16llu%6.1f\n", n->id, val, n->rate); 308 } 309 310 if (jw) { 311 jsonw_end_object(jw); 312 jsonw_destroy(&jw); 313 } 314 } 315 316 static void dump_incr_db(FILE *fp) 317 { 318 json_writer_t *jw = json_output ? jsonw_new(fp) : NULL; 319 struct nstat_ent *n, *h; 320 321 h = hist_db; 322 if (jw) { 323 jsonw_pretty(jw, pretty); 324 jsonw_name(jw, info_source); 325 jsonw_start_object(jw); 326 } else 327 fprintf(fp, "#%s\n", info_source); 328 329 for (n=kern_db; n; n=n->next) { 330 int ovfl = 0; 331 unsigned long long val = n->val; 332 struct nstat_ent *h1; 333 for (h1 = h; h1; h1 = h1->next) { 334 if (strcmp(h1->id, n->id) == 0) { 335 if (val < h1->val) { 336 ovfl = 1; 337 val = h1->val; 338 } 339 val -= h1->val; 340 h = h1->next; 341 break; 342 } 343 } 344 if (!dump_zeros && !val && !n->rate) 345 continue; 346 if (!match(n->id)) 347 continue; 348 349 if (jw) 350 jsonw_uint_field(jw, n->id, val); 351 else 352 fprintf(fp, "%-32s%-16llu%6.1f%s\n", n->id, val, 353 n->rate, ovfl?" (overflow)":""); 354 } 355 356 if (jw) { 357 jsonw_end_object(jw); 358 jsonw_destroy(&jw); 359 } 360 } 361 362 static int children; 363 364 static void sigchild(int signo) 365 { 366 } 367 368 static void update_db(int interval) 369 { 370 struct nstat_ent *n, *h; 371 372 n = kern_db; 373 kern_db = NULL; 374 375 load_netstat(); 376 load_snmp6(); 377 load_snmp(); 378 379 h = kern_db; 380 kern_db = n; 381 382 for (n = kern_db; n; n = n->next) { 383 struct nstat_ent *h1; 384 for (h1 = h; h1; h1 = h1->next) { 385 if (strcmp(h1->id, n->id) == 0) { 386 double sample; 387 unsigned long long incr = h1->val - n->val; 388 389 n->val = h1->val; 390 sample = (double)incr * 1000.0 / interval; 391 if (interval >= scan_interval) { 392 n->rate += W*(sample-n->rate); 393 } else if (interval >= 1000) { 394 if (interval >= time_constant) { 395 n->rate = sample; 396 } else { 397 double w = W*(double)interval/scan_interval; 398 n->rate += w*(sample-n->rate); 399 } 400 } 401 402 while (h != h1) { 403 struct nstat_ent *tmp = h; 404 h = h->next; 405 free(tmp->id); 406 free(tmp); 407 }; 408 h = h1->next; 409 free(h1->id); 410 free(h1); 411 break; 412 } 413 } 414 } 415 } 416 417 #define T_DIFF(a,b) (((a).tv_sec-(b).tv_sec)*1000 + ((a).tv_usec-(b).tv_usec)/1000) 418 419 420 static void server_loop(int fd) 421 { 422 struct timeval snaptime = { 0 }; 423 struct pollfd p; 424 p.fd = fd; 425 p.events = p.revents = POLLIN; 426 427 sprintf(info_source, "%d.%lu sampling_interval=%d time_const=%d", 428 getpid(), (unsigned long)random(), scan_interval/1000, time_constant/1000); 429 430 load_netstat(); 431 load_snmp6(); 432 load_snmp(); 433 434 for (;;) { 435 int status; 436 int tdiff; 437 struct timeval now; 438 gettimeofday(&now, NULL); 439 tdiff = T_DIFF(now, snaptime); 440 if (tdiff >= scan_interval) { 441 update_db(tdiff); 442 snaptime = now; 443 tdiff = 0; 444 } 445 if (poll(&p, 1, tdiff + scan_interval) > 0 446 && (p.revents&POLLIN)) { 447 int clnt = accept(fd, NULL, NULL); 448 if (clnt >= 0) { 449 pid_t pid; 450 if (children >= 5) { 451 close(clnt); 452 } else if ((pid = fork()) != 0) { 453 if (pid>0) 454 children++; 455 close(clnt); 456 } else { 457 FILE *fp = fdopen(clnt, "w"); 458 if (fp) { 459 if (tdiff > 0) 460 update_db(tdiff); 461 dump_kern_db(fp, 0); 462 } 463 exit(0); 464 } 465 } 466 } 467 while (children && waitpid(-1, &status, WNOHANG) > 0) 468 children--; 469 } 470 } 471 472 static int verify_forging(int fd) 473 { 474 struct ucred cred; 475 socklen_t olen = sizeof(cred); 476 477 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, (void*)&cred, &olen) || 478 olen < sizeof(cred)) 479 return -1; 480 if (cred.uid == getuid() || cred.uid == 0) 481 return 0; 482 return -1; 483 } 484 485 static void usage(void) __attribute__((noreturn)); 486 487 static void usage(void) 488 { 489 fprintf(stderr, 490 "Usage: nstat [OPTION] [ PATTERN [ PATTERN ] ]\n" 491 " -h, --help this message\n" 492 " -a, --ignore ignore history\n" 493 " -d, --scan=SECS sample every statistics every SECS\n" 494 " -j, --json format output in JSON\n" 495 " -n, --nooutput do history only\n" 496 " -p, --pretty pretty print\n" 497 " -r, --reset reset history\n" 498 " -s, --noupdate don\'t update history\n" 499 " -t, --interval=SECS report average over the last SECS\n" 500 " -V, --version output version information\n" 501 " -z, --zeros show entries with zero activity\n"); 502 exit(-1); 503 } 504 505 static const struct option longopts[] = { 506 { "help", 0, 0, 'h' }, 507 { "ignore", 0, 0, 'a' }, 508 { "scan", 1, 0, 'd'}, 509 { "nooutput", 0, 0, 'n' }, 510 { "json", 0, 0, 'j' }, 511 { "reset", 0, 0, 'r' }, 512 { "noupdate", 0, 0, 's' }, 513 { "pretty", 0, 0, 'p' }, 514 { "interval", 1, 0, 't' }, 515 { "version", 0, 0, 'V' }, 516 { "zeros", 0, 0, 'z' }, 517 { 0 } 518 }; 519 520 int main(int argc, char *argv[]) 521 { 522 char *hist_name; 523 struct sockaddr_un sun; 524 FILE *hist_fp = NULL; 525 int ch; 526 int fd; 527 528 while ((ch = getopt_long(argc, argv, "h?vVzrnasd:t:jp", 529 longopts, NULL)) != EOF) { 530 switch(ch) { 531 case 'z': 532 dump_zeros = 1; 533 break; 534 case 'r': 535 reset_history = 1; 536 break; 537 case 'a': 538 ignore_history = 1; 539 break; 540 case 's': 541 no_update = 1; 542 break; 543 case 'n': 544 no_output = 1; 545 break; 546 case 'd': 547 scan_interval = 1000*atoi(optarg); 548 break; 549 case 't': 550 if (sscanf(optarg, "%d", &time_constant) != 1 || 551 time_constant <= 0) { 552 fprintf(stderr, "nstat: invalid time constant divisor\n"); 553 exit(-1); 554 } 555 break; 556 case 'j': 557 json_output = 1; 558 break; 559 case 'p': 560 pretty = 1; 561 break; 562 case 'v': 563 case 'V': 564 printf("nstat utility, iproute2-ss%s\n", SNAPSHOT); 565 exit(0); 566 case 'h': 567 case '?': 568 default: 569 usage(); 570 } 571 } 572 573 argc -= optind; 574 argv += optind; 575 576 sun.sun_family = AF_UNIX; 577 sun.sun_path[0] = 0; 578 sprintf(sun.sun_path+1, "nstat%d", getuid()); 579 580 if (scan_interval > 0) { 581 if (time_constant == 0) 582 time_constant = 60; 583 time_constant *= 1000; 584 W = 1 - 1/exp(log(10)*(double)scan_interval/time_constant); 585 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { 586 perror("nstat: socket"); 587 exit(-1); 588 } 589 if (bind(fd, (struct sockaddr*)&sun, 2+1+strlen(sun.sun_path+1)) < 0) { 590 perror("nstat: bind"); 591 exit(-1); 592 } 593 if (listen(fd, 5) < 0) { 594 perror("nstat: listen"); 595 exit(-1); 596 } 597 if (daemon(0, 0)) { 598 perror("nstat: daemon"); 599 exit(-1); 600 } 601 signal(SIGPIPE, SIG_IGN); 602 signal(SIGCHLD, sigchild); 603 server_loop(fd); 604 exit(0); 605 } 606 607 patterns = argv; 608 npatterns = argc; 609 610 if ((hist_name = getenv("NSTAT_HISTORY")) == NULL) { 611 hist_name = malloc(128); 612 sprintf(hist_name, "/tmp/.nstat.u%d", getuid()); 613 } 614 615 if (reset_history) 616 unlink(hist_name); 617 618 if (!ignore_history || !no_update) { 619 struct stat stb; 620 621 fd = open(hist_name, O_RDWR|O_CREAT|O_NOFOLLOW, 0600); 622 if (fd < 0) { 623 perror("nstat: open history file"); 624 exit(-1); 625 } 626 if ((hist_fp = fdopen(fd, "r+")) == NULL) { 627 perror("nstat: fdopen history file"); 628 exit(-1); 629 } 630 if (flock(fileno(hist_fp), LOCK_EX)) { 631 perror("nstat: flock history file"); 632 exit(-1); 633 } 634 if (fstat(fileno(hist_fp), &stb) != 0) { 635 perror("nstat: fstat history file"); 636 exit(-1); 637 } 638 if (stb.st_nlink != 1 || stb.st_uid != getuid()) { 639 fprintf(stderr, "nstat: something is so wrong with history file, that I prefer not to proceed.\n"); 640 exit(-1); 641 } 642 if (!ignore_history) { 643 FILE *tfp; 644 long uptime = -1; 645 if ((tfp = fopen("/proc/uptime", "r")) != NULL) { 646 if (fscanf(tfp, "%ld", &uptime) != 1) 647 uptime = -1; 648 fclose(tfp); 649 } 650 if (uptime >= 0 && time(NULL) >= stb.st_mtime+uptime) { 651 fprintf(stderr, "nstat: history is aged out, resetting\n"); 652 if (ftruncate(fileno(hist_fp), 0) < 0) 653 perror("nstat: ftruncate"); 654 } 655 } 656 657 load_good_table(hist_fp); 658 659 hist_db = kern_db; 660 kern_db = NULL; 661 } 662 663 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0 && 664 (connect(fd, (struct sockaddr*)&sun, 2+1+strlen(sun.sun_path+1)) == 0 665 || (strcpy(sun.sun_path+1, "nstat0"), 666 connect(fd, (struct sockaddr*)&sun, 2+1+strlen(sun.sun_path+1)) == 0)) 667 && verify_forging(fd) == 0) { 668 FILE *sfp = fdopen(fd, "r"); 669 load_good_table(sfp); 670 if (hist_db && source_mismatch) { 671 fprintf(stderr, "nstat: history is stale, ignoring it.\n"); 672 hist_db = NULL; 673 } 674 fclose(sfp); 675 } else { 676 if (fd >= 0) 677 close(fd); 678 if (hist_db && info_source[0] && strcmp(info_source, "kernel")) { 679 fprintf(stderr, "nstat: history is stale, ignoring it.\n"); 680 hist_db = NULL; 681 info_source[0] = 0; 682 } 683 load_netstat(); 684 load_snmp6(); 685 load_snmp(); 686 if (info_source[0] == 0) 687 strcpy(info_source, "kernel"); 688 } 689 690 if (!no_output) { 691 if (ignore_history || hist_db == NULL) 692 dump_kern_db(stdout, 0); 693 else 694 dump_incr_db(stdout); 695 } 696 if (!no_update) { 697 if (ftruncate(fileno(hist_fp), 0) < 0) 698 perror("nstat: ftruncate"); 699 rewind(hist_fp); 700 701 json_output = 0; 702 dump_kern_db(hist_fp, 1); 703 fclose(hist_fp); 704 } 705 exit(0); 706 } 707