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