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