xref: /freebsd/usr.sbin/tcpsso/tcpsso.c (revision bdd1243df58e60e85101c09001d9812a789b6bc4)
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 
51 static struct xinpgen *
52 getxpcblist(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 
77 static bool
78 tcpsso(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 
92 static bool
93 tcpssoall(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 		/* Skip endpoints in TIME WAIT. */
119 		if (xtp->t_state == TCPS_TIME_WAIT)
120 			continue;
121 
122 		/* If requested, skip sockets not having the requested state. */
123 		if ((state != -1) && (xtp->t_state != state))
124 			continue;
125 
126 		/*
127 		 * If requested, skip sockets not having the requested
128 		 * congestion control algorithm.
129 		 */
130 		if (ca_name[0] != '\0' &&
131 		    strncmp(xtp->xt_cc, ca_name, TCP_CA_NAME_MAX))
132 			continue;
133 
134 		/* If requested, skip sockets not having the requested stack. */
135 		if (stack[0] != '\0' &&
136 		    strncmp(xtp->xt_stack, stack, TCP_FUNCTION_NAME_LEN_MAX))
137 			continue;
138 
139 		params->sop_inc = xip->inp_inc;
140 		if (!tcpsso(xip->inp_gencnt, params, optlen))
141 			ok = false;
142 	}
143 	free(head);
144 
145 	return (ok);
146 }
147 
148 struct so_level {
149 	int level;
150 	const char *name;
151 };
152 
153 #define level_entry(level) { level, #level }
154 
155 static struct so_level so_levels[] = {
156 	level_entry(SOL_SOCKET),
157 	level_entry(IPPROTO_IP),
158 	level_entry(IPPROTO_IPV6),
159 	level_entry(IPPROTO_TCP),
160 	{ 0, NULL }
161 };
162 
163 struct so_name {
164 	int level;
165 	int value;
166 	const char *name;
167 };
168 
169 #define sol_entry(name) { SOL_SOCKET, name, #name }
170 #define ip4_entry(name) { IPPROTO_IP, name, #name }
171 #define ip6_entry(name) { IPPROTO_IPV6, name, #name }
172 #define tcp_entry(name) { IPPROTO_TCP, name, #name }
173 
174 static struct so_name so_names[] = {
175 	/* SOL_SOCKET level socket options. */
176 	sol_entry(SO_DEBUG),			/* int */
177 	sol_entry(SO_RCVBUF),			/* int */
178 	sol_entry(SO_SNDBUF),			/* int */
179 	sol_entry(SO_RCVLOWAT),			/* int */
180 	sol_entry(SO_SNDLOWAT),			/* int */
181 	/* IPPROTO_IP level socket options. */
182 	ip4_entry(IP_TTL),			/* int */
183 	ip4_entry(IP_TOS),			/* int */
184 	/* IPPROTO_IPV6 level socket options. */
185 	ip6_entry(IPV6_UNICAST_HOPS),		/* int */
186 	ip6_entry(IPV6_TCLASS),			/* int */
187 	ip6_entry(IPV6_USE_MIN_MTU),		/* int */
188 	/* IPPROTO_TCP level socket options. */
189 	tcp_entry(TCP_NODELAY),			/* int */
190 	tcp_entry(TCP_NOOPT),			/* int */
191 	tcp_entry(TCP_NOPUSH),			/* int */
192 	tcp_entry(TCP_REMOTE_UDP_ENCAPS_PORT),	/* int */
193 	tcp_entry(TCP_MAXSEG),			/* int */
194 	tcp_entry(TCP_TXTLS_MODE),		/* unsigned int */
195 	tcp_entry(TCP_MAXUNACKTIME),		/* unsigned int */
196 	tcp_entry(TCP_KEEPIDLE),		/* unsigned int */
197 	tcp_entry(TCP_KEEPINTVL),		/* unsigned int */
198 	tcp_entry(TCP_KEEPINIT),		/* unsigned int */
199 	tcp_entry(TCP_KEEPCNT),			/* unsigned int */
200 	tcp_entry(TCP_PCAP_OUT),		/* int */
201 	tcp_entry(TCP_PCAP_IN),			/* int */
202 	tcp_entry(TCP_LOG),			/* int */
203 	tcp_entry(TCP_LOGID),			/* char * */
204 	tcp_entry(TCP_LOGDUMP),			/* char * */
205 	tcp_entry(TCP_LOGDUMPID),		/* char * */
206 	tcp_entry(TCP_CONGESTION),		/* char * */
207 	tcp_entry(TCP_FUNCTION_BLK),		/* char * */
208 	tcp_entry(TCP_NO_PRR),			/* int */
209 	tcp_entry(TCP_HDWR_RATE_CAP),		/* int */
210 #if notyet
211 	tcp_entry(TCP_PACING_RATE_CAP),		/* uint64_t */
212 #endif
213 	tcp_entry(TCP_HDWR_UP_ONLY),		/* int */
214 	tcp_entry(TCP_FAST_RSM_HACK),		/* int */
215 	tcp_entry(TCP_DELACK),			/* int */
216 	tcp_entry(TCP_REC_ABC_VAL),		/* int */
217 	tcp_entry(TCP_USE_CMP_ACKS),		/* int */
218 	tcp_entry(TCP_SHARED_CWND_TIME_LIMIT),	/* int */
219 	tcp_entry(TCP_SHARED_CWND_ENABLE),	/* int */
220 	tcp_entry(TCP_DATA_AFTER_CLOSE),	/* int */
221 	tcp_entry(TCP_DEFER_OPTIONS),		/* int */
222 	tcp_entry(TCP_MAXPEAKRATE),		/* int */
223 	tcp_entry(TCP_TIMELY_DYN_ADJ),		/* int */
224 	tcp_entry(TCP_RACK_TLP_REDUCE),		/* int */
225 	tcp_entry(TCP_RACK_PACE_ALWAYS),	/* int */
226 	tcp_entry(TCP_RACK_PACE_MAX_SEG),	/* int */
227 	tcp_entry(TCP_RACK_FORCE_MSEG),		/* int */
228 	tcp_entry(TCP_RACK_PACE_RATE_CA),	/* int */
229 	tcp_entry(TCP_RACK_PACE_RATE_SS),	/* int */
230 	tcp_entry(TCP_RACK_PACE_RATE_REC),	/* int */
231 	tcp_entry(TCP_RACK_GP_INCREASE_CA),	/* int */
232 	tcp_entry(TCP_RACK_GP_INCREASE_SS),	/* int */
233 	tcp_entry(TCP_RACK_GP_INCREASE_REC),	/* int */
234 	tcp_entry(TCP_RACK_RR_CONF),		/* int */
235 	tcp_entry(TCP_RACK_PRR_SENDALOT),	/* int */
236 	tcp_entry(TCP_RACK_MIN_TO),		/* int */
237 	tcp_entry(TCP_RACK_EARLY_SEG),		/* int */
238 	tcp_entry(TCP_RACK_REORD_THRESH),	/* int */
239 	tcp_entry(TCP_RACK_REORD_FADE),		/* int */
240 	tcp_entry(TCP_RACK_TLP_THRESH),		/* int */
241 	tcp_entry(TCP_RACK_PKT_DELAY),		/* int */
242 	tcp_entry(TCP_RACK_TLP_USE),		/* int */
243 	tcp_entry(TCP_RACK_DO_DETECTION),	/* int */
244 	tcp_entry(TCP_RACK_NONRXT_CFG_RATE),	/* int */
245 	tcp_entry(TCP_RACK_MBUF_QUEUE),		/* int */
246 	tcp_entry(TCP_RACK_NO_PUSH_AT_MAX),	/* int */
247 	tcp_entry(TCP_RACK_PACE_TO_FILL),	/* int */
248 	tcp_entry(TCP_RACK_PROFILE),		/* int */
249 	tcp_entry(TCP_RACK_ABC_VAL),		/* int */
250 	tcp_entry(TCP_RACK_MEASURE_CNT),	/* int */
251 	tcp_entry(TCP_RACK_DSACK_OPT),		/* int */
252 	tcp_entry(TCP_RACK_PACING_BETA),	/* int */
253 	tcp_entry(TCP_RACK_PACING_BETA_ECN),	/* int */
254 	tcp_entry(TCP_RACK_TIMER_SLOP),		/* int */
255 	tcp_entry(TCP_RACK_ENABLE_HYSTART),	/* int */
256 	tcp_entry(TCP_BBR_RACK_RTT_USE),	/* int */
257 	tcp_entry(TCP_BBR_USE_RACK_RR),		/* int */
258 	tcp_entry(TCP_BBR_HDWR_PACE),		/* int */
259 	tcp_entry(TCP_BBR_RACK_INIT_RATE),	/* int */
260 	tcp_entry(TCP_BBR_IWINTSO),		/* int */
261 	tcp_entry(TCP_BBR_ALGORITHM),		/* int */
262 	tcp_entry(TCP_BBR_TSLIMITS),		/* int */
263 	tcp_entry(TCP_BBR_RECFORCE),		/* int */
264 	tcp_entry(TCP_BBR_STARTUP_PG),		/* int */
265 	tcp_entry(TCP_BBR_DRAIN_PG),		/* int */
266 	tcp_entry(TCP_BBR_RWND_IS_APP),		/* int */
267 	tcp_entry(TCP_BBR_PROBE_RTT_INT),	/* int */
268 	tcp_entry(TCP_BBR_PROBE_RTT_GAIN),	/* int */
269 	tcp_entry(TCP_BBR_PROBE_RTT_LEN),	/* int */
270 	tcp_entry(TCP_BBR_STARTUP_LOSS_EXIT),	/* int */
271 	tcp_entry(TCP_BBR_USEDEL_RATE),		/* int */
272 	tcp_entry(TCP_BBR_MIN_RTO),		/* int */
273 	tcp_entry(TCP_BBR_MAX_RTO),		/* int */
274 	tcp_entry(TCP_BBR_PACE_PER_SEC),	/* int */
275 	tcp_entry(TCP_BBR_PACE_DEL_TAR),	/* int */
276 	tcp_entry(TCP_BBR_SEND_IWND_IN_TSO),	/* int */
277 	tcp_entry(TCP_BBR_EXTRA_STATE),		/* int */
278 	tcp_entry(TCP_BBR_UTTER_MAX_TSO),	/* int */
279 	tcp_entry(TCP_BBR_MIN_TOPACEOUT),	/* int */
280 	tcp_entry(TCP_BBR_FLOOR_MIN_TSO),	/* int */
281 	tcp_entry(TCP_BBR_TSTMP_RAISES),	/* int */
282 	tcp_entry(TCP_BBR_POLICER_DETECT),	/* int */
283 	tcp_entry(TCP_BBR_USE_RACK_CHEAT),	/* int */
284 	tcp_entry(TCP_BBR_PACE_SEG_MAX),	/* int */
285 	tcp_entry(TCP_BBR_PACE_SEG_MIN),	/* int */
286 	tcp_entry(TCP_BBR_PACE_CROSS),		/* int */
287 	tcp_entry(TCP_BBR_PACE_OH),		/* int */
288 	tcp_entry(TCP_BBR_TMR_PACE_OH),		/* int */
289 	tcp_entry(TCP_BBR_RETRAN_WTSO),		/* int */
290 	{0, 0, NULL}
291 };
292 
293 static struct sockopt_parameters *
294 create_parameters(char *level_str, char *optname_str, char *optval_str,
295     size_t *optlen)
296 {
297 	long long arg;
298 	int i, level, optname, optval_int;
299 	struct sockopt_parameters *params;
300 	char *end;
301 	bool optval_is_int;
302 
303 	/* Determine level, use IPPROTO_TCP as default. */
304 	if (level_str == NULL)
305 		level = IPPROTO_TCP;
306 	else {
307 		arg = strtoll(level_str, &end, 0);
308 		if (*end != '\0') {
309 			for (i = 0; so_levels[i].name != NULL; i++)
310 				if (strcmp(level_str, so_levels[i].name) == 0) {
311 					level = so_levels[i].level;
312 					break;
313 				}
314 			if (so_levels[i].name == NULL)
315 				errx(1, "unsupported level %s", optname_str);
316 		} else {
317 			if (arg < 0)
318 				errx(1, "level negative %s", optname_str);
319 			else if (arg > INT_MAX)
320 				errx(1, "level too large %s", optname_str);
321 			else
322 				level = (int)arg;
323 		}
324 	}
325 	/* Determine option name. */
326 	if (optname_str == NULL || *optname_str == '\0')
327 		return (NULL);
328 	arg = strtoll(optname_str, &end, 0);
329 	if (*end != '\0') {
330 		for (i = 0; so_names[i].name != NULL; i++)
331 			if (strcmp(optname_str, so_names[i].name) == 0) {
332 				level = so_names[i].level;
333 				optname = so_names[i].value;
334 				break;
335 			}
336 		if (so_names[i].name == NULL)
337 			errx(1, "unsupported option name %s", optname_str);
338 	} else {
339 		if (arg < 0)
340 			errx(1, "option name negative %s", optname_str);
341 		else if (arg > INT_MAX)
342 			errx(1, "option name too large %s", optname_str);
343 		else
344 			optname = (int)arg;
345 	}
346 	/*
347 	 * Determine option value. Use int, if can be parsed as an int,
348 	 * else use a char *.
349 	 */
350 	if (optval_str == NULL || *optval_str == '\0')
351 		return (NULL);
352 	arg = strtol(optval_str, &end, 0);
353 	optval_is_int = (*end == '\0');
354 	if (optval_is_int) {
355 		if (arg < INT_MIN)
356 			errx(1, "option value too small %s", optval_str);
357 		else if (arg > INT_MAX)
358 			errx(1, "option value too large %s", optval_str);
359 		else
360 			optval_int = (int)arg;
361 	}
362 	switch (optname) {
363 	case TCP_FUNCTION_BLK:
364 		*optlen = sizeof(struct tcp_function_set);
365 		break;
366 	default:
367 		if (optval_is_int)
368 			*optlen = sizeof(int);
369 		else
370 			*optlen = strlen(optval_str) + 1;
371 		break;
372 	}
373 	/* Fill socket option parameters. */
374 	params = malloc(sizeof(struct sockopt_parameters) + *optlen);
375 	if (params == NULL)
376 		return (NULL);
377 	memset(params, 0, sizeof(struct sockopt_parameters) + *optlen);
378 	params->sop_level = level;
379 	params->sop_optname = optname;
380 	switch (optname) {
381 	case TCP_FUNCTION_BLK:
382 		strlcpy(params->sop_optval, optval_str,
383 		    TCP_FUNCTION_NAME_LEN_MAX);
384 		break;
385 	default:
386 		if (optval_is_int)
387 			memcpy(params->sop_optval, &optval_int, *optlen);
388 		else
389 			memcpy(params->sop_optval, optval_str, *optlen);
390 	}
391 	return (params);
392 }
393 
394 static void
395 usage(void)
396 {
397 	fprintf(stderr,
398 "usage: tcpsso -i id [level] opt-name opt-value\n"
399 "       tcpsso -a [level] opt-name opt-value\n"
400 "       tcpsso -C cc-algo [-S stack] [-s state] [level] opt-name opt-value\n"
401 "       tcpsso [-C cc-algo] -S stack [-s state] [level] opt-name opt-value\n"
402 "       tcpsso [-C cc-algo] [-S stack] -s state [level] opt-name opt-value\n");
403 	exit(1);
404 }
405 
406 int
407 main(int argc, char *argv[])
408 {
409 	struct sockopt_parameters *params;
410 	uint64_t id;
411 	size_t optlen;
412 	int ch, state;
413 	char stack[TCP_FUNCTION_NAME_LEN_MAX];
414 	char ca_name[TCP_CA_NAME_MAX];
415 	bool ok, apply_all, apply_subset, apply_specific;
416 
417 	apply_all = false;
418 	apply_subset = false;
419 	apply_specific = false;
420 	ca_name[0] = '\0';
421 	stack[0] = '\0';
422 	state = -1;
423 	id = 0;
424 
425 	while ((ch = getopt(argc, argv, "aC:i:S:s:")) != -1) {
426 		switch (ch) {
427 		case 'a':
428 			apply_all = true;
429 			break;
430 		case 'C':
431 			apply_subset = true;
432 			strlcpy(ca_name, optarg, sizeof(ca_name));
433 			break;
434 		case 'i':
435 			apply_specific = true;
436 			id = strtoull(optarg, NULL, 0);
437 			break;
438 		case 'S':
439 			apply_subset = true;
440 			strlcpy(stack, optarg, sizeof(stack));
441 			break;
442 		case 's':
443 			apply_subset = true;
444 			for (state = 0; state < TCP_NSTATES; state++) {
445 				if (strcmp(tcpstates[state], optarg) == 0)
446 					break;
447 			}
448 			break;
449 		default:
450 			usage();
451 		}
452 	}
453 	argc -= optind;
454 	argv += optind;
455 	if ((state == TCP_NSTATES) ||
456 	    (state == TCPS_TIME_WAIT) ||
457 	    (argc < 2) || (argc > 3) ||
458 	    (apply_all && apply_subset) ||
459 	    (apply_all && apply_specific) ||
460 	    (apply_subset && apply_specific) ||
461 	    !(apply_all || apply_subset || apply_specific))
462 		usage();
463 	if (argc == 2)
464 		params = create_parameters(NULL, argv[0], argv[1], &optlen);
465 	else
466 		params = create_parameters(argv[0], argv[1], argv[2], &optlen);
467 	if (params != NULL) {
468 		if (apply_specific)
469 			ok = tcpsso(id, params, optlen);
470 		else
471 			ok = tcpssoall(ca_name, stack, state, params, optlen);
472 		free(params);
473 	} else
474 		ok = false;
475 	return (ok ? 0 : 1);
476 }
477