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