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 }