diff options
Diffstat (limited to 'usr/src/cmd/connstat/connstat_main.c')
-rw-r--r-- | usr/src/cmd/connstat/connstat_main.c | 567 |
1 files changed, 567 insertions, 0 deletions
diff --git a/usr/src/cmd/connstat/connstat_main.c b/usr/src/cmd/connstat/connstat_main.c new file mode 100644 index 0000000000..dd58e2ac2b --- /dev/null +++ b/usr/src/cmd/connstat/connstat_main.c @@ -0,0 +1,567 @@ +/* + * CDDL HEADER START + * + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2015, 2016 by Delphix. All rights reserved. + */ + +#include <err.h> +#include <stdio.h> +#include <errno.h> +#include <getopt.h> +#include <stdlib.h> +#include <stddef.h> +#include <strings.h> +#include <unistd.h> +#include <libgen.h> +#include <libintl.h> +#include <limits.h> +#include <locale.h> +#include <langinfo.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <sys/varargs.h> +#include <ofmt.h> +#include <inet/tcp.h> +#include <netinet/in.h> +#include <inet/mib2.h> +#include "connstat.h" +#include "connstat_mib.h" +#include "connstat_tcp.h" + +#define DEFAULT_PROTO "tcp" + +static const char *invalid_v4v6_msg = + "Invalid combination of IPv4 and IPv6 arguments\n"; + +static const char *invalid_T_msg = + "Invalid -T arg \"%s\". Must be \"u\" or \"d\"\n"; + +static const struct option longopts[] = { + { "count", required_argument, 0, 'c' }, + { "established", no_argument, 0, 'e' }, + { "filter", required_argument, 0, 'F' }, + { "help", no_argument, 0, 'h' }, + { "interval", required_argument, 0, 'i' }, + { "ipv4", no_argument, 0, '4' }, + { "ipv6", no_argument, 0, '6' }, + { "no-loopback", no_argument, 0, 'L' }, + { "output", required_argument, 0, 'o' }, + { "parsable", no_argument, 0, 'P' }, + { "protocol", required_argument, 0, 'p' }, + { "timestamp", required_argument, 0, 'T' }, + { NULL, 0, 0, 0 } +}; + +static connstat_proto_t connstat_protos[] = { + CONNSTAT_TCP_PROTO, + { NULL, NULL, 0, 0, 0, NULL, NULL, NULL } +}; + +typedef enum { NOTIMESTAMP, UTIMESTAMP, DTIMESTAMP } timestamp_fmt_t; + +static void die(const char *, ...) __NORETURN; +static void process_filter(char *, connstat_conn_attr_t *, uint_t *); +static void show_stats(connstat_proto_t *, ofmt_handle_t, uint_t, + connstat_conn_attr_t *, timestamp_fmt_t, uint_t, uint_t); + +static void __NORETURN +usage(int code) +{ + static const char *opts[] = { + "-4, --ipv4 Only display IPv4 connections", + "-6, --ipv6 Only display IPv6 connections", + "-c, --count=COUNT Only print COUNT reports", + "-e, --established Only display established connections", + "-F, --filter=FILTER Only display connections that match " + "FILTER", + "-h, --help Print this help", + "-i, --interval=SECONDS Report once every SECONDS seconds", + "-L, --no-loopback Omit loopback connections", + "-o, --output=FIELDS Restrict output to the comma-separated " + "list of fields\n" + " specified", + "-P, --parsable Parsable output mode", + "-T, --timestamp=TYPE Display a timestamp for each iteration", + NULL + }; + + (void) fprintf(stderr, gettext("usage: ")); + (void) fprintf(stderr, + gettext("%s [-eLP] [-4|-6] [-T d|u] [-F <filter>]\n" + " [-i <interval> [-c <count>]] [-o <field>[,...]]\n"), + getprogname()); + + (void) fprintf(stderr, gettext("\nOptions:\n")); + for (const char **optp = opts; *optp != NULL; optp++) { + (void) fprintf(stderr, " %s\n", gettext(*optp)); + } + + (void) fprintf(stderr, gettext("\nFilter:\n")); + (void) fprintf(stderr, gettext(" The FILTER argument for the -F " + "option is of the form:\n" + " <field>=<value>,[<field>=<value>,...]\n")); + (void) fprintf(stderr, gettext(" Filterable fields are laddr, lport, " + "raddr, rport, and state.\n")); + + (void) fprintf(stderr, gettext("\nFields:\n")); + (void) fprintf(stderr, gettext( + " laddr Local IP address\n" + " raddr Remote IP address\n" + " lport Local port\n" + " rport Remote port\n" + " inbytes Total bytes received\n" + " insegs Total segments received\n" + " inunorderbytes Bytes received out of order\n" + " inunordersegs Segments received out of order\n" + " outbytes Total bytes sent\n" + " outsegs Total segments sent\n" + " retransbytes Bytes retransmitted\n" + " retranssegs Segments retransmitted\n" + " suna Current unacknowledged bytes sent\n" + " unsent Unsent bytes on the transmit queue\n" + " swnd Send window size (peer's receive window)\n" + " cwnd Congestion window size\n" + " rwnd Receive window size\n" + " mss Maximum segment size\n" + " rto Retransmission timeout (ms)\n" + " rtt Smoothed round-trip time (us)\n" + " rtts Sum round-trip time (us)\n" + " rttc Count of round-trip times\n" + " state Connection state\n")); + exit(code); +} + +static connstat_proto_t * +getproto(const char *proto) +{ + for (connstat_proto_t *current = &connstat_protos[0]; + current->csp_proto != NULL; current++) { + if (strcasecmp(proto, current->csp_proto) == 0) { + return (current); + } + } + return (NULL); +} + +int +main(int argc, char *argv[]) +{ + int option; + int count = 0; + int interval = 0; + const char *errstr = NULL; + char *fields = NULL; + char *filterstr = NULL; + connstat_conn_attr_t filter = {0}; + char *protostr = DEFAULT_PROTO; + connstat_proto_t *proto; + ofmt_handle_t ofmt; + ofmt_status_t oferr; + char oferrbuf[OFMT_BUFSIZE]; + uint_t ofmtflags = OFMT_NOHEADER; + uint_t flags = CS_LOOPBACK | CS_IPV4 | CS_IPV6; + timestamp_fmt_t timestamp_fmt = NOTIMESTAMP; + + (void) setlocale(LC_ALL, ""); +#if !defined(TEXT_DOMAIN) +#define TEXT_DOMAIN "SYS_TEST" +#endif + (void) textdomain(TEXT_DOMAIN); + + setprogname(basename(argv[0])); + + while ((option = getopt_long(argc, argv, "c:eF:hi:Lo:Pp:T:46", + longopts, NULL)) != -1) { + switch (option) { + case 'c': + count = strtonum(optarg, 1, INT_MAX, &errstr); + if (errstr != NULL) { + (void) fprintf(stderr, gettext( + "error parsing -c argument (%s): %s\n"), + optarg, errstr); + usage(1); + } + break; + case 'e': + flags |= CS_STATE; + filter.ca_state = TCPS_ESTABLISHED; + break; + case 'F': + filterstr = optarg; + break; + case 'i': + interval = strtonum(optarg, 1, INT_MAX, &errstr); + if (errstr != NULL) { + (void) fprintf(stderr, gettext( + "error parsing -i argument (%s): %s\n"), + optarg, errstr); + usage(1); + } + break; + case 'L': + flags &= ~CS_LOOPBACK; + break; + case 'o': + fields = optarg; + break; + case 'P': + ofmtflags |= OFMT_PARSABLE; + flags |= CS_PARSABLE; + break; + case 'p': + /* + * -p is an undocumented flag whose only supported + * argument is "tcp". The idea is to reserve this + * flag for potential future use in case connstat + * is extended to support stats for other protocols. + */ + protostr = optarg; + break; + case 'T': + if (strcmp(optarg, "u") == 0) { + timestamp_fmt = UTIMESTAMP; + } else if (strcmp(optarg, "d") == 0) { + timestamp_fmt = DTIMESTAMP; + } else { + (void) fprintf(stderr, gettext( + invalid_T_msg), optarg); + usage(1); + } + break; + case '4': + if (!(flags & CS_IPV4)) { + (void) fprintf(stderr, gettext( + invalid_v4v6_msg)); + usage(1); + } + flags &= ~CS_IPV6; + break; + case '6': + if (!(flags & CS_IPV6)) { + (void) fprintf(stderr, gettext( + invalid_v4v6_msg)); + usage(1); + } + flags &= ~CS_IPV4; + break; + case '?': + default: + usage(1); + break; + } + } + + if ((proto = getproto(protostr)) == NULL) { + die("unknown protocol given to \"-p\": %s", protostr); + } + + if ((ofmtflags & OFMT_PARSABLE) && fields == NULL) { + die("parsable output requires \"-o\""); + } + + if ((ofmtflags & OFMT_PARSABLE) && fields != NULL && + strcasecmp(fields, "all") == 0) { + die("\"-o all\" is invalid with parsable output"); + } + + if (fields == NULL) { + fields = proto->csp_default_fields; + } + + /* If count is specified, then interval must also be specified. */ + if (count != 0 && interval == 0) { + die("\"-c\" requires \"-i\""); + } + + /* If interval is not specified, then the default count is 1. */ + if (interval == 0 && count == 0) { + count = 1; + } + + if (filterstr != NULL) { + process_filter(filterstr, &filter, &flags); + } + + oferr = ofmt_open(fields, proto->csp_getfields(), ofmtflags, 0, &ofmt); + if (oferr != OFMT_SUCCESS) { + (void) ofmt_strerror(ofmt, oferr, oferrbuf, sizeof (oferrbuf)); + die(oferrbuf); + } + ofmt_set_fs(ofmt, ','); + + show_stats(proto, ofmt, flags, &filter, timestamp_fmt, interval, count); + + ofmt_close(ofmt); + return (0); +} + +/* + * Convert the input IP address literal to sockaddr of the appropriate address + * family. Preserves any potential port number that may have been set in the + * input sockaddr_storage structure. + */ +static void +str2sockaddr(const char *addr, struct sockaddr_storage *ss) +{ + struct addrinfo hints, *res; + + bzero(&hints, sizeof (hints)); + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(addr, NULL, &hints, &res) != 0) { + die("invalid literal IP address: %s", addr); + } + bcopy(res->ai_addr, ss, res->ai_addrlen); + freeaddrinfo(res); +} + +/* + * The filterstr argument is of the form: <attr>=<value>[,...] + * Possible attributes are laddr, raddr, lport, and rport. Parse this + * filter and store the results into the provided attribute structure. + */ +static void +process_filter(char *filterstr, connstat_conn_attr_t *filter, uint_t *flags) +{ + int option; + char *val; + enum { F_LADDR, F_RADDR, F_LPORT, F_RPORT, F_STATE }; + static char *filter_optstr[] = + { "laddr", "raddr", "lport", "rport", "state", NULL }; + uint_t flag = 0; + struct sockaddr_storage *addrp = NULL; + const char *errstr = NULL; + int *portp = NULL; + + while (*filterstr != '\0') { + option = getsubopt(&filterstr, filter_optstr, &val); + errno = 0; + + switch (option) { + case F_LADDR: + flag = CS_LADDR; + addrp = &filter->ca_laddr; + break; + case F_RADDR: + flag = CS_RADDR; + addrp = &filter->ca_raddr; + break; + case F_LPORT: + flag = CS_LPORT; + portp = &filter->ca_lport; + break; + case F_RPORT: + flag = CS_RPORT; + portp = &filter->ca_rport; + break; + case F_STATE: + flag = CS_STATE; + break; + default: + usage(1); + } + + if (*flags & flag) { + (void) fprintf(stderr, gettext( + "Ambiguous filter provided. The \"%s\" field " + "appears more than once.\n"), + filter_optstr[option]); + usage(1); + } + *flags |= flag; + + switch (flag) { + case CS_LADDR: + case CS_RADDR: + str2sockaddr(val, addrp); + if (addrp->ss_family == AF_INET) { + if (!(*flags & CS_IPV4)) { + (void) fprintf(stderr, gettext( + invalid_v4v6_msg)); + usage(1); + } + *flags &= ~CS_IPV6; + } else { + if (!(*flags & CS_IPV6)) { + (void) fprintf(stderr, gettext( + invalid_v4v6_msg)); + usage(1); + } + *flags &= ~CS_IPV4; + } + break; + case CS_LPORT: + case CS_RPORT: + *portp = strtonum(val, 1, UINT16_MAX, &errstr); + if (errstr != NULL) { + (void) fprintf(stderr, gettext( + "error parsing port (%s): %s\n"), + val, errstr); + usage(1); + } + break; + case CS_STATE: + filter->ca_state = tcp_str2state(val); + if (filter->ca_state < TCPS_CLOSED) { + (void) fprintf(stderr, gettext( + "invalid TCP state: %s\n"), val); + usage(1); + } + break; + } + } + + /* Make sure that laddr and raddr are at least in the same family. */ + if ((*flags & (CS_LADDR|CS_RADDR)) == (CS_LADDR|CS_RADDR)) { + if (filter->ca_laddr.ss_family != filter->ca_raddr.ss_family) { + die("laddr and raddr must be of the same family."); + } + } +} + +/* + * Print timestamp as decimal representation of time_t value (-T u was + * specified) or in date(1) format (-T d was specified). + */ +static void +print_timestamp(timestamp_fmt_t timestamp_fmt, boolean_t parsable) +{ + time_t t = time(NULL); + char *pfx = parsable ? "= " : ""; + static char *fmt = NULL; + + /* We only need to retrieve this once per invocation */ + if (fmt == NULL) { + fmt = nl_langinfo(_DATE_FMT); + } + + switch (timestamp_fmt) { + case NOTIMESTAMP: + break; + case UTIMESTAMP: + (void) printf("%s%ld\n", pfx, t); + break; + case DTIMESTAMP: { + char dstr[64]; + size_t len; + + len = strftime(dstr, sizeof (dstr), fmt, localtime(&t)); + if (len > 0) { + (void) printf("%s%s\n", pfx, dstr); + } + break; + } + default: + abort(); + break; + } +} + +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) +{ + boolean_t done = B_FALSE; + uint_t i = 0; + int mibfd; + conn_walk_state_t state; + + state.cws_ofmt = ofmt; + state.cws_flags = flags; + state.cws_filter = *filter; + + if ((mibfd = mibopen(proto->csp_proto)) == -1) { + die("failed to open MIB stream: %s", strerror(errno)); + } + + do { + if (timestamp_fmt != NOTIMESTAMP) { + print_timestamp(timestamp_fmt, flags & CS_PARSABLE); + } + if (!(flags & CS_PARSABLE)) { + ofmt_print_header(ofmt); + } + + if (conn_walk(mibfd, proto, &state) != 0) { + die("failed to fetch and print connection info"); + } + + if (count != 0 && ++i == count) { + done = B_TRUE; + } else { + (void) sleep(interval); + } + } while (!done); +} + +/* + * ofmt callbacks for printing individual fields of various types. + */ +boolean_t +print_string(ofmt_arg_t *ofarg, char *buf, uint_t bufsize) +{ + char *value; + + value = (char *)ofarg->ofmt_cbarg + ofarg->ofmt_id; + (void) strlcpy(buf, value, bufsize); + return (B_TRUE); +} + +boolean_t +print_uint16(ofmt_arg_t *ofarg, char *buf, uint_t bufsize) +{ + uint16_t value; + + /* LINTED E_BAD_PTR_CAST_ALIGN */ + value = *(uint16_t *)((char *)ofarg->ofmt_cbarg + ofarg->ofmt_id); + (void) snprintf(buf, bufsize, "%hu", value); + return (B_TRUE); +} + +boolean_t +print_uint32(ofmt_arg_t *ofarg, char *buf, uint_t bufsize) +{ + uint32_t value; + + /* LINTED E_BAD_PTR_CAST_ALIGN */ + value = *(uint32_t *)((char *)ofarg->ofmt_cbarg + ofarg->ofmt_id); + (void) snprintf(buf, bufsize, "%u", value); + return (B_TRUE); +} + +boolean_t +print_uint64(ofmt_arg_t *ofarg, char *buf, uint_t bufsize) +{ + uint64_t value; + + /* LINTED E_BAD_PTR_CAST_ALIGN */ + value = *(uint64_t *)((char *)ofarg->ofmt_cbarg + ofarg->ofmt_id); + (void) snprintf(buf, bufsize, "%llu", value); + return (B_TRUE); +} + +/* PRINTFLIKE1 */ +static void +die(const char *format, ...) +{ + va_list alist; + + format = gettext(format); + + va_start(alist, format); + verrx(1, format, alist); + va_end(alist); +} |