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 }