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