• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
generic_proc_open(const char * env,char * name)46 static 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 
net_netstat_open(void)58 int net_netstat_open(void)
59 {
60 	return generic_proc_open("PROC_NET_NETSTAT", "net/netstat");
61 }
62 
net_snmp_open(void)63 int net_snmp_open(void)
64 {
65 	return generic_proc_open("PROC_NET_SNMP", "net/snmp");
66 }
67 
net_snmp6_open(void)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 
useless_number(char * id)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 
match(char * id)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 
load_good_table(FILE * fp)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[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 
load_ugly_table(FILE * fp)160 void 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 
load_snmp(void)231 void 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 
load_snmp6(void)240 void 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 
load_netstat(void)249 void 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 
dump_kern_db(FILE * fp,int to_hist)258 void 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 
dump_incr_db(FILE * fp)283 void 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 
312 static int children;
313 
sigchild(int signo)314 void sigchild(int signo)
315 {
316 }
317 
update_db(int interval)318 void 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 
server_loop(int fd)370 void 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 
verify_forging(int fd)422 int 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 
435 static void usage(void) __attribute__((noreturn));
436 
usage(void)437 static void usage(void)
438 {
439 	fprintf(stderr,
440 "Usage: nstat [ -h?vVzrnasd:t: ] [ PATTERN [ PATTERN ] ]\n"
441 		);
442 	exit(-1);
443 }
444 
445 
main(int argc,char * argv[])446 int 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;
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