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
usage(int code)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 *
getproto(const char * proto)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
main(int argc,char * argv[])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
str2sockaddr(const char * addr,struct sockaddr_storage * ss)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
process_filter(char * filterstr,connstat_conn_attr_t * filter,uint_t * flags)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
print_timestamp(timestamp_fmt_t timestamp_fmt,boolean_t parsable)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
show_stats(connstat_proto_t * proto,ofmt_handle_t ofmt,uint_t flags,connstat_conn_attr_t * filter,timestamp_fmt_t timestamp_fmt,uint_t interval,uint_t count)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
print_string(ofmt_arg_t * ofarg,char * buf,uint_t bufsize)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
print_uint16(ofmt_arg_t * ofarg,char * buf,uint_t bufsize)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
print_uint32(ofmt_arg_t * ofarg,char * buf,uint_t bufsize)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
print_uint64(ofmt_arg_t * ofarg,char * buf,uint_t bufsize)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
die(const char * format,...)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