1/*	$NetBSD: sockstat.c,v 1.16 2011/01/28 18:52:49 pooka Exp $ */
2
3/*
4 * Copyright (c) 2005 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Andrew Brown.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33#ifndef lint
34__RCSID("$NetBSD: sockstat.c,v 1.16 2011/01/28 18:52:49 pooka Exp $");
35#endif
36
37#include <sys/types.h>
38#include <sys/param.h>
39#include <sys/sysctl.h>
40#include <sys/socket.h>
41#include <sys/socketvar.h>
42#include <sys/un.h>
43#include <netinet/in.h>
44#include <net/route.h>
45#include <netinet/in_systm.h>
46#include <netinet/ip.h>
47#include <netinet/in_pcb.h>
48#include <netinet/in_pcb_hdr.h>
49#include <netinet/tcp_fsm.h>
50
51#define _KERNEL
52/* want DTYPE_* defines */
53#include <sys/file.h>
54#undef _KERNEL
55
56#include <arpa/inet.h>
57
58#include <bitstring.h>
59#include <ctype.h>
60#include <err.h>
61#include <errno.h>
62#include <netdb.h>
63#include <pwd.h>
64#include <stdio.h>
65#include <strings.h>
66#include <stdlib.h>
67#include <unistd.h>
68#include <util.h>
69
70#include "prog_ops.h"
71
72#define satosun(sa)	((struct sockaddr_un *)(sa))
73#define satosin(sa)	((struct sockaddr_in *)(sa))
74#ifdef INET6
75#define satosin6(sa)	((struct sockaddr_in6 *)(sa))
76#endif
77
78void	parse_ports(const char *);
79int	get_num(const char *, const char **, const char **);
80void	get_sockets(const char *);
81void	get_files(void);
82int	sort_files(const void *, const void *);
83void	sysctl_sucker(int *, u_int, void **, size_t *);
84void	socket_add_hash(struct kinfo_pcb *, int);
85int	isconnected(struct kinfo_pcb *);
86int	islistening(struct kinfo_pcb *);
87struct kinfo_pcb *pick_socket(struct kinfo_file *);
88int	get_proc(struct kinfo_proc2 *, int);
89int	print_socket(struct kinfo_file *, struct kinfo_pcb *,
90		     struct kinfo_proc2 *);
91void	print_addr(int, int, int, struct sockaddr *);
92
93LIST_HEAD(socklist, sockitem);
94#define HASHSIZE 1009
95struct socklist sockhash[HASHSIZE];
96struct sockitem {
97	LIST_ENTRY(sockitem) s_list;
98	struct kinfo_pcb *s_sock;
99};
100
101struct kinfo_file *flist;
102size_t flistc;
103
104int pf_list, only, nonames;
105bitstr_t *portmap;
106
107#define PF_LIST_INET	1
108#ifdef INET6
109#define PF_LIST_INET6	2
110#endif
111#define PF_LIST_LOCAL	4
112#define ONLY_CONNECTED	1
113#define ONLY_LISTEN	2
114
115int
116main(int argc, char *argv[])
117{
118	struct kinfo_pcb *kp;
119	int ch;
120	size_t i;
121	struct kinfo_proc2 p;
122
123	pf_list = only = 0;
124
125#ifdef INET6
126	while ((ch = getopt(argc, argv, "46cf:lnp:u")) != - 1) {
127#else
128	while ((ch = getopt(argc, argv, "4cf:lnp:u")) != - 1) {
129#endif
130		switch (ch) {
131		case '4':
132			pf_list |= PF_LIST_INET;
133			break;
134#ifdef INET6
135		case '6':
136			pf_list |= PF_LIST_INET6;
137			break;
138#endif
139		case 'c':
140			only |= ONLY_CONNECTED;
141			break;
142		case 'f':
143			if (strcasecmp(optarg, "inet") == 0)
144				pf_list |= PF_LIST_INET;
145#ifdef INET6
146			else if (strcasecmp(optarg, "inet6") == 0)
147				pf_list |= PF_LIST_INET6;
148#endif
149			else if (strcasecmp(optarg, "local") == 0)
150				pf_list |= PF_LIST_LOCAL;
151			else if (strcasecmp(optarg, "unix") == 0)
152				pf_list |= PF_LIST_LOCAL;
153			else
154				errx(1, "%s: unsupported protocol family",
155				    optarg);
156			break;
157		case 'l':
158			only |= ONLY_LISTEN;
159			break;
160		case 'n':
161			nonames++;
162			break;
163		case 'p':
164			parse_ports(optarg);
165			break;
166		case 'u':
167			pf_list |= PF_LIST_LOCAL;
168			break;
169		default:
170			/* usage(); */
171			exit(1);
172		}
173	}
174	argc -= optind;
175	argv += optind;
176
177	if (prog_init && prog_init() == -1)
178		err(1, "init");
179
180	if ((portmap != NULL) && (pf_list == 0)) {
181		pf_list = PF_LIST_INET;
182#ifdef INET6
183		pf_list |= PF_LIST_INET6;
184#endif
185	}
186	if (pf_list == 0) {
187		pf_list = PF_LIST_INET | PF_LIST_LOCAL;
188#ifdef INET6
189		pf_list |= PF_LIST_INET6;
190#endif
191	}
192	if ((portmap != NULL) && (pf_list & PF_LIST_LOCAL))
193		errx(1, "local domain sockets do not have ports");
194
195	if (pf_list & PF_LIST_INET) {
196		get_sockets("net.inet.tcp.pcblist");
197		get_sockets("net.inet.udp.pcblist");
198		if (portmap == NULL)
199			get_sockets("net.inet.raw.pcblist");
200	}
201
202#ifdef INET6
203	if (pf_list & PF_LIST_INET6) {
204		get_sockets("net.inet6.tcp6.pcblist");
205		get_sockets("net.inet6.udp6.pcblist");
206		if (portmap == NULL)
207			get_sockets("net.inet6.raw6.pcblist");
208	}
209#endif
210
211	if (pf_list & PF_LIST_LOCAL) {
212		get_sockets("net.local.stream.pcblist");
213		get_sockets("net.local.seqpacket.pcblist");
214		get_sockets("net.local.dgram.pcblist");
215	}
216
217	get_files();
218
219	p.p_pid = 0;
220	for (i = 0; i < flistc; i++)
221		if ((kp = pick_socket(&flist[i])) != NULL &&
222		    get_proc(&p, flist[i].ki_pid) == 0)
223			print_socket(&flist[i], kp, &p);
224
225	return (0);
226}
227
228void
229parse_ports(const char *l)
230{
231	struct servent *srv;
232	const char *s, *e;
233	long i, j;
234
235	if (portmap == NULL) {
236		portmap = bit_alloc(65536);
237		if (portmap == NULL)
238			err(1, "malloc");
239	}
240
241	if ((srv = getservbyname(l, NULL)) != NULL) {
242		bit_set(portmap, ntohs(srv->s_port));
243		return;
244	}
245
246	s = e = l;
247	while (*s != '\0') {
248		i = get_num(l, &s, &e);
249		switch (*e) {
250		case ',':
251			e++;
252		case '\0':
253			bit_set(portmap, i);
254			s = e;
255			continue;
256		case '-':
257			s = ++e;
258			j = get_num(l, &s, &e);
259			for (; i <= j; i++)
260				bit_set(portmap, i);
261			break;
262		default:
263			errno = EINVAL;
264			err(1, "%s", l);
265		}
266	}
267}
268
269int
270get_num(const char *l, const char **s, const char **e)
271{
272	long x;
273	char *t;
274
275	while (isdigit((u_int)**e))
276		(*e)++;
277	if (*s != *e) {
278		errno = 0;
279		x = strtol(*s, &t, 0);
280		if (errno == 0 && x >= 0 && x <= 65535 && t == *e)
281			return (x);
282	}
283
284	errno = EINVAL;
285	err(1, "%s", l);
286}
287
288void
289get_sockets(const char *mib)
290{
291	void *v;
292	size_t sz;
293	int rc, n, name[CTL_MAXNAME];
294	u_int namelen;
295
296	sz = CTL_MAXNAME;
297	rc = sysctlnametomib(mib, &name[0], &sz);
298	if (rc == -1) {
299		if (errno == ENOENT)
300			return;
301		err(1, "sysctlnametomib: %s", mib);
302	}
303	namelen = sz;
304
305	name[namelen++] = PCB_ALL;
306	name[namelen++] = 0;		/* XXX all pids */
307	name[namelen++] = sizeof(struct kinfo_pcb);
308	name[namelen++] = INT_MAX;	/* all of them */
309
310	sysctl_sucker(&name[0], namelen, &v, &sz);
311	n = sz / sizeof(struct kinfo_pcb);
312	socket_add_hash(v, n);
313}
314
315void
316get_files(void)
317{
318	void *v;
319	size_t sz;
320	int rc, name[CTL_MAXNAME];
321	u_int namelen;
322
323	sz = CTL_MAXNAME;
324	rc = sysctlnametomib("kern.file2", &name[0], &sz);
325	if (rc == -1)
326		err(1, "sysctlnametomib");
327	namelen = sz;
328
329	name[namelen++] = KERN_FILE_BYPID;
330	name[namelen++] = 0;		/* XXX all pids */
331	name[namelen++] = sizeof(struct kinfo_file);
332	name[namelen++] = INT_MAX;	/* all of them */
333
334	sysctl_sucker(&name[0], namelen, &v, &sz);
335	flist = v;
336	flistc = sz / sizeof(struct kinfo_file);
337
338	qsort(flist, flistc, sizeof(*flist), sort_files);
339}
340
341int
342sort_files(const void *a, const void *b)
343{
344	const struct kinfo_file *ka = a, *kb = b;
345
346	if (ka->ki_pid == kb->ki_pid)
347		return (ka->ki_fd - kb->ki_fd);
348
349	return (ka->ki_pid - kb->ki_pid);
350}
351
352void
353sysctl_sucker(int *name, u_int namelen, void **vp, size_t *szp)
354{
355	int rc;
356	void *v;
357	size_t sz;
358
359	/* printf("name %p, namelen %u\n", name, namelen); */
360
361	v = NULL;
362	sz = 0;
363	do {
364		rc = prog_sysctl(&name[0], namelen, v, &sz, NULL, 0);
365		if (rc == -1 && errno != ENOMEM)
366			err(1, "sysctl");
367		if (rc == -1 && v != NULL) {
368			free(v);
369			v = NULL;
370		}
371		if (v == NULL) {
372			v = malloc(sz);
373			rc = -1;
374		}
375		if (v == NULL)
376			err(1, "malloc");
377	} while (rc == -1);
378
379	*vp = v;
380	*szp = sz;
381	/* printf("got %zu at %p\n", sz, v); */
382}
383
384void
385socket_add_hash(struct kinfo_pcb *kp, int n)
386{
387	struct sockitem *si;
388	int hash, i;
389
390	if (n == 0)
391		return;
392
393	si = malloc(sizeof(*si) * n);
394	if (si== NULL)
395		err(1, "malloc");
396
397	for (i = 0; i < n; i++) {
398		si[i].s_sock = &kp[i];
399		hash = (int)(kp[i].ki_sockaddr % HASHSIZE);
400		LIST_INSERT_HEAD(&sockhash[hash], &si[i], s_list);
401	}
402}
403
404int
405isconnected(struct kinfo_pcb *kp)
406{
407
408	if ((kp->ki_sostate & SS_ISCONNECTED) ||
409	    (kp->ki_prstate >= INP_CONNECTED) ||
410	    (kp->ki_tstate > TCPS_LISTEN) ||
411	    (kp->ki_conn != 0))
412		return (1);
413
414	return (0);
415}
416
417int
418islistening(struct kinfo_pcb *kp)
419{
420
421	if (isconnected(kp))
422		return (0);
423
424	if (kp->ki_tstate == TCPS_LISTEN)
425		return (1);
426
427	switch (kp->ki_family) {
428	case PF_INET:
429		if (kp->ki_type == SOCK_RAW ||
430		    (kp->ki_type == SOCK_DGRAM &&
431		     ntohs(satosin(&kp->ki_src)->sin_port) != 0))
432			return (1);
433		break;
434#ifdef INET6
435	case PF_INET6:
436		if (kp->ki_type == SOCK_RAW ||
437		    (kp->ki_type == SOCK_DGRAM &&
438		     ntohs(satosin6(&kp->ki_src)->sin6_port) != 0))
439			return (1);
440		break;
441#endif
442	case PF_LOCAL:
443		if (satosun(&kp->ki_src)->sun_path[0] != '\0')
444			return (1);
445		break;
446	default:
447		break;
448	}
449
450	return (0);
451}
452
453struct kinfo_pcb *
454pick_socket(struct kinfo_file *f)
455{
456	struct sockitem *si;
457	struct kinfo_pcb *kp;
458	int hash;
459
460	if (f->ki_ftype != DTYPE_SOCKET)
461		return (NULL);
462
463	hash = (int)(f->ki_fdata % HASHSIZE);
464	LIST_FOREACH(si, &sockhash[hash], s_list) {
465		if (si->s_sock->ki_sockaddr == f->ki_fdata)
466			break;
467	}
468	if (si == NULL)
469		return (NULL);
470
471	kp = si->s_sock;
472
473	if (only) {
474		if (isconnected(kp)) {
475			/*
476			 * connected but you didn't say you wanted
477			 * connected sockets
478			 */
479			if (!(only & ONLY_CONNECTED))
480				return (NULL);
481		}
482		else if (islistening(kp)) {
483			/*
484			 * listening but you didn't ask for listening
485			 * sockets
486			 */
487			if (!(only & ONLY_LISTEN))
488				return (NULL);
489		}
490		else
491			/*
492			 * neither connected nor listening, so you
493			 * don't get it
494			 */
495			return (NULL);
496	}
497
498	if (portmap) {
499		switch (kp->ki_family) {
500		case AF_INET:
501			if (!bit_test(portmap,
502				      ntohs(satosin(&kp->ki_src)->sin_port)) &&
503			    !bit_test(portmap,
504				      ntohs(satosin(&kp->ki_dst)->sin_port)))
505				return (NULL);
506			break;
507#ifdef INET6
508		case AF_INET6:
509			if (!bit_test(portmap,
510			    ntohs(satosin6(&kp->ki_src)->sin6_port)) &&
511			    !bit_test(portmap,
512				      ntohs(satosin6(&kp->ki_dst)->sin6_port)))
513				return (NULL);
514			break;
515#endif
516		default:
517			return (NULL);
518		}
519	}
520
521	return (kp);
522}
523
524int
525get_proc(struct kinfo_proc2 *p, int pid)
526{
527	int name[6];
528	u_int namelen;
529	size_t sz;
530
531	if (p->p_pid == pid)
532		return (0);
533
534	sz = sizeof(*p);
535	namelen = 0;
536	name[namelen++] = CTL_KERN;
537	name[namelen++] = KERN_PROC2;
538	name[namelen++] = KERN_PROC_PID;
539	name[namelen++] = pid;
540	name[namelen++] = sz;
541	name[namelen++] = 1;
542
543	return (prog_sysctl(&name[0], namelen, p, &sz, NULL, 0));
544}
545
546int
547print_socket(struct kinfo_file *kf, struct kinfo_pcb *kp, struct kinfo_proc2 *p)
548{
549	static int first = 1;
550	struct passwd *pw;
551	const char *t;
552	char proto[22];
553
554	if (first) {
555		printf("%-8s " "%-10s "   "%-5s " "%-2s " "%-6s "
556		       "%-21s "         "%s\n",
557		       "USER", "COMMAND", "PID",  "FD",   "PROTO",
558		       "LOCAL ADDRESS", "FOREIGN ADDRESS");
559		first = 0;
560	}
561
562	if ((pw = getpwuid(p->p_uid)) != NULL)
563		printf("%-8s ", pw->pw_name);
564	else
565		printf("%-8d ", (int)p->p_uid);
566
567	printf("%-10.10s ", p->p_comm);
568	printf("%-5d ", (int)kf->ki_pid);
569	printf("%2d ", (int)kf->ki_fd);
570
571	snprintf(proto, sizeof(proto), "%d/%d", kp->ki_family, kp->ki_protocol);
572
573	switch (kp->ki_family) {
574	case PF_INET:
575		switch (kp->ki_protocol) {
576		case IPPROTO_TCP:	t = "tcp";	break;
577		case IPPROTO_UDP:	t = "udp";	break;
578		case IPPROTO_RAW:	t = "raw";	break;
579		default:		t = proto;	break;
580		}
581		break;
582#ifdef INET6
583	case PF_INET6:
584		switch (kp->ki_protocol) {
585		case IPPROTO_TCP:	t = "tcp6";	break;
586		case IPPROTO_UDP:	t = "udp6";	break;
587		case IPPROTO_RAW:	t = "raw6";	break;
588		default:		t = proto;	break;
589		}
590		break;
591#endif
592	case PF_LOCAL:
593		switch (kp->ki_type) {
594		case SOCK_STREAM:	t = "stream";	break;
595		case SOCK_DGRAM:	t = "dgram";	break;
596		case SOCK_RAW:		t = "raw";	break;
597		case SOCK_RDM:		t = "rdm";	break;
598		case SOCK_SEQPACKET:	t = "seq";	break;
599		default:		t = proto;	break;
600		}
601		break;
602	default:
603		snprintf(proto, sizeof(proto), "%d/%d/%d",
604			 kp->ki_family, kp->ki_type, kp->ki_protocol);
605		t = proto;
606		break;
607	}
608
609	printf("%-6s ", t);
610
611/*
612	if (kp->ki_family == PF_LOCAL) {
613		if (kp->ki_src.sa_len > 2) {
614			print_addr(0, kp->ki_type, kp->ki_pflags, &kp->ki_src);
615			if (kp->ki_dst.sa_family == PF_LOCAL)
616				printf(" ");
617		}
618		if (kp->ki_dst.sa_family == PF_LOCAL)
619			printf("-> ");
620	}
621	else */{
622		print_addr(21, kp->ki_type, kp->ki_pflags, &kp->ki_src);
623		printf(" ");
624	}
625
626	if (isconnected(kp))
627		print_addr(0, kp->ki_type, kp->ki_pflags, &kp->ki_dst);
628	else if (kp->ki_family == PF_INET
629#ifdef INET6
630	    || kp->ki_family == PF_INET6
631#endif
632	    )
633		printf("%-*s", 0, "*.*");
634	/* else if (kp->ki_src.sa_len == 2)
635	   printf("%-*s", 0, "-"); */
636	else
637		printf("-");
638
639	printf("\n");
640
641	return (0);
642}
643
644void
645print_addr(int l, int t, int f, struct sockaddr *sa)
646{
647	char sabuf[256], pbuf[32];
648	int r = 0;
649
650	if (!(f & INP_ANONPORT))
651		f = 0;
652	else
653		f = NI_NUMERICSERV;
654	if (t == SOCK_DGRAM)
655		f |= NI_DGRAM;
656	if (nonames)
657		f |= NI_NUMERICHOST|NI_NUMERICSERV;
658
659	getnameinfo(sa, sa->sa_len, sabuf, sizeof(sabuf),
660		    pbuf, sizeof(pbuf), f);
661
662	switch (sa->sa_family) {
663	case PF_UNSPEC:
664		r = printf("(PF_UNSPEC)");
665		break;
666	case PF_INET: {
667		struct sockaddr_in *si = satosin(sa);
668		if (si->sin_addr.s_addr != INADDR_ANY)
669			r = printf("%s.%s", sabuf, pbuf);
670		else if (ntohs(si->sin_port) != 0)
671			r = printf("*.%s", pbuf);
672		else
673			r = printf("*.*");
674		break;
675	}
676#ifdef INET6
677	case PF_INET6: {
678		struct sockaddr_in6 *si6 = satosin6(sa);
679		if (!IN6_IS_ADDR_UNSPECIFIED(&si6->sin6_addr))
680			r = printf("%s.%s", sabuf, pbuf);
681		else if (ntohs(si6->sin6_port) != 0)
682			r = printf("*.%s", pbuf);
683		else
684			r = printf("*.*");
685		break;
686	}
687#endif
688	case PF_LOCAL: {
689		struct sockaddr_un *sun = satosun(sa);
690		r = printf("%s", sun->sun_path);
691		if (r == 0)
692			r = printf("-");
693		break;
694	}
695	default:
696		break;
697	}
698
699	if (r > 0)
700		l -= r;
701	if (l > 0)
702		printf("%*s", l, "");
703}
704