xref: /openbsd-src/usr.bin/systat/netstat.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: netstat.c,v 1.16 2001/07/28 05:36:18 pvalchev Exp $	*/
2 /*	$NetBSD: netstat.c,v 1.3 1995/06/18 23:53:07 cgd Exp $	*/
3 
4 /*-
5  * Copyright (c) 1980, 1992, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *	This product includes software developed by the University of
19  *	California, Berkeley and its contributors.
20  * 4. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 
37 #ifndef lint
38 #if 0
39 static char sccsid[] = "@(#)netstat.c	8.1 (Berkeley) 6/6/93";
40 #endif
41 static char rcsid[] = "$OpenBSD: netstat.c,v 1.16 2001/07/28 05:36:18 pvalchev Exp $";
42 #endif /* not lint */
43 
44 /*
45  * netstat
46  */
47 #include <sys/param.h>
48 #include <sys/socket.h>
49 #include <sys/socketvar.h>
50 #include <sys/mbuf.h>
51 #include <sys/protosw.h>
52 
53 #include <netinet/in.h>
54 #include <net/route.h>
55 #include <netinet/in_systm.h>
56 #include <netinet/ip.h>
57 #include <netinet/in_pcb.h>
58 #include <netinet/ip_icmp.h>
59 #include <netinet/icmp_var.h>
60 #include <netinet/ip_var.h>
61 #include <netinet/tcp.h>
62 #include <netinet/tcpip.h>
63 #include <netinet/tcp_seq.h>
64 #define TCPSTATES
65 #include <netinet/tcp_fsm.h>
66 #include <netinet/tcp_timer.h>
67 #include <netinet/tcp_var.h>
68 #include <netinet/tcp_debug.h>
69 #include <netinet/udp.h>
70 #include <netinet/udp_var.h>
71 #include <arpa/inet.h>
72 
73 #include <netdb.h>
74 #include <stdlib.h>
75 #include <string.h>
76 #include <err.h>
77 #include <nlist.h>
78 #include <paths.h>
79 #include "systat.h"
80 #include "extern.h"
81 
82 static void enter __P((struct inpcb *, struct socket *, int, char *));
83 static const char *inetname __P((struct in_addr));
84 static void inetprint __P((struct in_addr *, int, char *));
85 #ifdef INET6
86 static const char *inet6name __P((struct in6_addr *));
87 static void inet6print __P((struct in6_addr *, int, char *));
88 #endif
89 
90 #define	streq(a,b)	(strcmp(a,b)==0)
91 #define	YMAX(w)		((w)->_maxy-1)
92 
93 WINDOW *
94 opennetstat()
95 {
96 	sethostent(1);
97 	setnetent(1);
98 	return (subwin(stdscr, LINES-5-1, 0, 5, 0));
99 }
100 
101 struct netinfo {
102 	struct	netinfo *nif_forw, *nif_prev;
103 	int	nif_family;
104 	short	nif_line;		/* line on screen */
105 	short	nif_seen;		/* 0 when not present in list */
106 	short	nif_flags;
107 #define	NIF_LACHG	0x1		/* local address changed */
108 #define	NIF_FACHG	0x2		/* foreign address changed */
109 	short	nif_state;		/* tcp state */
110 	char	*nif_proto;		/* protocol */
111 	struct	in_addr nif_laddr;	/* local address */
112 #ifdef INET6
113 	struct	in6_addr nif_laddr6;	/* local address */
114 #endif
115 	long	nif_lport;		/* local port */
116 	struct	in_addr	nif_faddr;	/* foreign address */
117 #ifdef INET6
118 	struct	in6_addr nif_faddr6;	/* foreign address */
119 #endif
120 	long	nif_fport;		/* foreign port */
121 	long	nif_rcvcc;		/* rcv buffer character count */
122 	long	nif_sndcc;		/* snd buffer character count */
123 };
124 
125 static struct {
126 	struct	netinfo *nif_forw, *nif_prev;
127 } netcb;
128 
129 static	int aflag = 0;
130 static	int nflag = 0;
131 static	int lastrow = 1;
132 
133 void
134 closenetstat(w)
135         WINDOW *w;
136 {
137 	register struct netinfo *p;
138 
139 	endhostent();
140 	endnetent();
141 	p = (struct netinfo *)netcb.nif_forw;
142 	while (p != (struct netinfo *)&netcb) {
143 		if (p->nif_line != -1)
144 			lastrow--;
145 		p->nif_line = -1;
146 		p = p->nif_forw;
147 	}
148         if (w != NULL) {
149 		wclear(w);
150 		wrefresh(w);
151 		delwin(w);
152 	}
153 }
154 
155 static struct nlist namelist[] = {
156 #define	X_TCBTABLE	0
157 	{ "_tcbtable" },
158 #define	X_UDBTABLE	1
159 	{ "_udbtable" },
160 	{ "" },
161 };
162 
163 int
164 initnetstat()
165 {
166 	int ret;
167 
168 	if ((ret = kvm_nlist(kd, namelist)) == -1)
169 		errx(1, "%s", kvm_geterr(kd));
170 	else if (ret)
171 		nlisterr(namelist);
172 	if (namelist[X_TCBTABLE].n_value == 0) {
173 		error("No symbols in namelist");
174 		return(0);
175 	}
176 	netcb.nif_forw = netcb.nif_prev = (struct netinfo *)&netcb;
177 	protos = TCP|UDP;
178 	return(1);
179 }
180 
181 void
182 fetchnetstat()
183 {
184 	struct inpcbtable pcbtable;
185 	register struct inpcb *head, *prev, *next;
186 	register struct netinfo *p;
187 	struct inpcb inpcb;
188 	struct socket sockb;
189 	struct tcpcb tcpcb;
190 	void *off;
191 	int istcp;
192 
193 	if (namelist[X_TCBTABLE].n_value == 0)
194 		return;
195 	for (p = netcb.nif_forw; p != (struct netinfo *)&netcb; p = p->nif_forw)
196 		p->nif_seen = 0;
197 	if (protos&TCP) {
198 		off = NPTR(X_TCBTABLE);
199 		istcp = 1;
200 	}
201 	else if (protos&UDP) {
202 		off = NPTR(X_UDBTABLE);
203 		istcp = 0;
204 	}
205 	else {
206 		error("No protocols to display");
207 		return;
208 	}
209 again:
210 	KREAD(off, &pcbtable, sizeof (struct inpcbtable));
211 	prev = head = (struct inpcb *)&((struct inpcbtable *)off)->inpt_queue;
212 	next = pcbtable.inpt_queue.cqh_first;
213 	while (next != head) {
214 		KREAD(next, &inpcb, sizeof (inpcb));
215 		if (inpcb.inp_queue.cqe_prev != prev) {
216 printf("prev = %p, head = %p, next = %p, inpcb...prev = %p\n", prev, head, next, inpcb.inp_queue.cqe_prev);
217 			p = netcb.nif_forw;
218 			for (; p != (struct netinfo *)&netcb; p = p->nif_forw)
219 				p->nif_seen = 1;
220 			error("Kernel state in transition");
221 			return;
222 		}
223 		prev = next;
224 		next = inpcb.inp_queue.cqe_next;
225 
226 #ifndef INET6
227 		if (inpcb.inp_flags & INP_IPV6)
228 			continue;
229 #endif
230 
231 		if (!aflag) {
232 			if (!(inpcb.inp_flags & INP_IPV6)
233 			 && inet_lnaof(inpcb.inp_laddr) == INADDR_ANY)
234 				continue;
235 #ifdef INET6
236 			if ((inpcb.inp_flags & INP_IPV6)
237 			 && IN6_IS_ADDR_UNSPECIFIED(&inpcb.inp_laddr6))
238 				continue;
239 #endif
240 		}
241 		if (nhosts && !checkhost(&inpcb))
242 			continue;
243 		if (nports && !checkport(&inpcb))
244 			continue;
245 		KREAD(inpcb.inp_socket, &sockb, sizeof (sockb));
246 		if (istcp) {
247 			KREAD(inpcb.inp_ppcb, &tcpcb, sizeof (tcpcb));
248 			enter(&inpcb, &sockb, tcpcb.t_state, "tcp");
249 		} else
250 			enter(&inpcb, &sockb, 0, "udp");
251 	}
252 	if (istcp && (protos&UDP)) {
253 		istcp = 0;
254 		off = NPTR(X_UDBTABLE);
255 		goto again;
256 	}
257 }
258 
259 static void
260 enter(inp, so, state, proto)
261 	register struct inpcb *inp;
262 	register struct socket *so;
263 	int state;
264 	char *proto;
265 {
266 	register struct netinfo *p;
267 
268 	/*
269 	 * Only take exact matches, any sockets with
270 	 * previously unbound addresses will be deleted
271 	 * below in the display routine because they
272 	 * will appear as ``not seen'' in the kernel
273 	 * data structures.
274 	 */
275 	for (p = netcb.nif_forw; p != (struct netinfo *)&netcb; p = p->nif_forw) {
276 #ifdef INET6
277 		if (p->nif_family == AF_INET && (inp->inp_flags & INP_IPV6))
278 			continue;
279 		if (p->nif_family == AF_INET6 && !(inp->inp_flags & INP_IPV6))
280 			continue;
281 #endif
282 		if (!streq(proto, p->nif_proto))
283 			continue;
284 		if (p->nif_family == AF_INET) {
285 			if (p->nif_lport != inp->inp_lport ||
286 			    p->nif_laddr.s_addr != inp->inp_laddr.s_addr)
287 				continue;
288 			if (p->nif_faddr.s_addr == inp->inp_faddr.s_addr &&
289 			    p->nif_fport == inp->inp_fport)
290 				break;
291 
292 		}
293 #ifdef INET6
294 		else if (p->nif_family == AF_INET6) {
295 			if (p->nif_lport != inp->inp_lport ||
296 			    !IN6_ARE_ADDR_EQUAL(&p->nif_laddr6, &inp->inp_laddr6))
297 				continue;
298 			if (IN6_ARE_ADDR_EQUAL(&p->nif_faddr6, &inp->inp_faddr6) &&
299 			    p->nif_fport == inp->inp_fport)
300 				break;
301 		}
302 #endif
303 		else
304 			continue;
305 	}
306 	if (p == (struct netinfo *)&netcb) {
307 		if ((p = malloc(sizeof(*p))) == NULL) {
308 			error("Out of memory");
309 			return;
310 		}
311 		p->nif_prev = (struct netinfo *)&netcb;
312 		p->nif_forw = netcb.nif_forw;
313 		netcb.nif_forw->nif_prev = p;
314 		netcb.nif_forw = p;
315 		p->nif_line = -1;
316 		p->nif_lport = inp->inp_lport;
317 		p->nif_fport = inp->inp_fport;
318 		p->nif_proto = proto;
319 		p->nif_flags = NIF_LACHG|NIF_FACHG;
320 #ifdef INET6
321 		if (inp->inp_flags & INP_IPV6) {
322 			p->nif_laddr6 = inp->inp_laddr6;
323 			p->nif_faddr6 = inp->inp_faddr6;
324 			p->nif_family = AF_INET6;
325 		}
326 		else
327 #endif
328 		{
329 			p->nif_laddr = inp->inp_laddr;
330 			p->nif_faddr = inp->inp_faddr;
331 			p->nif_family = AF_INET;
332 		}
333 	}
334 	p->nif_rcvcc = so->so_rcv.sb_cc;
335 	p->nif_sndcc = so->so_snd.sb_cc;
336 	p->nif_state = state;
337 	p->nif_seen = 1;
338 }
339 
340 /* column locations */
341 #define	LADDR	0
342 #define	FADDR	LADDR+23
343 #define	PROTO	FADDR+23
344 #define	RCVCC	PROTO+6
345 #define	SNDCC	RCVCC+7
346 #define	STATE	SNDCC+7
347 
348 
349 void
350 labelnetstat()
351 {
352 	if (namelist[X_TCBTABLE].n_type == 0)
353 		return;
354 	wmove(wnd, 0, 0); wclrtobot(wnd);
355 	mvwaddstr(wnd, 0, LADDR, "Local Address");
356 	mvwaddstr(wnd, 0, FADDR, "Foreign Address");
357 	mvwaddstr(wnd, 0, PROTO, "Proto");
358 	mvwaddstr(wnd, 0, RCVCC, "Recv-Q");
359 	mvwaddstr(wnd, 0, SNDCC, "Send-Q");
360 	mvwaddstr(wnd, 0, STATE, "(state)");
361 }
362 
363 void
364 shownetstat()
365 {
366 	register struct netinfo *p, *q;
367 
368 	/*
369 	 * First, delete any connections that have gone
370 	 * away and adjust the position of connections
371 	 * below to reflect the deleted line.
372 	 */
373 	p = netcb.nif_forw;
374 	while (p != (struct netinfo *)&netcb) {
375 		if (p->nif_line == -1 || p->nif_seen) {
376 			p = p->nif_forw;
377 			continue;
378 		}
379 		wmove(wnd, p->nif_line, 0); wdeleteln(wnd);
380 		q = netcb.nif_forw;
381 		for (; q != (struct netinfo *)&netcb; q = q->nif_forw)
382 			if (q != p && q->nif_line > p->nif_line) {
383 				q->nif_line--;
384 				/* this shouldn't be necessary */
385 				q->nif_flags |= NIF_LACHG|NIF_FACHG;
386 			}
387 		lastrow--;
388 		q = p->nif_forw;
389 		p->nif_prev->nif_forw = p->nif_forw;
390 		p->nif_forw->nif_prev = p->nif_prev;
391 		free(p);
392 		p = q;
393 	}
394 	/*
395 	 * Update existing connections and add new ones.
396 	 */
397 	for (p = netcb.nif_forw; p != (struct netinfo *)&netcb; p = p->nif_forw) {
398 		if (p->nif_line == -1) {
399 			/*
400 			 * Add a new entry if possible.
401 			 */
402 			if (lastrow > YMAX(wnd))
403 				continue;
404 			p->nif_line = lastrow++;
405 			p->nif_flags |= NIF_LACHG|NIF_FACHG;
406 		}
407 		if (p->nif_flags & NIF_LACHG) {
408 			wmove(wnd, p->nif_line, LADDR);
409 			switch (p->nif_family) {
410 			case AF_INET:
411 				inetprint(&p->nif_laddr, p->nif_lport,
412 					p->nif_proto);
413 				break;
414 #ifdef INET6
415 			case AF_INET6:
416 				inet6print(&p->nif_laddr6, p->nif_lport,
417 					p->nif_proto);
418 				break;
419 #endif
420 			}
421 			p->nif_flags &= ~NIF_LACHG;
422 		}
423 		if (p->nif_flags & NIF_FACHG) {
424 			wmove(wnd, p->nif_line, FADDR);
425 			switch (p->nif_family) {
426 			case AF_INET:
427 				inetprint(&p->nif_faddr, p->nif_fport,
428 					p->nif_proto);
429 				break;
430 #ifdef INET6
431 			case AF_INET6:
432 				inet6print(&p->nif_faddr6, p->nif_fport,
433 					p->nif_proto);
434 				break;
435 #endif
436 			}
437 			p->nif_flags &= ~NIF_FACHG;
438 		}
439 		mvwaddstr(wnd, p->nif_line, PROTO, p->nif_proto);
440 #ifdef INET6
441 		if (p->nif_family == AF_INET6)
442 			waddstr(wnd, "6");
443 #endif
444 		mvwprintw(wnd, p->nif_line, RCVCC, "%6d", p->nif_rcvcc);
445 		mvwprintw(wnd, p->nif_line, SNDCC, "%6d", p->nif_sndcc);
446 		if (streq(p->nif_proto, "tcp"))
447 			if (p->nif_state < 0 || p->nif_state >= TCP_NSTATES)
448 				mvwprintw(wnd, p->nif_line, STATE, "%d",
449 				    p->nif_state);
450 			else
451 				mvwaddstr(wnd, p->nif_line, STATE,
452 				    tcpstates[p->nif_state]);
453 		wclrtoeol(wnd);
454 	}
455 	if (lastrow < YMAX(wnd)) {
456 		wmove(wnd, lastrow, 0); wclrtobot(wnd);
457 		wmove(wnd, YMAX(wnd), 0); wdeleteln(wnd);	/* XXX */
458 	}
459 }
460 
461 /*
462  * Pretty print an Internet address (net address + port).
463  * If the nflag was specified, use numbers instead of names.
464  */
465 static void
466 inetprint(in, port, proto)
467 	register struct in_addr *in;
468 	int port;
469 	char *proto;
470 {
471 	struct servent *sp = 0;
472 	char line[80], *cp;
473 
474 	snprintf(line, sizeof line, "%.*s.", 16, inetname(*in));
475 	cp = strchr(line, '\0');
476 	if (!nflag && port)
477 		sp = getservbyport(port, proto);
478 	if (sp || port == 0)
479 		snprintf(cp, sizeof line - strlen(cp), "%.8s",
480 		    sp ? sp->s_name : "*");
481 	else
482 		snprintf(cp, sizeof line - strlen(cp), "%d",
483 		    ntohs((u_short)port));
484 	/* pad to full column to clear any garbage */
485 	cp = strchr(line, '\0');
486 	while (cp - line < 22 && cp - line < sizeof line-1)
487 		*cp++ = ' ';
488 	*cp = '\0';
489 	waddstr(wnd, line);
490 }
491 
492 #ifdef INET6
493 static void
494 inet6print(in6, port, proto)
495 	register struct in6_addr *in6;
496 	int port;
497 	char *proto;
498 {
499 	struct servent *sp = 0;
500 	char line[80], *cp;
501 
502 	snprintf(line, sizeof line, "%.*s.", 16, inet6name(in6));
503 	cp = strchr(line, '\0');
504 	if (!nflag && port)
505 		sp = getservbyport(port, proto);
506 	if (sp || port == 0)
507 		snprintf(cp, sizeof line - strlen(cp), "%.8s",
508 		    sp ? sp->s_name : "*");
509 	else
510 		snprintf(cp, sizeof line - strlen(cp), "%d",
511 		    ntohs((u_short)port));
512 	/* pad to full column to clear any garbage */
513 	cp = strchr(line, '\0');
514 	while (cp - line < 22 && cp - line < sizeof line-1)
515 		*cp++ = ' ';
516 	*cp = '\0';
517 	waddstr(wnd, line);
518 }
519 #endif
520 
521 /*
522  * Construct an Internet address representation.
523  * If the nflag has been supplied, give
524  * numeric value, otherwise try for symbolic name.
525  */
526 static const char *
527 inetname(in)
528 	struct in_addr in;
529 {
530 	char *cp = 0;
531 	static char line[50];
532 	struct hostent *hp;
533 	struct netent *np;
534 
535 	if (!nflag && in.s_addr != INADDR_ANY) {
536 		int net = inet_netof(in);
537 		int lna = inet_lnaof(in);
538 
539 		if (lna == INADDR_ANY) {
540 			np = getnetbyaddr(net, AF_INET);
541 			if (np)
542 				cp = np->n_name;
543 		}
544 		if (cp == 0) {
545 			hp = gethostbyaddr((char *)&in, sizeof (in), AF_INET);
546 			if (hp)
547 				cp = hp->h_name;
548 		}
549 	}
550 	if (in.s_addr == INADDR_ANY) {
551 		strlcpy(line, "*", sizeof line);
552 	} else if (cp) {
553 		strlcpy(line, cp, sizeof line);
554 	} else {
555 		in.s_addr = ntohl(in.s_addr);
556 #define C(x)	((x) & 0xff)
557 		snprintf(line, sizeof line, "%u.%u.%u.%u", C(in.s_addr >> 24),
558 			C(in.s_addr >> 16), C(in.s_addr >> 8), C(in.s_addr));
559 	}
560 	return (line);
561 }
562 
563 #ifdef INET6
564 static const char *
565 inet6name(in6)
566 	struct in6_addr *in6;
567 {
568 	static char line[NI_MAXHOST];
569 	struct sockaddr_in6 sin6;
570 	int flags;
571 
572 	if (nflag)
573 		flags = NI_NUMERICHOST;
574 	else
575 		flags = 0;
576 	if (IN6_IS_ADDR_UNSPECIFIED(in6))
577 		return "*";
578 	memset(&sin6, 0, sizeof(sin6));
579 	sin6.sin6_family = AF_INET6;
580 	sin6.sin6_len = sizeof(struct sockaddr_in6);
581 	sin6.sin6_addr = *in6;
582 	if (getnameinfo((struct sockaddr *)&sin6, sin6.sin6_len,
583 			line, sizeof(line), NULL, 0, flags) == 0)
584 		return line;
585 	return "?";
586 }
587 #endif
588 
589 int
590 cmdnetstat(cmd, args)
591 	char *cmd, *args;
592 {
593 	register struct netinfo *p;
594 
595 	if (prefix(cmd, "all")) {
596 		aflag = !aflag;
597 		goto fixup;
598 	}
599 	if  (prefix(cmd, "numbers") || prefix(cmd, "names")) {
600 		int new;
601 
602 		new = prefix(cmd, "numbers");
603 		if (new == nflag)
604 			return (1);
605 		p = netcb.nif_forw;
606 		for (; p != (struct netinfo *)&netcb; p = p->nif_forw) {
607 			if (p->nif_line == -1)
608 				continue;
609 			p->nif_flags |= NIF_LACHG|NIF_FACHG;
610 		}
611 		nflag = new;
612 		wclear(wnd);
613 		labelnetstat();
614 		goto redisplay;
615 	}
616 	if (!netcmd(cmd, args))
617 		return (0);
618 fixup:
619 	fetchnetstat();
620 redisplay:
621 	shownetstat();
622 	refresh();
623 	return (1);
624 }
625