]> git.proxmox.com Git - mirror_iproute2.git/blob - misc/nstat.c
ss: Make leading ":" always optional for sport and dport
[mirror_iproute2.git] / misc / nstat.c
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
35 int dump_zeros;
36 int reset_history;
37 int ignore_history;
38 int no_output;
39 int json_output;
40 int no_update;
41 int scan_interval;
42 int time_constant;
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
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
63 static int net_netstat_open(void)
64 {
65 return generic_proc_open("PROC_NET_NETSTAT", "net/netstat");
66 }
67
68 static int net_snmp_open(void)
69 {
70 return generic_proc_open("PROC_NET_SNMP", "net/snmp");
71 }
72
73 static int net_snmp6_open(void)
74 {
75 return generic_proc_open("PROC_NET_SNMP6", "net/snmp6");
76 }
77
78 static int net_sctp_snmp_open(void)
79 {
80 return generic_proc_open("PROC_NET_SCTP_SNMP", "net/sctp/snmp");
81 }
82
83 struct nstat_ent {
84 struct nstat_ent *next;
85 char *id;
86 unsigned long long val;
87 double rate;
88 };
89
90 struct nstat_ent *kern_db;
91 struct nstat_ent *hist_db;
92
93 static const char *useless_numbers[] = {
94 "IpForwarding", "IpDefaultTTL",
95 "TcpRtoAlgorithm", "TcpRtoMin", "TcpRtoMax",
96 "TcpMaxConn", "TcpCurrEstab"
97 };
98
99 static 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
109 static 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
123 static 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
172 static 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
182 static 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
276 static 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
286 static 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
296 static 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
306 static 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
317 static 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
364 static 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
414 static int children;
415
416 static void sigchild(int signo)
417 {
418 }
419
420 static 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
476 static 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
531 static 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
544 static void usage(void) __attribute__((noreturn));
545
546 static 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
564 static 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
579 int 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 }