xref: /illumos-gate/usr/src/cmd/connstat/connstat_main.c (revision a73ed278e8e65ae8780cbb2efbcb6ed43e0f3c69)
1 /*
2  * CDDL HEADER START
3  *
4  * This file and its contents are supplied under the terms of the
5  * Common Development and Distribution License ("CDDL"), version 1.0.
6  * You may only use this file in accordance with the terms of version
7  * 1.0 of the CDDL.
8  *
9  * A full copy of the text of the CDDL should have accompanied this
10  * source.  A copy of the CDDL is also available via the Internet at
11  * http://www.illumos.org/license/CDDL.
12  *
13  * CDDL HEADER END
14  */
15 /*
16  * Copyright (c) 2015, 2016 by Delphix. All rights reserved.
17  */
18 
19 #include <err.h>
20 #include <stdio.h>
21 #include <errno.h>
22 #include <getopt.h>
23 #include <stdlib.h>
24 #include <stddef.h>
25 #include <strings.h>
26 #include <unistd.h>
27 #include <libgen.h>
28 #include <libintl.h>
29 #include <limits.h>
30 #include <locale.h>
31 #include <langinfo.h>
32 #include <sys/types.h>
33 #include <sys/socket.h>
34 #include <netdb.h>
35 #include <sys/varargs.h>
36 #include <ofmt.h>
37 #include <inet/tcp.h>
38 #include <netinet/in.h>
39 #include <inet/mib2.h>
40 #include "connstat.h"
41 #include "connstat_mib.h"
42 #include "connstat_tcp.h"
43 
44 #define	DEFAULT_PROTO	"tcp"
45 
46 static const char *invalid_v4v6_msg =
47 	"Invalid combination of IPv4 and IPv6 arguments\n";
48 
49 static const char *invalid_T_msg =
50 	"Invalid -T arg \"%s\". Must be \"u\" or \"d\"\n";
51 
52 static const struct option longopts[] = {
53 	{ "count",	required_argument,	0, 'c'	},
54 	{ "established",	no_argument,	0, 'e'	},
55 	{ "filter",	required_argument,	0, 'F'	},
56 	{ "help",	no_argument,		0, 'h'	},
57 	{ "interval",	required_argument,	0, 'i'	},
58 	{ "ipv4",	no_argument,		0, '4'	},
59 	{ "ipv6",	no_argument,		0, '6'	},
60 	{ "no-loopback",	no_argument,	0, 'L'	},
61 	{ "output",	required_argument,	0, 'o'	},
62 	{ "parsable",	no_argument,		0, 'P'	},
63 	{ "protocol",	required_argument,	0, 'p'	},
64 	{ "timestamp",	required_argument,	0, 'T'	},
65 	{ NULL, 0, 0, 0 }
66 };
67 
68 static connstat_proto_t connstat_protos[] = {
69 	CONNSTAT_TCP_PROTO,
70 	{ NULL, NULL, 0, 0, 0, NULL, NULL, NULL }
71 };
72 
73 typedef enum { NOTIMESTAMP, UTIMESTAMP, DTIMESTAMP } timestamp_fmt_t;
74 
75 static void	die(const char *, ...) __NORETURN;
76 static void	process_filter(char *, connstat_conn_attr_t *, uint_t *);
77 static void	show_stats(connstat_proto_t *, ofmt_handle_t, uint_t,
78     connstat_conn_attr_t *, timestamp_fmt_t, uint_t, uint_t);
79 
80 static void __NORETURN
81 usage(int code)
82 {
83 	static const char *opts[] = {
84 		"-4, --ipv4             Only display IPv4 connections",
85 		"-6, --ipv6             Only display IPv6 connections",
86 		"-c, --count=COUNT      Only print COUNT reports",
87 		"-e, --established      Only display established connections",
88 		"-F, --filter=FILTER    Only display connections that match "
89 		    "FILTER",
90 		"-h, --help             Print this help",
91 		"-i, --interval=SECONDS Report once every SECONDS seconds",
92 		"-L, --no-loopback      Omit loopback connections",
93 		"-o, --output=FIELDS    Restrict output to the comma-separated "
94 		    "list of fields\n"
95 		    "                         specified",
96 		"-P, --parsable         Parsable output mode",
97 		"-T, --timestamp=TYPE   Display a timestamp for each iteration",
98 		NULL
99 	};
100 
101 	(void) fprintf(stderr, gettext("usage: "));
102 	(void) fprintf(stderr,
103 	    gettext("%s [-eLP] [-4|-6] [-T d|u] [-F <filter>]\n"
104 	    "               [-i <interval> [-c <count>]] [-o <field>[,...]]\n"),
105 	    getprogname());
106 
107 	(void) fprintf(stderr, gettext("\nOptions:\n"));
108 	for (const char **optp = opts; *optp != NULL; optp++) {
109 		(void) fprintf(stderr, "  %s\n", gettext(*optp));
110 	}
111 
112 	(void) fprintf(stderr, gettext("\nFilter:\n"));
113 	(void) fprintf(stderr, gettext("  The FILTER argument for the -F "
114 	    "option is of the form:\n"
115 	    "    <field>=<value>,[<field>=<value>,...]\n"));
116 	(void) fprintf(stderr, gettext("  Filterable fields are laddr, lport, "
117 	    "raddr, rport, and state.\n"));
118 
119 	(void) fprintf(stderr, gettext("\nFields:\n"));
120 	(void) fprintf(stderr, gettext(
121 	    "  laddr           Local IP address\n"
122 	    "  raddr           Remote IP address\n"
123 	    "  lport           Local port\n"
124 	    "  rport           Remote port\n"
125 	    "  inbytes         Total bytes received\n"
126 	    "  insegs          Total segments received\n"
127 	    "  inunorderbytes  Bytes received out of order\n"
128 	    "  inunordersegs   Segments received out of order\n"
129 	    "  outbytes        Total bytes sent\n"
130 	    "  outsegs         Total segments sent\n"
131 	    "  retransbytes    Bytes retransmitted\n"
132 	    "  retranssegs     Segments retransmitted\n"
133 	    "  suna            Current unacknowledged bytes sent\n"
134 	    "  unsent          Unsent bytes on the transmit queue\n"
135 	    "  swnd            Send window size (peer's receive window)\n"
136 	    "  cwnd            Congestion window size\n"
137 	    "  rwnd            Receive window size\n"
138 	    "  mss             Maximum segment size\n"
139 	    "  rto             Retransmission timeout (ms)\n"
140 	    "  rtt             Smoothed round-trip time (us)\n"
141 	    "  rtts            Sum round-trip time (us)\n"
142 	    "  rttc            Count of round-trip times\n"
143 	    "  state           Connection state\n"));
144 	exit(code);
145 }
146 
147 static connstat_proto_t *
148 getproto(const char *proto)
149 {
150 	for (connstat_proto_t *current = &connstat_protos[0];
151 	    current->csp_proto != NULL; current++) {
152 		if (strcasecmp(proto, current->csp_proto) == 0) {
153 			return (current);
154 		}
155 	}
156 	return (NULL);
157 }
158 
159 int
160 main(int argc, char *argv[])
161 {
162 	int option;
163 	int count = 0;
164 	int interval = 0;
165 	const char *errstr = NULL;
166 	char *fields = NULL;
167 	char *filterstr = NULL;
168 	connstat_conn_attr_t filter = {0};
169 	char *protostr = DEFAULT_PROTO;
170 	connstat_proto_t *proto;
171 	ofmt_handle_t ofmt;
172 	ofmt_status_t oferr;
173 	char oferrbuf[OFMT_BUFSIZE];
174 	uint_t ofmtflags = OFMT_NOHEADER;
175 	uint_t flags = CS_LOOPBACK | CS_IPV4 | CS_IPV6;
176 	timestamp_fmt_t timestamp_fmt = NOTIMESTAMP;
177 
178 	(void) setlocale(LC_ALL, "");
179 #if !defined(TEXT_DOMAIN)
180 #define	TEXT_DOMAIN "SYS_TEST"
181 #endif
182 	(void) textdomain(TEXT_DOMAIN);
183 
184 	setprogname(basename(argv[0]));
185 
186 	while ((option = getopt_long(argc, argv, "c:eF:hi:Lo:Pp:T:46",
187 	    longopts, NULL)) != -1) {
188 		switch (option) {
189 		case 'c':
190 			count = strtonum(optarg, 1, INT_MAX, &errstr);
191 			if (errstr != NULL) {
192 				(void) fprintf(stderr, gettext(
193 				    "error parsing -c argument (%s): %s\n"),
194 				    optarg, errstr);
195 				usage(1);
196 			}
197 			break;
198 		case 'e':
199 			flags |= CS_STATE;
200 			filter.ca_state = TCPS_ESTABLISHED;
201 			break;
202 		case 'F':
203 			filterstr = optarg;
204 			break;
205 		case 'i':
206 			interval = strtonum(optarg, 1, INT_MAX, &errstr);
207 			if (errstr != NULL) {
208 				(void) fprintf(stderr, gettext(
209 				    "error parsing -i argument (%s): %s\n"),
210 				    optarg, errstr);
211 				usage(1);
212 			}
213 			break;
214 		case 'L':
215 			flags &= ~CS_LOOPBACK;
216 			break;
217 		case 'o':
218 			fields = optarg;
219 			break;
220 		case 'P':
221 			ofmtflags |= OFMT_PARSABLE;
222 			flags |= CS_PARSABLE;
223 			break;
224 		case 'p':
225 			/*
226 			 * -p is an undocumented flag whose only supported
227 			 * argument is "tcp". The idea is to reserve this
228 			 * flag for potential future use in case connstat
229 			 * is extended to support stats for other protocols.
230 			 */
231 			protostr = optarg;
232 			break;
233 		case 'T':
234 			if (strcmp(optarg, "u") == 0) {
235 				timestamp_fmt = UTIMESTAMP;
236 			} else if (strcmp(optarg, "d") == 0) {
237 				timestamp_fmt = DTIMESTAMP;
238 			} else {
239 				(void) fprintf(stderr, gettext(
240 				    invalid_T_msg), optarg);
241 				usage(1);
242 			}
243 			break;
244 		case '4':
245 			if (!(flags & CS_IPV4)) {
246 				(void) fprintf(stderr, gettext(
247 				    invalid_v4v6_msg));
248 				usage(1);
249 			}
250 			flags &= ~CS_IPV6;
251 			break;
252 		case '6':
253 			if (!(flags & CS_IPV6)) {
254 				(void) fprintf(stderr, gettext(
255 				    invalid_v4v6_msg));
256 				usage(1);
257 			}
258 			flags &= ~CS_IPV4;
259 			break;
260 		case '?':
261 		default:
262 			usage(1);
263 			break;
264 		}
265 	}
266 
267 	if ((proto = getproto(protostr)) == NULL) {
268 		die("unknown protocol given to \"-p\": %s", protostr);
269 	}
270 
271 	if ((ofmtflags & OFMT_PARSABLE) && fields == NULL) {
272 		die("parsable output requires \"-o\"");
273 	}
274 
275 	if ((ofmtflags & OFMT_PARSABLE) && fields != NULL &&
276 	    strcasecmp(fields, "all") == 0) {
277 		die("\"-o all\" is invalid with parsable output");
278 	}
279 
280 	if (fields == NULL) {
281 		fields = proto->csp_default_fields;
282 	}
283 
284 	/* If count is specified, then interval must also be specified. */
285 	if (count != 0 && interval == 0) {
286 		die("\"-c\" requires \"-i\"");
287 	}
288 
289 	/* If interval is not specified, then the default count is 1. */
290 	if (interval == 0 && count == 0) {
291 		count = 1;
292 	}
293 
294 	if (filterstr != NULL) {
295 		process_filter(filterstr, &filter, &flags);
296 	}
297 
298 	oferr = ofmt_open(fields, proto->csp_getfields(), ofmtflags, 0, &ofmt);
299 	if (oferr != OFMT_SUCCESS) {
300 		(void) ofmt_strerror(ofmt, oferr, oferrbuf, sizeof (oferrbuf));
301 		die(oferrbuf);
302 	}
303 	ofmt_set_fs(ofmt, ',');
304 
305 	show_stats(proto, ofmt, flags, &filter, timestamp_fmt, interval, count);
306 
307 	ofmt_close(ofmt);
308 	return (0);
309 }
310 
311 /*
312  * Convert the input IP address literal to sockaddr of the appropriate address
313  * family. Preserves any potential port number that may have been set in the
314  * input sockaddr_storage structure.
315  */
316 static void
317 str2sockaddr(const char *addr, struct sockaddr_storage *ss)
318 {
319 	struct addrinfo hints, *res;
320 
321 	bzero(&hints, sizeof (hints));
322 	hints.ai_flags = AI_NUMERICHOST;
323 	if (getaddrinfo(addr, NULL, &hints, &res) != 0) {
324 		die("invalid literal IP address: %s", addr);
325 	}
326 	bcopy(res->ai_addr, ss, res->ai_addrlen);
327 	freeaddrinfo(res);
328 }
329 
330 /*
331  * The filterstr argument is of the form: <attr>=<value>[,...]
332  * Possible attributes are laddr, raddr, lport, and rport. Parse this
333  * filter and store the results into the provided attribute structure.
334  */
335 static void
336 process_filter(char *filterstr, connstat_conn_attr_t *filter, uint_t *flags)
337 {
338 	int option;
339 	char *val;
340 	enum { F_LADDR, F_RADDR, F_LPORT, F_RPORT, F_STATE };
341 	static char *filter_optstr[] =
342 	    { "laddr", "raddr", "lport", "rport", "state", NULL };
343 	uint_t flag = 0;
344 	struct sockaddr_storage *addrp = NULL;
345 	const char *errstr = NULL;
346 	int *portp = NULL;
347 
348 	while (*filterstr != '\0') {
349 		option = getsubopt(&filterstr, filter_optstr, &val);
350 		errno = 0;
351 
352 		switch (option) {
353 		case F_LADDR:
354 			flag = CS_LADDR;
355 			addrp = &filter->ca_laddr;
356 			break;
357 		case F_RADDR:
358 			flag = CS_RADDR;
359 			addrp = &filter->ca_raddr;
360 			break;
361 		case F_LPORT:
362 			flag = CS_LPORT;
363 			portp = &filter->ca_lport;
364 			break;
365 		case F_RPORT:
366 			flag = CS_RPORT;
367 			portp = &filter->ca_rport;
368 			break;
369 		case F_STATE:
370 			flag = CS_STATE;
371 			break;
372 		default:
373 			usage(1);
374 		}
375 
376 		if (*flags & flag) {
377 			(void) fprintf(stderr, gettext(
378 			    "Ambiguous filter provided. The \"%s\" field "
379 			    "appears more than once.\n"),
380 			    filter_optstr[option]);
381 			usage(1);
382 		}
383 		*flags |= flag;
384 
385 		switch (flag) {
386 		case CS_LADDR:
387 		case CS_RADDR:
388 			str2sockaddr(val, addrp);
389 			if (addrp->ss_family == AF_INET) {
390 				if (!(*flags & CS_IPV4)) {
391 					(void) fprintf(stderr, gettext(
392 					    invalid_v4v6_msg));
393 					usage(1);
394 				}
395 				*flags &= ~CS_IPV6;
396 			} else {
397 				if (!(*flags & CS_IPV6)) {
398 					(void) fprintf(stderr, gettext(
399 					    invalid_v4v6_msg));
400 					usage(1);
401 				}
402 				*flags &= ~CS_IPV4;
403 			}
404 			break;
405 		case CS_LPORT:
406 		case CS_RPORT:
407 			*portp = strtonum(val, 1, UINT16_MAX, &errstr);
408 			if (errstr != NULL) {
409 				(void) fprintf(stderr, gettext(
410 				    "error parsing port (%s): %s\n"),
411 				    val, errstr);
412 				usage(1);
413 			}
414 			break;
415 		case CS_STATE:
416 			filter->ca_state = tcp_str2state(val);
417 			if (filter->ca_state < TCPS_CLOSED) {
418 				(void) fprintf(stderr, gettext(
419 				    "invalid TCP state: %s\n"), val);
420 				usage(1);
421 			}
422 			break;
423 		}
424 	}
425 
426 	/* Make sure that laddr and raddr are at least in the same family. */
427 	if ((*flags & (CS_LADDR|CS_RADDR)) == (CS_LADDR|CS_RADDR)) {
428 		if (filter->ca_laddr.ss_family != filter->ca_raddr.ss_family) {
429 			die("laddr and raddr must be of the same family.");
430 		}
431 	}
432 }
433 
434 /*
435  * Print timestamp as decimal representation of time_t value (-T u was
436  * specified) or in date(1) format (-T d was specified).
437  */
438 static void
439 print_timestamp(timestamp_fmt_t timestamp_fmt, boolean_t parsable)
440 {
441 	time_t t = time(NULL);
442 	char *pfx = parsable ? "= " : "";
443 	static char *fmt = NULL;
444 
445 	/* We only need to retrieve this once per invocation */
446 	if (fmt == NULL) {
447 		fmt = nl_langinfo(_DATE_FMT);
448 	}
449 
450 	switch (timestamp_fmt) {
451 	case NOTIMESTAMP:
452 		break;
453 	case UTIMESTAMP:
454 		(void) printf("%s%ld\n", pfx, t);
455 		break;
456 	case DTIMESTAMP: {
457 		char dstr[64];
458 		size_t len;
459 
460 		len = strftime(dstr, sizeof (dstr), fmt, localtime(&t));
461 		if (len > 0) {
462 			(void) printf("%s%s\n", pfx, dstr);
463 		}
464 		break;
465 	}
466 	default:
467 		abort();
468 		break;
469 	}
470 }
471 
472 static void
473 show_stats(connstat_proto_t *proto, ofmt_handle_t ofmt, uint_t flags,
474     connstat_conn_attr_t *filter, timestamp_fmt_t timestamp_fmt,
475     uint_t interval, uint_t count)
476 {
477 	boolean_t done = B_FALSE;
478 	uint_t i = 0;
479 	int mibfd;
480 	conn_walk_state_t state;
481 
482 	state.cws_ofmt = ofmt;
483 	state.cws_flags = flags;
484 	state.cws_filter = *filter;
485 
486 	if ((mibfd = mibopen(proto->csp_proto)) == -1) {
487 		die("failed to open MIB stream: %s", strerror(errno));
488 	}
489 
490 	do {
491 		if (timestamp_fmt != NOTIMESTAMP) {
492 			print_timestamp(timestamp_fmt, flags & CS_PARSABLE);
493 		}
494 		if (!(flags & CS_PARSABLE)) {
495 			ofmt_print_header(ofmt);
496 		}
497 
498 		if (conn_walk(mibfd, proto, &state) != 0) {
499 			die("failed to fetch and print connection info");
500 		}
501 
502 		if (count != 0 && ++i == count) {
503 			done = B_TRUE;
504 		} else {
505 			(void) sleep(interval);
506 		}
507 	} while (!done);
508 }
509 
510 /*
511  * ofmt callbacks for printing individual fields of various types.
512  */
513 boolean_t
514 print_string(ofmt_arg_t *ofarg, char *buf, uint_t bufsize)
515 {
516 	char *value;
517 
518 	value = (char *)ofarg->ofmt_cbarg + ofarg->ofmt_id;
519 	(void) strlcpy(buf, value, bufsize);
520 	return (B_TRUE);
521 }
522 
523 boolean_t
524 print_uint16(ofmt_arg_t *ofarg, char *buf, uint_t bufsize)
525 {
526 	uint16_t value;
527 
528 	/* LINTED E_BAD_PTR_CAST_ALIGN */
529 	value = *(uint16_t *)((char *)ofarg->ofmt_cbarg + ofarg->ofmt_id);
530 	(void) snprintf(buf, bufsize, "%hu", value);
531 	return (B_TRUE);
532 }
533 
534 boolean_t
535 print_uint32(ofmt_arg_t *ofarg, char *buf, uint_t bufsize)
536 {
537 	uint32_t value;
538 
539 	/* LINTED E_BAD_PTR_CAST_ALIGN */
540 	value = *(uint32_t *)((char *)ofarg->ofmt_cbarg + ofarg->ofmt_id);
541 	(void) snprintf(buf, bufsize, "%u", value);
542 	return (B_TRUE);
543 }
544 
545 boolean_t
546 print_uint64(ofmt_arg_t *ofarg, char *buf, uint_t bufsize)
547 {
548 	uint64_t value;
549 
550 	/* LINTED E_BAD_PTR_CAST_ALIGN */
551 	value = *(uint64_t *)((char *)ofarg->ofmt_cbarg + ofarg->ofmt_id);
552 	(void) snprintf(buf, bufsize, "%llu", value);
553 	return (B_TRUE);
554 }
555 
556 /* PRINTFLIKE1 */
557 static void
558 die(const char *format, ...)
559 {
560 	va_list alist;
561 
562 	format = gettext(format);
563 
564 	va_start(alist, format);
565 	verrx(1, format, alist);
566 	va_end(alist);
567 }
568