xref: /dragonfly/usr.bin/sockstat/sockstat.c (revision 81c11cd3)
1 /*-
2  * Copyright (c) 2005 Joerg Sonnenberger <joerg@bec.de>.  All rights reserved.
3  * Copyright (c) 2002 Dag-Erling Co�dan Sm�rgrav
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer
11  *    in this position and unchanged.
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. The name of the author may not be used to endorse or promote products
16  *    derived from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  *
29  * $FreeBSD: src/usr.bin/sockstat/sockstat.c,v 1.12 2004/12/06 09:28:05 ru Exp $
30  * $DragonFly: src/usr.bin/sockstat/sockstat.c,v 1.7 2007/02/01 10:33:26 corecode Exp $
31  */
32 
33 #include <sys/user.h>
34 #include <sys/param.h>
35 #include <sys/socket.h>
36 #include <sys/socketvar.h>
37 #include <sys/sysctl.h>
38 #include <sys/file.h>
39 
40 #include <sys/un.h>
41 #include <sys/unpcb.h>
42 
43 #include <net/route.h>
44 
45 #include <netinet/in.h>
46 #include <netinet/in_pcb.h>
47 #include <netinet/tcp.h>
48 #include <netinet/tcp_seq.h>
49 #include <netinet/tcp_var.h>
50 #include <arpa/inet.h>
51 
52 #include <ctype.h>
53 #include <err.h>
54 #include <errno.h>
55 #include <kinfo.h>
56 #include <netdb.h>
57 #include <pwd.h>
58 #include <stdarg.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <unistd.h>
63 
64 static int	 opt_4;		/* Show IPv4 sockets */
65 static int	 opt_6;		/* Show IPv6 sockets */
66 static int	 opt_c;		/* Show connected sockets */
67 static int	 opt_l;		/* Show listening sockets */
68 static int	 opt_u;		/* Show Unix domain sockets */
69 static int	 opt_v;		/* Verbose mode */
70 
71 static int	*ports;
72 
73 #define INT_BIT (sizeof(int)*CHAR_BIT)
74 #define SET_PORT(p) do { ports[p / INT_BIT] |= 1 << (p % INT_BIT); } while (0)
75 #define CHK_PORT(p) (ports[p / INT_BIT] & (1 << (p % INT_BIT)))
76 
77 struct sock {
78 	void *socket;
79 	void *pcb;
80 	int vflag;
81 	int family;
82 	int proto;
83 	const char *protoname;
84 	struct sockaddr_storage laddr;
85 	struct sockaddr_storage faddr;
86 	struct sock *next;
87 };
88 
89 static int xprintf(const char *, ...) __printflike(1, 2);
90 
91 #define HASHSIZE 1009
92 static struct sock *sockhash[HASHSIZE];
93 
94 static struct kinfo_file *xfiles;
95 static size_t nxfiles;
96 
97 static int
98 xprintf(const char *fmt, ...)
99 {
100 	va_list ap;
101 	int len;
102 
103 	va_start(ap, fmt);
104 	len = vprintf(fmt, ap);
105 	va_end(ap);
106 	if (len < 0)
107 		err(1, "printf()");
108 	return (len);
109 }
110 
111 static void
112 parse_ports(const char *portspec)
113 {
114 	const char *p, *q;
115 	int port, end;
116 
117 	if (ports == NULL)
118 		if ((ports = calloc(65536 / INT_BIT, sizeof(int))) == NULL)
119 			err(1, "calloc()");
120 	p = portspec;
121 	while (*p != '\0') {
122 		if (!isdigit(*p))
123 			errx(1, "syntax error in port range");
124 		for (q = p; *q != '\0' && isdigit(*q); ++q)
125 			/* nothing */ ;
126 		for (port = 0; p < q; ++p)
127 			port = port * 10 + (*p - '0');
128 		if (port < 0 || port > 65535)
129 			errx(1, "invalid port number");
130 		SET_PORT(port);
131 		switch (*p) {
132 		case '-':
133 			++p;
134 			break;
135 		case ',':
136 			++p;
137 			/* fall through */
138 		case '\0':
139 		default:
140 			continue;
141 		}
142 		for (q = p; *q != '\0' && isdigit(*q); ++q)
143 			/* nothing */ ;
144 		for (end = 0; p < q; ++p)
145 			end = end * 10 + (*p - '0');
146 		if (end < port || end > 65535)
147 			errx(1, "invalid port number");
148 		while (port++ < end)
149 			SET_PORT(port);
150 		if (*p == ',')
151 			++p;
152 	}
153 }
154 
155 static void
156 sockaddr(struct sockaddr_storage *sa, int af, void *addr, int port)
157 {
158 	struct sockaddr_in *sin4;
159 	struct sockaddr_in6 *sin6;
160 
161 	bzero(sa, sizeof *sa);
162 	switch (af) {
163 	case AF_INET:
164 		sin4 = (struct sockaddr_in *)sa;
165 		sin4->sin_len = sizeof *sin4;
166 		sin4->sin_family = af;
167 		sin4->sin_port = port;
168 		sin4->sin_addr = *(struct in_addr *)addr;
169 		break;
170 	case AF_INET6:
171 		sin6 = (struct sockaddr_in6 *)sa;
172 		sin6->sin6_len = sizeof *sin6;
173 		sin6->sin6_family = af;
174 		sin6->sin6_port = port;
175 		sin6->sin6_addr = *(struct in6_addr *)addr;
176 		break;
177 	default:
178 		abort();
179 	}
180 }
181 
182 static void
183 gather_inet(int proto)
184 {
185 	void *so_begin, *so_end;
186 	struct xinpcb *xip;
187 	struct xtcpcb *xtp;
188 	struct inpcb *inp;
189 	struct xsocket *so;
190 	struct sock *sock;
191 	const char *varname, *protoname;
192 	size_t len;
193 	void *buf;
194 	int hash, vflag;
195 
196 	vflag = 0;
197 	if (opt_4)
198 		vflag |= INP_IPV4;
199 	if (opt_6)
200 		vflag |= INP_IPV6;
201 
202 	switch (proto) {
203 	case IPPROTO_TCP:
204 		varname = "net.inet.tcp.pcblist";
205 		protoname = "tcp";
206 		break;
207 	case IPPROTO_UDP:
208 		varname = "net.inet.udp.pcblist";
209 		protoname = "udp";
210 		break;
211 	case IPPROTO_DIVERT:
212 		varname = "net.inet.divert.pcblist";
213 		protoname = "div";
214 		break;
215 	default:
216 		abort();
217 	}
218 
219 	buf = NULL;
220 	len = 0;
221 
222 	if (sysctlbyname(varname, NULL, &len, NULL, 0)) {
223 		if (errno == ENOENT)
224 			goto out;
225 		err(1, "fetching %s", varname);
226 	}
227 	if ((buf = malloc(len)) == NULL)
228 		err(1, "malloc()");
229 	if (sysctlbyname(varname, buf, &len, NULL, 0)) {
230 		if (errno == ENOENT)
231 			goto out;
232 		err(1, "fetching %s", varname);
233 	}
234 
235 	so_begin = buf;
236 	so_end = (uint8_t *)buf + len;
237 
238 	for (so_begin = buf, so_end = (uint8_t *)so_begin + len;
239 	     (uint8_t *)so_begin + sizeof(size_t) < (uint8_t *)so_end &&
240 	     (uint8_t *)so_begin + *(size_t *)so_begin <= (uint8_t *)so_end;
241 	     so_begin = (uint8_t *)so_begin + *(size_t *)so_begin) {
242 		switch (proto) {
243 		case IPPROTO_TCP:
244 			xtp = (struct xtcpcb *)so_begin;
245 			if (xtp->xt_len != sizeof *xtp) {
246 				warnx("struct xtcpcb size mismatch");
247 				goto out;
248 			}
249 			inp = &xtp->xt_inp;
250 			so = &xtp->xt_socket;
251 			break;
252 		case IPPROTO_UDP:
253 		case IPPROTO_DIVERT:
254 			xip = (struct xinpcb *)so_begin;
255 			if (xip->xi_len != sizeof *xip) {
256 				warnx("struct xinpcb size mismatch");
257 				goto out;
258 			}
259 			inp = &xip->xi_inp;
260 			so = &xip->xi_socket;
261 			break;
262 		default:
263 			abort();
264 		}
265 		if ((inp->inp_vflag & vflag) == 0)
266 			continue;
267 		if (inp->inp_vflag & INP_IPV4) {
268 			if ((inp->inp_fport == 0 && !opt_l) ||
269 			    (inp->inp_fport != 0 && !opt_c))
270 				continue;
271 		} else if (inp->inp_vflag & INP_IPV6) {
272 			if ((inp->in6p_fport == 0 && !opt_l) ||
273 			    (inp->in6p_fport != 0 && !opt_c))
274 				continue;
275 		} else {
276 			if (opt_v)
277 				warnx("invalid vflag 0x%x", inp->inp_vflag);
278 			continue;
279 		}
280 		if ((sock = calloc(1, sizeof *sock)) == NULL)
281 			err(1, "malloc()");
282 		sock->socket = so->xso_so;
283 		sock->proto = proto;
284 		if (inp->inp_vflag & INP_IPV4) {
285 			sock->family = AF_INET;
286 			sockaddr(&sock->laddr, sock->family,
287 			    &inp->inp_laddr, inp->inp_lport);
288 			sockaddr(&sock->faddr, sock->family,
289 			    &inp->inp_faddr, inp->inp_fport);
290 		} else if (inp->inp_vflag & INP_IPV6) {
291 			sock->family = AF_INET6;
292 			sockaddr(&sock->laddr, sock->family,
293 			    &inp->in6p_laddr, inp->in6p_lport);
294 			sockaddr(&sock->faddr, sock->family,
295 			    &inp->in6p_faddr, inp->in6p_fport);
296 		}
297 		sock->vflag = inp->inp_vflag;
298 		sock->protoname = protoname;
299 		hash = (int)((uintptr_t)sock->socket % HASHSIZE);
300 		sock->next = sockhash[hash];
301 		sockhash[hash] = sock;
302 	}
303 out:
304 	free(buf);
305 }
306 
307 static void
308 gather_unix(int proto)
309 {
310 	void *so_begin, *so_end;
311 	struct xunpcb *xup;
312 	struct sock *sock;
313 	const char *varname, *protoname;
314 	size_t len;
315 	void *buf;
316 	int hash;
317 
318 	switch (proto) {
319 	case SOCK_STREAM:
320 		varname = "net.local.stream.pcblist";
321 		protoname = "stream";
322 		break;
323 	case SOCK_DGRAM:
324 		varname = "net.local.dgram.pcblist";
325 		protoname = "dgram";
326 		break;
327 	default:
328 		abort();
329 	}
330 
331 	buf = NULL;
332 	len = 0;
333 
334 	if (sysctlbyname(varname, NULL, &len, NULL, 0))
335 		err(1, "fetching %s", varname);
336 
337 	if ((buf = malloc(len)) == NULL)
338 		err(1, "malloc()");
339 	if (sysctlbyname(varname, buf, &len, NULL, 0))
340 		err(1, "fetching %s", varname);
341 
342 	for (so_begin = buf, so_end = (uint8_t *)buf + len;
343 	     (uint8_t *)so_begin + sizeof(size_t) < (uint8_t *)so_end &&
344 	     (uint8_t *)so_begin + *(size_t *)so_begin <= (uint8_t *)so_end;
345 	     so_begin = (uint8_t *)so_begin + *(size_t *)so_begin) {
346 		xup = so_begin;
347 		if (xup->xu_len != sizeof *xup) {
348 			warnx("struct xunpcb size mismatch");
349 			goto out;
350 		}
351 		if ((xup->xu_unp.unp_conn == NULL && !opt_l) ||
352 		    (xup->xu_unp.unp_conn != NULL && !opt_c))
353 			continue;
354 		if ((sock = calloc(1, sizeof *sock)) == NULL)
355 			err(1, "malloc()");
356 		sock->socket = xup->xu_socket.xso_so;
357 		sock->pcb = xup->xu_unpp;
358 		sock->proto = proto;
359 		sock->family = AF_UNIX;
360 		sock->protoname = protoname;
361 		if (xup->xu_unp.unp_addr != NULL)
362 			sock->laddr =
363 			    *(struct sockaddr_storage *)(void *)&xup->xu_addr;
364 		else if (xup->xu_unp.unp_conn != NULL)
365 			*(void **)&sock->faddr = xup->xu_unp.unp_conn;
366 		hash = (int)((uintptr_t)sock->socket % HASHSIZE);
367 		sock->next = sockhash[hash];
368 		sockhash[hash] = sock;
369 	}
370 out:
371 	free(buf);
372 }
373 
374 static void
375 getfiles(void)
376 {
377 	if (kinfo_get_files(&xfiles, &nxfiles))
378 		err(1, "kinfo_get_files");
379 }
380 
381 static int
382 printaddr(int af, struct sockaddr_storage *ss)
383 {
384 	char addrstr[INET6_ADDRSTRLEN] = { '\0', '\0' };
385 	struct sockaddr_un *sun;
386 	void *addr;
387 	int off, port;
388 
389 	switch (af) {
390 	case AF_INET:
391 		addr = &((struct sockaddr_in *)ss)->sin_addr;
392 		if (inet_lnaof(*(struct in_addr *)addr) == INADDR_ANY)
393 			addrstr[0] = '*';
394 		port = ntohs(((struct sockaddr_in *)ss)->sin_port);
395 		break;
396 	case AF_INET6:
397 		addr = &((struct sockaddr_in6 *)ss)->sin6_addr;
398 		if (IN6_IS_ADDR_UNSPECIFIED((struct in6_addr *)addr))
399 			addrstr[0] = '*';
400 		port = ntohs(((struct sockaddr_in6 *)ss)->sin6_port);
401 		break;
402 	case AF_UNIX:
403 		sun = (struct sockaddr_un *)ss;
404 		off = (int)((char *)&sun->sun_path - (char *)sun);
405 		return (xprintf("%.*s", sun->sun_len - off, sun->sun_path));
406 	default:
407 		abort();
408 	}
409 	if (addrstr[0] == '\0')
410 		inet_ntop(af, addr, addrstr, sizeof addrstr);
411 	if (port == 0)
412 		return xprintf("%s:*", addrstr);
413 	else
414 		return xprintf("%s:%d", addrstr, port);
415 }
416 
417 static const char *
418 getprocname(pid_t pid)
419 {
420 	static struct kinfo_proc proc;
421 	size_t len;
422 	int mib[4];
423 
424 	mib[0] = CTL_KERN;
425 	mib[1] = KERN_PROC;
426 	mib[2] = KERN_PROC_PID;
427 	mib[3] = (int)pid;
428 	len = sizeof proc;
429 	if (sysctl(mib, 4, &proc, &len, NULL, 0) == -1) {
430 		warn("sysctl()");
431 		return ("??");
432 	}
433 	return (proc.kp_comm);
434 }
435 
436 static int
437 check_ports(struct sock *s)
438 {
439 	int port;
440 
441 	if (ports == NULL)
442 		return (1);
443 	if ((s->family != AF_INET) && (s->family != AF_INET6))
444 		return (1);
445 	if (s->family == AF_INET)
446 		port = ntohs(((struct sockaddr_in *)(&s->laddr))->sin_port);
447 	else
448 		port = ntohs(((struct sockaddr_in6 *)(&s->laddr))->sin6_port);
449 	if (CHK_PORT(port))
450 		return (1);
451 	if (s->family == AF_INET)
452 		port = ntohs(((struct sockaddr_in *)(&s->faddr))->sin_port);
453 	else
454 		port = ntohs(((struct sockaddr_in6 *)(&s->faddr))->sin6_port);
455 	if (CHK_PORT(port))
456 		return (1);
457 	return (0);
458 }
459 
460 static void
461 display(void)
462 {
463 	struct passwd *pwd;
464 	struct kinfo_file *xf;
465 	struct sock *s;
466 	void *p;
467 	int hash, n, pos;
468 
469 	printf("%-8s %-10s %-5s %-2s %-6s %-21s %-21s\n",
470 	    "USER", "COMMAND", "PID", "FD", "PROTO",
471 	    "LOCAL ADDRESS", "FOREIGN ADDRESS");
472 	setpassent(1);
473 	for (xf = xfiles, n = 0; n < nxfiles; ++n, ++xf) {
474 		if (xf->f_data == NULL)
475 			continue;
476 		hash = (int)((uintptr_t)xf->f_data % HASHSIZE);
477 		for (s = sockhash[hash]; s != NULL; s = s->next)
478 			if ((void *)s->socket == xf->f_data)
479 				break;
480 		if (s == NULL)
481 			continue;
482 		if (!check_ports(s))
483 			continue;
484 		pos = 0;
485 		if ((pwd = getpwuid(xf->f_uid)) == NULL)
486 			pos += xprintf("%lu", (u_long)xf->f_uid);
487 		else
488 			pos += xprintf("%s", pwd->pw_name);
489 		while (pos < 9)
490 			pos += xprintf(" ");
491 		pos += xprintf("%.10s", getprocname(xf->f_pid));
492 		while (pos < 20)
493 			pos += xprintf(" ");
494 		pos += xprintf("%lu", (u_long)xf->f_pid);
495 		while (pos < 26)
496 			pos += xprintf(" ");
497 		pos += xprintf("%d", xf->f_fd);
498 		while (pos < 29)
499 			pos += xprintf(" ");
500 		pos += xprintf("%s", s->protoname);
501 		if (s->vflag & INP_IPV4)
502 			pos += xprintf("4");
503 		if (s->vflag & INP_IPV6)
504 			pos += xprintf("6");
505 		while (pos < 36)
506 			pos += xprintf(" ");
507 		switch (s->family) {
508 		case AF_INET:
509 		case AF_INET6:
510 			pos += printaddr(s->family, &s->laddr);
511 			while (pos < 57)
512 				pos += xprintf(" ");
513 			pos += xprintf(" ");
514 			pos += printaddr(s->family, &s->faddr);
515 			break;
516 		case AF_UNIX:
517 			/* server */
518 			if (s->laddr.ss_len > 0) {
519 				pos += printaddr(s->family, &s->laddr);
520 				break;
521 			}
522 			/* client */
523 			p = *(void **)&s->faddr;
524 			if (p == NULL) {
525 				pos += xprintf("(not connected)");
526 				break;
527 			}
528 			pos += xprintf("-> ");
529 			for (hash = 0; hash < HASHSIZE; ++hash) {
530 				for (s = sockhash[hash]; s != NULL; s = s->next)
531 					if (s->pcb == p)
532 						break;
533 				if (s != NULL)
534 					break;
535 			}
536 			if (s == NULL || s->laddr.ss_len == 0)
537 				pos += xprintf("??");
538 			else
539 				pos += printaddr(s->family, &s->laddr);
540 			break;
541 		default:
542 			abort();
543 		}
544 		xprintf("\n");
545 	}
546 }
547 
548 static void
549 usage(void)
550 {
551 	fprintf(stderr, "Usage: sockstat [-46clu] [-p ports]\n");
552 	exit(1);
553 }
554 
555 int
556 main(int argc, char *argv[])
557 {
558 	int o;
559 
560 	while ((o = getopt(argc, argv, "46clp:uv")) != -1)
561 		switch (o) {
562 		case '4':
563 			opt_4 = 1;
564 			break;
565 		case '6':
566 			opt_6 = 1;
567 			break;
568 		case 'c':
569 			opt_c = 1;
570 			break;
571 		case 'l':
572 			opt_l = 1;
573 			break;
574 		case 'p':
575 			parse_ports(optarg);
576 			break;
577 		case 'u':
578 			opt_u = 1;
579 			break;
580 		case 'v':
581 			++opt_v;
582 			break;
583 		default:
584 			usage();
585 		}
586 
587 	argc -= optind;
588 	argv += optind;
589 
590 	if (argc > 0)
591 		usage();
592 
593 	if (!opt_4 && !opt_6 && !opt_u)
594 		opt_4 = opt_6 = opt_u = 1;
595 	if (!opt_c && !opt_l)
596 		opt_c = opt_l = 1;
597 
598 	if (opt_4 || opt_6) {
599 		gather_inet(IPPROTO_TCP);
600 		gather_inet(IPPROTO_UDP);
601 		gather_inet(IPPROTO_DIVERT);
602 	}
603 	if (opt_u) {
604 		gather_unix(SOCK_STREAM);
605 		gather_unix(SOCK_DGRAM);
606 	}
607 	getfiles();
608 	display();
609 
610 	exit(0);
611 }
612