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