From 881631a2a3716f8aa6dc0892c634f8e6c4fe30a6 Mon Sep 17 00:00:00 2001 From: Michael Tuexen Date: Wed, 9 Feb 2022 19:14:25 +0100 Subject: [PATCH] usr.sbin: add tcpsso tcpsso is a command line tool to apply a socket option to an existing TCP endpoint, which is identified by the inp_gencnt. tcpsso can be used, for example, to switch the congestion control module or the TCP stack. Reviewed by: rrs, rscheff, debdrup, pau amma Relnotes: yes Sponsored by: Netflix, Inc. Differential Revision: https://reviews.freebsd.org/D34139 --- usr.sbin/tcpsso/Makefile | 4 + usr.sbin/tcpsso/Makefile.depend | 16 ++ usr.sbin/tcpsso/tcpsso.8 | 238 ++++++++++++++++ usr.sbin/tcpsso/tcpsso.c | 475 ++++++++++++++++++++++++++++++++ 4 files changed, 733 insertions(+) create mode 100644 usr.sbin/tcpsso/Makefile create mode 100644 usr.sbin/tcpsso/Makefile.depend create mode 100644 usr.sbin/tcpsso/tcpsso.8 create mode 100644 usr.sbin/tcpsso/tcpsso.c diff --git a/usr.sbin/tcpsso/Makefile b/usr.sbin/tcpsso/Makefile new file mode 100644 index 000000000000..93130b1138a7 --- /dev/null +++ b/usr.sbin/tcpsso/Makefile @@ -0,0 +1,4 @@ +PROG= tcpsso +MAN= tcpsso.8 + +.include diff --git a/usr.sbin/tcpsso/Makefile.depend b/usr.sbin/tcpsso/Makefile.depend new file mode 100644 index 000000000000..84b8ddd67e34 --- /dev/null +++ b/usr.sbin/tcpsso/Makefile.depend @@ -0,0 +1,16 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + + +.include + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/tcpsso/tcpsso.8 b/usr.sbin/tcpsso/tcpsso.8 new file mode 100644 index 000000000000..9b62459ffba5 --- /dev/null +++ b/usr.sbin/tcpsso/tcpsso.8 @@ -0,0 +1,238 @@ +.\" +.\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD +.\" +.\" Copyright (c) 2022 Michael Tuexen +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd February 9, 2022 +.Dt TCPSSO 8 +.Os +.Sh NAME +.Nm tcpsso +.Nd set a socket option on a TCP endpoint +.Sh SYNOPSIS +.Nm +.Fl i Ar id +.Op Ar level +.Ar optname +.Ar optval +.Nm +.Fl a +.Op Ar level +.Ar optname +.Ar optval +.Nm +.Fl C Ar cc-algo +.Op Fl S Ar stack +.Op Fl s Ar state +.Op Ar level +.Ar optname +.Ar optval +.Nm +.Op Fl C Ar cc-algo +.Fl S Ar stack +.Op Fl s Ar state +.Op Ar level +.Ar optname +.Ar optval +.Nm +.Op Fl C Ar cc-algo +.Op Fl S Ar stack +.Fl s Ar state +.Op Ar level +.Ar optname +.Ar optval +.Sh DESCRIPTION +The +.Nm +command applies a +.Ar level +level socket option with name +.Ar optname +and value +.Ar optval +on a TCP endpoint from the command line. +.Pp +.Op Ar level +can be specified as a non negative number or a symbolic name like +.Dv SOL_SOCKET , +.Dv IPPROTO_IP , +.Dv IPPROTO_IPV6 , +or +.Dv IPPROTO_TCP . +If not specified, +.Nm +deduces the level from +.Ar optname , +if provided as a symbolic name. +If that is not the case, +.Dv IPPROTO_TCP +is used. +.Pp +.Ar optname +can be specified as a non negative number or a symbolic name like +.Dv SO_DEBUG , +.Dv IP_TOS , +.Dv IPV6_TCLASS , +.Dv TCP_LOG , +.Dv TCP_CONGESTION , +or +.Dv TCP_FUNCTION_BLK . +.Pp +.Ar optval +can be in integer value, which will be converted to a binary value and +passed as an int value. +If it cannot be parsed as an integer value, it will be processed as a string. +If the +.Ar optname +is +.Dv TCP_FUNCTION_BLK +then +.Ar optval +is converted to a +.Vt "struct tcp_function_set" . +.Pp +If +.Fl i Ar id +is specified then +.Nm +will apply the socket option to the TCP endpoint with the +.Dv inp_gencnt +provided as +.Ar id . +The +.Dv inp_gencnt +for existing TCP endpoints can be determined by using +.Xr sockstat 1 . +.Pp +If +.Fl a +is specified then +.Nm +will apply the socket option to all TCP endpoints not being in the state +.Dv TIME_WAIT . +.Pp +If +.Fl C Ar cc-algo +is specified then +.Nm +will apply the socket option to all TCP endpoints using the TCP +congestion control algorithm +.Ar cc-algo +and not being in the state +.Dv TIME_WAIT . +.Pp +If +.Fl S Ar stack +is specified then +.Nm +will apply the socket option to all TCP endpoints using the TCP +stack +.Ar stack +and not being in the state +.Dv TIME_WAIT . +.Pp +If +.Fl s Ar state +is specified then +.Nm +will apply the socket option to all TCP endpoints being in the state +.Ar state . +.Ar state +is one of +.Dv CLOSED , +.Dv LISTEN , +.Dv SYN_SENT , +.Dv SYN_RCVD , +.Dv ESTABLISHED , +.Dv CLOSE_WAIT , +.Dv FIN_WAIT_1 , +.Dv CLOSING , +.Dv LAST_ACK , +.Dv FIN_WAIT_2 . +.Pp +If multiple of +.Fl C Ar cc-algo , +.Fl S Ar stack , +and +.Fl s Ar state +are specified, +.Nm +will apply the socket option to all TCP endpoints not being in the +state +.Dv TIME_WAIT +and using the congestion control algorithm +.Ar cc-algo , +being in the state +.Ar state , +and using the TCP stack +.Ar stack , +if specified. +.Pp +If none of the +.Fl a , +.Fl C , +.Fl S , +or +.Fl s +options are specified then the option +.Fl i +must be specified. +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +To diagnose a problem with a particular TCP connection to +.Xr sshd 8 , +first determine its +.Dv inp_gencnt +using +.Xr sockstat 1 : +.Bd -literal -offset indent +# sockstat -4 -c -i -p 22 -P tcp -q +root sshd 827 4 tcp4 \e + 192.168.1.1:22 192.168.1.2:53736 435 +.Ed +.Pp +Then, use the following command to enable Black Box Logging on it: +.Bd -literal -offset indent +# tcpsso -i 435 TCP_LOG 4 +.Ed +.Pp +To switch all TCP endpoints from using the freebsd stack to the rack stack use: +.Bd -literal -offset indent +# tcpsso -S freebsd TCP_FUNCTION_BLK rack +.Ed +.Pp +The following command will set the congestion control module of all TCP +endpoints currently using cubic as its congestion control algorithm to the +congestion control algorithm new-reno: +.Bd -literal -offset indent +# tcpsso -C cubic TCP_CONGESTION new-reno +.Ed +.Sh SEE ALSO +.Xr sockstat 1 , +.Xr setsockopt 2 , +.Xr tcp 4 , +.Xr tcp_functions 9 +.Sh AUTHORS +.An Michael Tuexen Aq Mt tuexen@FreeBSD.org diff --git a/usr.sbin/tcpsso/tcpsso.c b/usr.sbin/tcpsso/tcpsso.c new file mode 100644 index 000000000000..2ac4d0c57d1d --- /dev/null +++ b/usr.sbin/tcpsso/tcpsso.c @@ -0,0 +1,475 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2022 Michael Tuexen + * Copyright (c) 2009 Juli Mallett + * Copyright (c) 2004 Markus Friedl + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#define TCPSTATES +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static struct xinpgen * +getxpcblist(const char *name) +{ + struct xinpgen *xinp; + size_t len; + int rv; + + len = 0; + rv = sysctlbyname(name, NULL, &len, NULL, 0); + if (rv == -1) + err(1, "sysctlbyname %s", name); + + if (len == 0) + errx(1, "%s is empty", name); + + xinp = malloc(len); + if (xinp == NULL) + errx(1, "malloc failed"); + + rv = sysctlbyname(name, xinp, &len, NULL, 0); + if (rv == -1) + err(1, "sysctlbyname %s", name); + + return (xinp); +} + +static bool +tcpsso(uint64_t id, struct sockopt_parameters *params, size_t optlen) +{ + int rv; + + params->sop_id = id; + rv = sysctlbyname("net.inet.tcp.setsockopt", NULL, NULL, params, + sizeof(struct sockopt_parameters) + optlen); + if (rv == -1) { + warn("Failed for id %" PRIu64, params->sop_id); + return (false); + } else + return (true); +} + +static bool +tcpssoall(const char *ca_name, const char *stack, int state, + struct sockopt_parameters *params, size_t optlen) +{ + struct xinpgen *head, *xinp; + struct xtcpcb *xtp; + struct xinpcb *xip; + bool ok; + + ok = true; + + head = getxpcblist("net.inet.tcp.pcblist"); + +#define XINP_NEXT(xinp) \ + ((struct xinpgen *)(uintptr_t)((uintptr_t)(xinp) + (xinp)->xig_len)) + + for (xinp = XINP_NEXT(head); xinp->xig_len > sizeof *xinp; + xinp = XINP_NEXT(xinp)) { + xtp = (struct xtcpcb *)xinp; + xip = &xtp->xt_inp; + + /* Ignore PCBs which were freed during copyout. */ + if (xip->inp_gencnt > head->xig_gen) + continue; + + + /* Skip endpoints in TIME WAIT. */ + if (xtp->t_state == TCPS_TIME_WAIT) + continue; + + /* If requested, skip sockets not having the requested state. */ + if ((state != -1) && (xtp->t_state != state)) + continue; + + /* + * If requested, skip sockets not having the requested + * congestion control algorithm. + */ + if (ca_name[0] != '\0' && + strncmp(xtp->xt_cc, ca_name, TCP_CA_NAME_MAX)) + continue; + + /* If requested, skip sockets not having the requested stack. */ + if (stack[0] != '\0' && + strncmp(xtp->xt_stack, stack, TCP_FUNCTION_NAME_LEN_MAX)) + continue; + + params->sop_inc = xip->inp_inc; + if (!tcpsso(xip->inp_gencnt, params, optlen)) + ok = false; + } + free(head); + + return (ok); +} + +struct so_level { + int level; + const char *name; +}; + +#define level_entry(level) { level, #level } + +static struct so_level so_levels[] = { + level_entry(SOL_SOCKET), + level_entry(IPPROTO_IP), + level_entry(IPPROTO_IPV6), + level_entry(IPPROTO_TCP), + { 0, NULL } +}; + +struct so_name { + int level; + int value; + const char *name; +}; + +#define sol_entry(name) { SOL_SOCKET, name, #name } +#define ip4_entry(name) { IPPROTO_IP, name, #name } +#define ip6_entry(name) { IPPROTO_IPV6, name, #name } +#define tcp_entry(name) { IPPROTO_TCP, name, #name } + +static struct so_name so_names[] = { + /* SOL_SOCKET level socket options. */ + sol_entry(SO_DEBUG), /* int */ + sol_entry(SO_RCVBUF), /* int */ + sol_entry(SO_SNDBUF), /* int */ + sol_entry(SO_RCVLOWAT), /* int */ + sol_entry(SO_SNDLOWAT), /* int */ + /* IPPROTO_IP level socket options. */ + ip4_entry(IP_TTL), /* int */ + ip4_entry(IP_TOS), /* int */ + /* IPPROTO_IPV6 level socket options. */ + ip6_entry(IPV6_UNICAST_HOPS), /* int */ + ip6_entry(IPV6_TCLASS), /* int */ + ip6_entry(IPV6_USE_MIN_MTU), /* int */ + /* IPPROTO_TCP level socket options. */ + tcp_entry(TCP_NODELAY), /* int */ + tcp_entry(TCP_NOOPT), /* int */ + tcp_entry(TCP_NOPUSH), /* int */ + tcp_entry(TCP_REMOTE_UDP_ENCAPS_PORT), /* int */ + tcp_entry(TCP_MAXSEG), /* int */ + tcp_entry(TCP_TXTLS_MODE), /* unsigned int */ + tcp_entry(TCP_KEEPIDLE), /* unsigned int */ + tcp_entry(TCP_KEEPINTVL), /* unsigned int */ + tcp_entry(TCP_KEEPINIT), /* unsigned int */ + tcp_entry(TCP_KEEPCNT), /* unsigned int */ + tcp_entry(TCP_PCAP_OUT), /* int */ + tcp_entry(TCP_PCAP_IN), /* int */ + tcp_entry(TCP_LOG), /* int */ + tcp_entry(TCP_LOGID), /* char * */ + tcp_entry(TCP_LOGDUMP), /* char * */ + tcp_entry(TCP_LOGDUMPID), /* char * */ + tcp_entry(TCP_CONGESTION), /* char * */ + tcp_entry(TCP_FUNCTION_BLK), /* char * */ + tcp_entry(TCP_NO_PRR), /* int */ + tcp_entry(TCP_HDWR_RATE_CAP), /* int */ +#if notyet + tcp_entry(TCP_PACING_RATE_CAP), /* uint64_t */ +#endif + tcp_entry(TCP_HDWR_UP_ONLY), /* int */ + tcp_entry(TCP_FAST_RSM_HACK), /* int */ + tcp_entry(TCP_DELACK), /* int */ + tcp_entry(TCP_REC_ABC_VAL), /* int */ + tcp_entry(TCP_USE_CMP_ACKS), /* int */ + tcp_entry(TCP_SHARED_CWND_TIME_LIMIT), /* int */ + tcp_entry(TCP_SHARED_CWND_ENABLE), /* int */ + tcp_entry(TCP_DATA_AFTER_CLOSE), /* int */ + tcp_entry(TCP_DEFER_OPTIONS), /* int */ + tcp_entry(TCP_MAXPEAKRATE), /* int */ + tcp_entry(TCP_TIMELY_DYN_ADJ), /* int */ + tcp_entry(TCP_RACK_TLP_REDUCE), /* int */ + tcp_entry(TCP_RACK_PACE_ALWAYS), /* int */ + tcp_entry(TCP_RACK_PACE_MAX_SEG), /* int */ + tcp_entry(TCP_RACK_FORCE_MSEG), /* int */ + tcp_entry(TCP_RACK_PACE_RATE_CA), /* int */ + tcp_entry(TCP_RACK_PACE_RATE_SS), /* int */ + tcp_entry(TCP_RACK_PACE_RATE_REC), /* int */ + tcp_entry(TCP_RACK_GP_INCREASE_CA), /* int */ + tcp_entry(TCP_RACK_GP_INCREASE_SS), /* int */ + tcp_entry(TCP_RACK_GP_INCREASE_REC), /* int */ + tcp_entry(TCP_RACK_RR_CONF), /* int */ + tcp_entry(TCP_RACK_PRR_SENDALOT), /* int */ + tcp_entry(TCP_RACK_MIN_TO), /* int */ + tcp_entry(TCP_RACK_EARLY_SEG), /* int */ + tcp_entry(TCP_RACK_REORD_THRESH), /* int */ + tcp_entry(TCP_RACK_REORD_FADE), /* int */ + tcp_entry(TCP_RACK_TLP_THRESH), /* int */ + tcp_entry(TCP_RACK_PKT_DELAY), /* int */ + tcp_entry(TCP_RACK_TLP_USE), /* int */ + tcp_entry(TCP_RACK_DO_DETECTION), /* int */ + tcp_entry(TCP_RACK_NONRXT_CFG_RATE), /* int */ + tcp_entry(TCP_RACK_MBUF_QUEUE), /* int */ + tcp_entry(TCP_RACK_NO_PUSH_AT_MAX), /* int */ + tcp_entry(TCP_RACK_PACE_TO_FILL), /* int */ + tcp_entry(TCP_RACK_PROFILE), /* int */ + tcp_entry(TCP_RACK_ABC_VAL), /* int */ + tcp_entry(TCP_RACK_MEASURE_CNT), /* int */ + tcp_entry(TCP_RACK_DSACK_OPT), /* int */ + tcp_entry(TCP_RACK_PACING_BETA), /* int */ + tcp_entry(TCP_RACK_PACING_BETA_ECN), /* int */ + tcp_entry(TCP_RACK_TIMER_SLOP), /* int */ + tcp_entry(TCP_RACK_ENABLE_HYSTART), /* int */ + tcp_entry(TCP_BBR_RACK_RTT_USE), /* int */ + tcp_entry(TCP_BBR_USE_RACK_RR), /* int */ + tcp_entry(TCP_BBR_HDWR_PACE), /* int */ + tcp_entry(TCP_BBR_RACK_INIT_RATE), /* int */ + tcp_entry(TCP_BBR_IWINTSO), /* int */ + tcp_entry(TCP_BBR_ALGORITHM), /* int */ + tcp_entry(TCP_BBR_TSLIMITS), /* int */ + tcp_entry(TCP_BBR_RECFORCE), /* int */ + tcp_entry(TCP_BBR_STARTUP_PG), /* int */ + tcp_entry(TCP_BBR_DRAIN_PG), /* int */ + tcp_entry(TCP_BBR_RWND_IS_APP), /* int */ + tcp_entry(TCP_BBR_PROBE_RTT_INT), /* int */ + tcp_entry(TCP_BBR_PROBE_RTT_GAIN), /* int */ + tcp_entry(TCP_BBR_PROBE_RTT_LEN), /* int */ + tcp_entry(TCP_BBR_STARTUP_LOSS_EXIT), /* int */ + tcp_entry(TCP_BBR_USEDEL_RATE), /* int */ + tcp_entry(TCP_BBR_MIN_RTO), /* int */ + tcp_entry(TCP_BBR_MAX_RTO), /* int */ + tcp_entry(TCP_BBR_PACE_PER_SEC), /* int */ + tcp_entry(TCP_BBR_PACE_DEL_TAR), /* int */ + tcp_entry(TCP_BBR_SEND_IWND_IN_TSO), /* int */ + tcp_entry(TCP_BBR_EXTRA_STATE), /* int */ + tcp_entry(TCP_BBR_UTTER_MAX_TSO), /* int */ + tcp_entry(TCP_BBR_MIN_TOPACEOUT), /* int */ + tcp_entry(TCP_BBR_FLOOR_MIN_TSO), /* int */ + tcp_entry(TCP_BBR_TSTMP_RAISES), /* int */ + tcp_entry(TCP_BBR_POLICER_DETECT), /* int */ + tcp_entry(TCP_BBR_USE_RACK_CHEAT), /* int */ + tcp_entry(TCP_BBR_PACE_SEG_MAX), /* int */ + tcp_entry(TCP_BBR_PACE_SEG_MIN), /* int */ + tcp_entry(TCP_BBR_PACE_CROSS), /* int */ + tcp_entry(TCP_BBR_PACE_OH), /* int */ + tcp_entry(TCP_BBR_TMR_PACE_OH), /* int */ + tcp_entry(TCP_BBR_RETRAN_WTSO), /* int */ + {0, 0, NULL} +}; + +static struct sockopt_parameters * +create_parameters(char *level_str, char *optname_str, char *optval_str, + size_t *optlen) +{ + long long arg; + int i, level, optname, optval_int; + struct sockopt_parameters *params; + char *end; + bool optval_is_int; + + /* Determine level, use IPPROTO_TCP as default. */ + if (level_str == NULL) + level = IPPROTO_TCP; + else { + arg = strtoll(level_str, &end, 0); + if (*end != '\0') { + for (i = 0; so_levels[i].name != NULL; i++) + if (strcmp(level_str, so_levels[i].name) == 0) { + level = so_levels[i].level; + break; + } + if (so_levels[i].name == NULL) + errx(1, "unsupported level %s", optname_str); + } else { + if (arg < 0) + errx(1, "level negative %s", optname_str); + else if (arg > INT_MAX) + errx(1, "level too large %s", optname_str); + else + level = (int)arg; + } + } + /* Determine option name. */ + if (optname_str == NULL || *optname_str == '\0') + return (NULL); + arg = strtoll(optname_str, &end, 0); + if (*end != '\0') { + for (i = 0; so_names[i].name != NULL; i++) + if (strcmp(optname_str, so_names[i].name) == 0) { + level = so_names[i].level; + optname = so_names[i].value; + break; + } + if (so_names[i].name == NULL) + errx(1, "unsupported option name %s", optname_str); + } else { + if (arg < 0) + errx(1, "option name negative %s", optname_str); + else if (arg > INT_MAX) + errx(1, "option name too large %s", optname_str); + else + optname = (int)arg; + } + /* + * Determine option value. Use int, if can be parsed as an int, + * else use a char *. + */ + if (optval_str == NULL || *optval_str == '\0') + return (NULL); + arg = strtol(optval_str, &end, 0); + optval_is_int = (*end == '\0'); + if (optval_is_int) { + if (arg < INT_MIN) + errx(1, "option value too small %s", optval_str); + else if (arg > INT_MAX) + errx(1, "option value too large %s", optval_str); + else + optval_int = (int)arg; + } + switch (optname) { + case TCP_FUNCTION_BLK: + *optlen = sizeof(struct tcp_function_set); + break; + default: + if (optval_is_int) + *optlen = sizeof(int); + else + *optlen = strlen(optval_str) + 1; + break; + } + /* Fill socket option parameters. */ + params = malloc(sizeof(struct sockopt_parameters) + *optlen); + if (params == NULL) + return (NULL); + memset(params, 0, sizeof(struct sockopt_parameters) + *optlen); + params->sop_level = level; + params->sop_optname = optname; + switch (optname) { + case TCP_FUNCTION_BLK: + strlcpy(params->sop_optval, optval_str, + TCP_FUNCTION_NAME_LEN_MAX); + break; + default: + if (optval_is_int) + memcpy(params->sop_optval, &optval_int, *optlen); + else + memcpy(params->sop_optval, optval_str, *optlen); + } + return (params); +} + +static void +usage(void) +{ + fprintf(stderr, +"usage: tcpsso -i id [level] opt-name opt-value\n" +" tcpsso -a [level] opt-name opt-value\n" +" tcpsso -C cc-algo [-S stack] [-s state] [level] opt-name opt-value\n" +" tcpsso [-C cc-algo] -S stack [-s state] [level] opt-name opt-value\n" +" tcpsso [-C cc-algo] [-S stack] -s state [level] opt-name opt-value\n"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct sockopt_parameters *params; + uint64_t id; + size_t optlen; + int ch, state; + char stack[TCP_FUNCTION_NAME_LEN_MAX]; + char ca_name[TCP_CA_NAME_MAX]; + bool ok, apply_all, apply_subset, apply_specific; + + apply_all = false; + apply_subset = false; + apply_specific = false; + ca_name[0] = '\0'; + stack[0] = '\0'; + state = -1; + id = 0; + + while ((ch = getopt(argc, argv, "aC:i:S:s:")) != -1) { + switch (ch) { + case 'a': + apply_all = true; + break; + case 'C': + apply_subset = true; + strlcpy(ca_name, optarg, sizeof(ca_name)); + break; + case 'i': + apply_specific = true; + id = strtoull(optarg, NULL, 0); + break; + case 'S': + apply_subset = true; + strlcpy(stack, optarg, sizeof(stack)); + break; + case 's': + apply_subset = true; + for (state = 0; state < TCP_NSTATES; state++) { + if (strcmp(tcpstates[state], optarg) == 0) + break; + } + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + if ((state == TCP_NSTATES) || + (state == TCPS_TIME_WAIT) || + (argc < 2) || (argc > 3) || + (apply_all && apply_subset) || + (apply_all && apply_specific) || + (apply_subset && apply_specific) || + !(apply_all || apply_subset || apply_specific)) + usage(); + if (argc == 2) + params = create_parameters(NULL, argv[0], argv[1], &optlen); + else + params = create_parameters(argv[0], argv[1], argv[2], &optlen); + if (params != NULL) { + if (apply_specific) + ok = tcpsso(id, params, optlen); + else + ok = tcpssoall(ca_name, stack, state, params, optlen); + free(params); + } else + ok = false; + return (ok ? 0 : 1); +}