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