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
32int dump_zeros = 0;
33int reset_history = 0;
34int ignore_history = 0;
35int no_output = 0;
36int no_update = 0;
37int scan_interval = 0;
38int time_constant = 0;
39double W;
40char **patterns;
41int npatterns;
42
43char info_source[128];
44int source_mismatch;
45
46int 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
58int net_netstat_open(void)
59{
60	return generic_proc_open("PROC_NET_NETSTAT", "net/netstat");
61}
62
63int net_snmp_open(void)
64{
65	return generic_proc_open("PROC_NET_SNMP", "net/snmp");
66}
67
68int net_snmp6_open(void)
69{
70	return generic_proc_open("PROC_NET_SNMP6", "net/snmp6");
71}
72
73struct 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
82struct nstat_ent *kern_db;
83struct nstat_ent *hist_db;
84
85char *useless_numbers[] = {
86"IpForwarding", "IpDefaultTTL",
87"TcpRtoAlgorithm", "TcpRtoMin", "TcpRtoMax",
88"TcpMaxConn", "TcpCurrEstab"
89};
90
91int 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
100int 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
114void 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
160void 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
231void 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
240void 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
249void 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
258void 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
283void 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
312static int children;
313
314void sigchild(int signo)
315{
316}
317
318void 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
370void 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
422int 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
435static void usage(void) __attribute__((noreturn));
436
437static void usage(void)
438{
439	fprintf(stderr,
440"Usage: nstat [ -h?vVzrnasd:t: ] [ PATTERN [ PATTERN ] ]\n"
441		);
442	exit(-1);
443}
444
445
446int main(int argc, char *argv[])
447{
448	char hist_name[128];
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 (fork())
517			exit(0);
518		chdir("/");
519		close(0); close(1); close(2); setsid();
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 (getenv("NSTAT_HISTORY"))
530		snprintf(hist_name, sizeof(hist_name), getenv("NSTAT_HISTORY"));
531	else
532		sprintf(hist_name, "/tmp/.nstat.u%d", getuid());
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