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