1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2022 Michael Tuexen <tuexen@FreeBSD.org>
5 * Copyright (c) 2009 Juli Mallett <jmallett@FreeBSD.org>
6 * Copyright (c) 2004 Markus Friedl <markus@openbsd.org>
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/param.h>
31#include <sys/types.h>
32#include <sys/socket.h>
33#include <sys/socketvar.h>
34#include <sys/sysctl.h>
35
36#include <netinet/in.h>
37#include <netinet/in_pcb.h>
38#define TCPSTATES
39#include <netinet/tcp_fsm.h>
40#include <netinet/tcp_var.h>
41
42#include <err.h>
43#include <errno.h>
44#include <inttypes.h>
45#include <stdbool.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <unistd.h>
50
51static struct xinpgen *
52getxpcblist(const char *name)
53{
54	struct xinpgen *xinp;
55	size_t len;
56	int rv;
57
58	len = 0;
59	rv = sysctlbyname(name, NULL, &len, NULL, 0);
60	if (rv == -1)
61		err(1, "sysctlbyname %s", name);
62
63	if (len == 0)
64		errx(1, "%s is empty", name);
65
66	xinp = malloc(len);
67	if (xinp == NULL)
68		errx(1, "malloc failed");
69
70	rv = sysctlbyname(name, xinp, &len, NULL, 0);
71	if (rv == -1)
72		err(1, "sysctlbyname %s", name);
73
74	return (xinp);
75}
76
77static bool
78tcpsso(uint64_t id, struct sockopt_parameters *params, size_t optlen)
79{
80	int rv;
81
82	params->sop_id = id;
83	rv = sysctlbyname("net.inet.tcp.setsockopt", NULL, NULL, params,
84	    sizeof(struct sockopt_parameters) + optlen);
85	if (rv == -1) {
86		warn("Failed for id %" PRIu64, params->sop_id);
87		return (false);
88	} else
89		return (true);
90}
91
92static bool
93tcpssoall(const char *ca_name, const char *stack, int state,
94    struct sockopt_parameters *params, size_t optlen)
95{
96	struct xinpgen *head, *xinp;
97	struct xtcpcb *xtp;
98	struct xinpcb *xip;
99	bool ok;
100
101	ok = true;
102
103	head = getxpcblist("net.inet.tcp.pcblist");
104
105#define	XINP_NEXT(xinp)							\
106	((struct xinpgen *)(uintptr_t)((uintptr_t)(xinp) + (xinp)->xig_len))
107
108	for (xinp = XINP_NEXT(head); xinp->xig_len > sizeof *xinp;
109	    xinp = XINP_NEXT(xinp)) {
110		xtp = (struct xtcpcb *)xinp;
111		xip = &xtp->xt_inp;
112
113		/* Ignore PCBs which were freed during copyout. */
114		if (xip->inp_gencnt > head->xig_gen)
115			continue;
116
117
118		/* If requested, skip sockets not having the requested state. */
119		if ((state != -1) && (xtp->t_state != state))
120			continue;
121
122		/*
123		 * If requested, skip sockets not having the requested
124		 * congestion control algorithm.
125		 */
126		if (ca_name[0] != '\0' &&
127		    strncmp(xtp->xt_cc, ca_name, TCP_CA_NAME_MAX))
128			continue;
129
130		/* If requested, skip sockets not having the requested stack. */
131		if (stack[0] != '\0' &&
132		    strncmp(xtp->xt_stack, stack, TCP_FUNCTION_NAME_LEN_MAX))
133			continue;
134
135		params->sop_inc = xip->inp_inc;
136		if (!tcpsso(xip->inp_gencnt, params, optlen))
137			ok = false;
138	}
139	free(head);
140
141	return (ok);
142}
143
144struct so_level {
145	int level;
146	const char *name;
147};
148
149#define level_entry(level) { level, #level }
150
151static struct so_level so_levels[] = {
152	level_entry(SOL_SOCKET),
153	level_entry(IPPROTO_IP),
154	level_entry(IPPROTO_IPV6),
155	level_entry(IPPROTO_TCP),
156	{ 0, NULL }
157};
158
159struct so_name {
160	int level;
161	int value;
162	const char *name;
163};
164
165#define sol_entry(name) { SOL_SOCKET, name, #name }
166#define ip4_entry(name) { IPPROTO_IP, name, #name }
167#define ip6_entry(name) { IPPROTO_IPV6, name, #name }
168#define tcp_entry(name) { IPPROTO_TCP, name, #name }
169
170static struct so_name so_names[] = {
171	/* SOL_SOCKET level socket options. */
172	sol_entry(SO_DEBUG),			/* int */
173	sol_entry(SO_RCVBUF),			/* int */
174	sol_entry(SO_SNDBUF),			/* int */
175	sol_entry(SO_RCVLOWAT),			/* int */
176	sol_entry(SO_SNDLOWAT),			/* int */
177	/* IPPROTO_IP level socket options. */
178	ip4_entry(IP_TTL),			/* int */
179	ip4_entry(IP_TOS),			/* int */
180	/* IPPROTO_IPV6 level socket options. */
181	ip6_entry(IPV6_UNICAST_HOPS),		/* int */
182	ip6_entry(IPV6_TCLASS),			/* int */
183	ip6_entry(IPV6_USE_MIN_MTU),		/* int */
184	/* IPPROTO_TCP level socket options. */
185	tcp_entry(TCP_NODELAY),			/* int */
186	tcp_entry(TCP_NOOPT),			/* int */
187	tcp_entry(TCP_NOPUSH),			/* int */
188	tcp_entry(TCP_REMOTE_UDP_ENCAPS_PORT),	/* int */
189	tcp_entry(TCP_MAXSEG),			/* int */
190	tcp_entry(TCP_TXTLS_MODE),		/* unsigned int */
191	tcp_entry(TCP_MAXUNACKTIME),		/* unsigned int */
192	tcp_entry(TCP_KEEPIDLE),		/* unsigned int */
193	tcp_entry(TCP_KEEPINTVL),		/* unsigned int */
194	tcp_entry(TCP_KEEPINIT),		/* unsigned int */
195	tcp_entry(TCP_KEEPCNT),			/* unsigned int */
196	tcp_entry(TCP_PCAP_OUT),		/* int */
197	tcp_entry(TCP_PCAP_IN),			/* int */
198	tcp_entry(TCP_LOG),			/* int */
199	tcp_entry(TCP_LOGID),			/* char * */
200	tcp_entry(TCP_LOGDUMP),			/* char * */
201	tcp_entry(TCP_LOGDUMPID),		/* char * */
202	tcp_entry(TCP_CONGESTION),		/* char * */
203	tcp_entry(TCP_FUNCTION_BLK),		/* char * */
204	tcp_entry(TCP_NO_PRR),			/* int */
205	tcp_entry(TCP_HDWR_RATE_CAP),		/* int */
206#if notyet
207	tcp_entry(TCP_PACING_RATE_CAP),		/* uint64_t */
208#endif
209	tcp_entry(TCP_HDWR_UP_ONLY),		/* int */
210	tcp_entry(TCP_FAST_RSM_HACK),		/* int */
211	tcp_entry(TCP_DELACK),			/* int */
212	tcp_entry(TCP_REC_ABC_VAL),		/* int */
213	tcp_entry(TCP_USE_CMP_ACKS),		/* int */
214	tcp_entry(TCP_SHARED_CWND_TIME_LIMIT),	/* int */
215	tcp_entry(TCP_SHARED_CWND_ENABLE),	/* int */
216	tcp_entry(TCP_DATA_AFTER_CLOSE),	/* int */
217	tcp_entry(TCP_DEFER_OPTIONS),		/* int */
218	tcp_entry(TCP_MAXPEAKRATE),		/* int */
219	tcp_entry(TCP_TIMELY_DYN_ADJ),		/* int */
220	tcp_entry(TCP_RACK_TLP_REDUCE),		/* int */
221	tcp_entry(TCP_RACK_PACE_ALWAYS),	/* int */
222	tcp_entry(TCP_RACK_PACE_MAX_SEG),	/* int */
223	tcp_entry(TCP_RACK_FORCE_MSEG),		/* int */
224	tcp_entry(TCP_RACK_PACE_RATE_CA),	/* int */
225	tcp_entry(TCP_RACK_PACE_RATE_SS),	/* int */
226	tcp_entry(TCP_RACK_PACE_RATE_REC),	/* int */
227	tcp_entry(TCP_RACK_GP_INCREASE_CA),	/* int */
228	tcp_entry(TCP_RACK_GP_INCREASE_SS),	/* int */
229	tcp_entry(TCP_RACK_GP_INCREASE_REC),	/* int */
230	tcp_entry(TCP_RACK_RR_CONF),		/* int */
231	tcp_entry(TCP_RACK_PRR_SENDALOT),	/* int */
232	tcp_entry(TCP_RACK_MIN_TO),		/* int */
233	tcp_entry(TCP_RACK_EARLY_SEG),		/* int */
234	tcp_entry(TCP_RACK_REORD_THRESH),	/* int */
235	tcp_entry(TCP_RACK_REORD_FADE),		/* int */
236	tcp_entry(TCP_RACK_TLP_THRESH),		/* int */
237	tcp_entry(TCP_RACK_PKT_DELAY),		/* int */
238	tcp_entry(TCP_RACK_TLP_USE),		/* int */
239	tcp_entry(TCP_RACK_DO_DETECTION),	/* int */
240	tcp_entry(TCP_RACK_NONRXT_CFG_RATE),	/* int */
241	tcp_entry(TCP_RACK_MBUF_QUEUE),		/* int */
242	tcp_entry(TCP_RACK_NO_PUSH_AT_MAX),	/* int */
243	tcp_entry(TCP_RACK_PACE_TO_FILL),	/* int */
244	tcp_entry(TCP_RACK_PROFILE),		/* int */
245	tcp_entry(TCP_RACK_ABC_VAL),		/* int */
246	tcp_entry(TCP_RACK_MEASURE_CNT),	/* int */
247	tcp_entry(TCP_RACK_DSACK_OPT),		/* int */
248	tcp_entry(TCP_RACK_PACING_BETA),	/* int */
249	tcp_entry(TCP_RACK_PACING_BETA_ECN),	/* int */
250	tcp_entry(TCP_RACK_TIMER_SLOP),		/* int */
251	tcp_entry(TCP_RACK_ENABLE_HYSTART),	/* int */
252	tcp_entry(TCP_BBR_RACK_RTT_USE),	/* int */
253	tcp_entry(TCP_BBR_USE_RACK_RR),		/* int */
254	tcp_entry(TCP_BBR_HDWR_PACE),		/* int */
255	tcp_entry(TCP_BBR_RACK_INIT_RATE),	/* int */
256	tcp_entry(TCP_BBR_IWINTSO),		/* int */
257	tcp_entry(TCP_BBR_ALGORITHM),		/* int */
258	tcp_entry(TCP_BBR_TSLIMITS),		/* int */
259	tcp_entry(TCP_BBR_RECFORCE),		/* int */
260	tcp_entry(TCP_BBR_STARTUP_PG),		/* int */
261	tcp_entry(TCP_BBR_DRAIN_PG),		/* int */
262	tcp_entry(TCP_BBR_RWND_IS_APP),		/* int */
263	tcp_entry(TCP_BBR_PROBE_RTT_INT),	/* int */
264	tcp_entry(TCP_BBR_PROBE_RTT_GAIN),	/* int */
265	tcp_entry(TCP_BBR_PROBE_RTT_LEN),	/* int */
266	tcp_entry(TCP_BBR_STARTUP_LOSS_EXIT),	/* int */
267	tcp_entry(TCP_BBR_USEDEL_RATE),		/* int */
268	tcp_entry(TCP_BBR_MIN_RTO),		/* int */
269	tcp_entry(TCP_BBR_MAX_RTO),		/* int */
270	tcp_entry(TCP_BBR_PACE_PER_SEC),	/* int */
271	tcp_entry(TCP_BBR_PACE_DEL_TAR),	/* int */
272	tcp_entry(TCP_BBR_SEND_IWND_IN_TSO),	/* int */
273	tcp_entry(TCP_BBR_EXTRA_STATE),		/* int */
274	tcp_entry(TCP_BBR_UTTER_MAX_TSO),	/* int */
275	tcp_entry(TCP_BBR_MIN_TOPACEOUT),	/* int */
276	tcp_entry(TCP_BBR_FLOOR_MIN_TSO),	/* int */
277	tcp_entry(TCP_BBR_TSTMP_RAISES),	/* int */
278	tcp_entry(TCP_BBR_POLICER_DETECT),	/* int */
279	tcp_entry(TCP_BBR_USE_RACK_CHEAT),	/* int */
280	tcp_entry(TCP_BBR_PACE_SEG_MAX),	/* int */
281	tcp_entry(TCP_BBR_PACE_SEG_MIN),	/* int */
282	tcp_entry(TCP_BBR_PACE_CROSS),		/* int */
283	tcp_entry(TCP_BBR_PACE_OH),		/* int */
284	tcp_entry(TCP_BBR_TMR_PACE_OH),		/* int */
285	tcp_entry(TCP_BBR_RETRAN_WTSO),		/* int */
286	{0, 0, NULL}
287};
288
289static struct sockopt_parameters *
290create_parameters(char *level_str, char *optname_str, char *optval_str,
291    size_t *optlen)
292{
293	long long arg;
294	int i, level, optname, optval_int;
295	struct sockopt_parameters *params;
296	char *end;
297	bool optval_is_int;
298
299	/* Determine level, use IPPROTO_TCP as default. */
300	if (level_str == NULL)
301		level = IPPROTO_TCP;
302	else {
303		arg = strtoll(level_str, &end, 0);
304		if (*end != '\0') {
305			for (i = 0; so_levels[i].name != NULL; i++)
306				if (strcmp(level_str, so_levels[i].name) == 0) {
307					level = so_levels[i].level;
308					break;
309				}
310			if (so_levels[i].name == NULL)
311				errx(1, "unsupported level %s", optname_str);
312		} else {
313			if (arg < 0)
314				errx(1, "level negative %s", optname_str);
315			else if (arg > INT_MAX)
316				errx(1, "level too large %s", optname_str);
317			else
318				level = (int)arg;
319		}
320	}
321	/* Determine option name. */
322	if (optname_str == NULL || *optname_str == '\0')
323		return (NULL);
324	arg = strtoll(optname_str, &end, 0);
325	if (*end != '\0') {
326		for (i = 0; so_names[i].name != NULL; i++)
327			if (strcmp(optname_str, so_names[i].name) == 0) {
328				level = so_names[i].level;
329				optname = so_names[i].value;
330				break;
331			}
332		if (so_names[i].name == NULL)
333			errx(1, "unsupported option name %s", optname_str);
334	} else {
335		if (arg < 0)
336			errx(1, "option name negative %s", optname_str);
337		else if (arg > INT_MAX)
338			errx(1, "option name too large %s", optname_str);
339		else
340			optname = (int)arg;
341	}
342	/*
343	 * Determine option value. Use int, if can be parsed as an int,
344	 * else use a char *.
345	 */
346	if (optval_str == NULL || *optval_str == '\0')
347		return (NULL);
348	arg = strtol(optval_str, &end, 0);
349	optval_is_int = (*end == '\0');
350	if (optval_is_int) {
351		if (arg < INT_MIN)
352			errx(1, "option value too small %s", optval_str);
353		else if (arg > INT_MAX)
354			errx(1, "option value too large %s", optval_str);
355		else
356			optval_int = (int)arg;
357	}
358	switch (optname) {
359	case TCP_FUNCTION_BLK:
360		*optlen = sizeof(struct tcp_function_set);
361		break;
362	default:
363		if (optval_is_int)
364			*optlen = sizeof(int);
365		else
366			*optlen = strlen(optval_str) + 1;
367		break;
368	}
369	/* Fill socket option parameters. */
370	params = malloc(sizeof(struct sockopt_parameters) + *optlen);
371	if (params == NULL)
372		return (NULL);
373	memset(params, 0, sizeof(struct sockopt_parameters) + *optlen);
374	params->sop_level = level;
375	params->sop_optname = optname;
376	switch (optname) {
377	case TCP_FUNCTION_BLK:
378		strlcpy(params->sop_optval, optval_str,
379		    TCP_FUNCTION_NAME_LEN_MAX);
380		break;
381	default:
382		if (optval_is_int)
383			memcpy(params->sop_optval, &optval_int, *optlen);
384		else
385			memcpy(params->sop_optval, optval_str, *optlen);
386	}
387	return (params);
388}
389
390static void
391usage(void)
392{
393	fprintf(stderr,
394"usage: tcpsso -i id [level] opt-name opt-value\n"
395"       tcpsso -a [level] opt-name opt-value\n"
396"       tcpsso -C cc-algo [-S stack] [-s state] [level] opt-name opt-value\n"
397"       tcpsso [-C cc-algo] -S stack [-s state] [level] opt-name opt-value\n"
398"       tcpsso [-C cc-algo] [-S stack] -s state [level] opt-name opt-value\n");
399	exit(1);
400}
401
402int
403main(int argc, char *argv[])
404{
405	struct sockopt_parameters *params;
406	uint64_t id;
407	size_t optlen;
408	int ch, state;
409	char stack[TCP_FUNCTION_NAME_LEN_MAX];
410	char ca_name[TCP_CA_NAME_MAX];
411	bool ok, apply_all, apply_subset, apply_specific;
412
413	apply_all = false;
414	apply_subset = false;
415	apply_specific = false;
416	ca_name[0] = '\0';
417	stack[0] = '\0';
418	state = -1;
419	id = 0;
420
421	while ((ch = getopt(argc, argv, "aC:i:S:s:")) != -1) {
422		switch (ch) {
423		case 'a':
424			apply_all = true;
425			break;
426		case 'C':
427			apply_subset = true;
428			strlcpy(ca_name, optarg, sizeof(ca_name));
429			break;
430		case 'i':
431			apply_specific = true;
432			id = strtoull(optarg, NULL, 0);
433			break;
434		case 'S':
435			apply_subset = true;
436			strlcpy(stack, optarg, sizeof(stack));
437			break;
438		case 's':
439			apply_subset = true;
440			for (state = 0; state < TCP_NSTATES; state++) {
441				if (strcmp(tcpstates[state], optarg) == 0)
442					break;
443			}
444			break;
445		default:
446			usage();
447		}
448	}
449	argc -= optind;
450	argv += optind;
451	if ((state == TCP_NSTATES) ||
452	    (argc < 2) || (argc > 3) ||
453	    (apply_all && apply_subset) ||
454	    (apply_all && apply_specific) ||
455	    (apply_subset && apply_specific) ||
456	    !(apply_all || apply_subset || apply_specific))
457		usage();
458	if (argc == 2)
459		params = create_parameters(NULL, argv[0], argv[1], &optlen);
460	else
461		params = create_parameters(argv[0], argv[1], argv[2], &optlen);
462	if (params != NULL) {
463		if (apply_specific)
464			ok = tcpsso(id, params, optlen);
465		else
466			ok = tcpssoall(ca_name, stack, state, params, optlen);
467		free(params);
468	} else
469		ok = false;
470	return (ok ? 0 : 1);
471}
472