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