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 *
getxpcblist(const char * name)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
tcpsso(uint64_t id,struct sockopt_parameters * params,size_t optlen)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
tcpssoall(const char * ca_name,const char * stack,int state,struct sockopt_parameters * params,size_t optlen)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 /* 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
144 struct so_level {
145 int level;
146 const char *name;
147 };
148
149 #define level_entry(level) { level, #level }
150
151 static 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
159 struct 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
170 static 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_DELACK), /* int */
211 tcp_entry(TCP_REC_ABC_VAL), /* int */
212 tcp_entry(TCP_USE_CMP_ACKS), /* int */
213 tcp_entry(TCP_SHARED_CWND_TIME_LIMIT), /* int */
214 tcp_entry(TCP_SHARED_CWND_ENABLE), /* int */
215 tcp_entry(TCP_DATA_AFTER_CLOSE), /* int */
216 tcp_entry(TCP_DEFER_OPTIONS), /* int */
217 tcp_entry(TCP_TIMELY_DYN_ADJ), /* int */
218 tcp_entry(TCP_RACK_TLP_REDUCE), /* int */
219 tcp_entry(TCP_RACK_PACE_ALWAYS), /* int */
220 tcp_entry(TCP_RACK_PACE_MAX_SEG), /* int */
221 tcp_entry(TCP_RACK_FORCE_MSEG), /* int */
222 tcp_entry(TCP_RACK_PACE_RATE_CA), /* int */
223 tcp_entry(TCP_RACK_PACE_RATE_SS), /* int */
224 tcp_entry(TCP_RACK_PACE_RATE_REC), /* int */
225 tcp_entry(TCP_RACK_GP_INCREASE_CA), /* int */
226 tcp_entry(TCP_RACK_GP_INCREASE_SS), /* int */
227 tcp_entry(TCP_RACK_GP_INCREASE_REC), /* int */
228 tcp_entry(TCP_RACK_RR_CONF), /* int */
229 tcp_entry(TCP_RACK_PRR_SENDALOT), /* int */
230 tcp_entry(TCP_RACK_MIN_TO), /* int */
231 tcp_entry(TCP_RACK_EARLY_SEG), /* int */
232 tcp_entry(TCP_RACK_REORD_THRESH), /* int */
233 tcp_entry(TCP_RACK_REORD_FADE), /* int */
234 tcp_entry(TCP_RACK_TLP_THRESH), /* int */
235 tcp_entry(TCP_RACK_PKT_DELAY), /* int */
236 tcp_entry(TCP_RACK_TLP_USE), /* int */
237 tcp_entry(TCP_RACK_DO_DETECTION), /* int */
238 tcp_entry(TCP_RACK_NONRXT_CFG_RATE), /* int */
239 tcp_entry(TCP_RACK_MBUF_QUEUE), /* int */
240 tcp_entry(TCP_RACK_NO_PUSH_AT_MAX), /* int */
241 tcp_entry(TCP_RACK_PACE_TO_FILL), /* int */
242 tcp_entry(TCP_RACK_PROFILE), /* int */
243 tcp_entry(TCP_RACK_ABC_VAL), /* int */
244 tcp_entry(TCP_RACK_MEASURE_CNT), /* int */
245 tcp_entry(TCP_RACK_DSACK_OPT), /* int */
246 tcp_entry(TCP_RACK_PACING_BETA), /* int */
247 tcp_entry(TCP_RACK_PACING_BETA_ECN), /* int */
248 tcp_entry(TCP_RACK_TIMER_SLOP), /* int */
249 tcp_entry(TCP_RACK_ENABLE_HYSTART), /* int */
250 tcp_entry(TCP_BBR_RACK_RTT_USE), /* int */
251 tcp_entry(TCP_BBR_USE_RACK_RR), /* int */
252 tcp_entry(TCP_BBR_HDWR_PACE), /* int */
253 tcp_entry(TCP_BBR_RACK_INIT_RATE), /* int */
254 tcp_entry(TCP_BBR_IWINTSO), /* int */
255 tcp_entry(TCP_BBR_ALGORITHM), /* int */
256 tcp_entry(TCP_BBR_TSLIMITS), /* int */
257 tcp_entry(TCP_BBR_STARTUP_PG), /* int */
258 tcp_entry(TCP_BBR_DRAIN_PG), /* int */
259 tcp_entry(TCP_BBR_PROBE_RTT_INT), /* int */
260 tcp_entry(TCP_BBR_PROBE_RTT_GAIN), /* int */
261 tcp_entry(TCP_BBR_PROBE_RTT_LEN), /* int */
262 tcp_entry(TCP_BBR_STARTUP_LOSS_EXIT), /* int */
263 tcp_entry(TCP_BBR_USEDEL_RATE), /* int */
264 tcp_entry(TCP_BBR_MIN_RTO), /* int */
265 tcp_entry(TCP_BBR_MAX_RTO), /* int */
266 tcp_entry(TCP_BBR_PACE_PER_SEC), /* int */
267 tcp_entry(TCP_BBR_PACE_DEL_TAR), /* int */
268 tcp_entry(TCP_BBR_SEND_IWND_IN_TSO), /* int */
269 tcp_entry(TCP_BBR_EXTRA_STATE), /* int */
270 tcp_entry(TCP_BBR_UTTER_MAX_TSO), /* int */
271 tcp_entry(TCP_BBR_MIN_TOPACEOUT), /* int */
272 tcp_entry(TCP_BBR_FLOOR_MIN_TSO), /* int */
273 tcp_entry(TCP_BBR_TSTMP_RAISES), /* int */
274 tcp_entry(TCP_BBR_USE_RACK_CHEAT), /* int */
275 tcp_entry(TCP_BBR_PACE_SEG_MAX), /* int */
276 tcp_entry(TCP_BBR_PACE_SEG_MIN), /* int */
277 tcp_entry(TCP_BBR_PACE_CROSS), /* int */
278 tcp_entry(TCP_BBR_PACE_OH), /* int */
279 tcp_entry(TCP_BBR_TMR_PACE_OH), /* int */
280 tcp_entry(TCP_BBR_RETRAN_WTSO), /* int */
281 {0, 0, NULL}
282 };
283
284 static struct sockopt_parameters *
create_parameters(char * level_str,char * optname_str,char * optval_str,size_t * optlen)285 create_parameters(char *level_str, char *optname_str, char *optval_str,
286 size_t *optlen)
287 {
288 long long arg;
289 int i, level, optname, optval_int;
290 struct sockopt_parameters *params;
291 char *end;
292 bool optval_is_int;
293
294 /* Determine level, use IPPROTO_TCP as default. */
295 if (level_str == NULL)
296 level = IPPROTO_TCP;
297 else {
298 arg = strtoll(level_str, &end, 0);
299 if (*end != '\0') {
300 for (i = 0; so_levels[i].name != NULL; i++)
301 if (strcmp(level_str, so_levels[i].name) == 0) {
302 level = so_levels[i].level;
303 break;
304 }
305 if (so_levels[i].name == NULL)
306 errx(1, "unsupported level %s", optname_str);
307 } else {
308 if (arg < 0)
309 errx(1, "level negative %s", optname_str);
310 else if (arg > INT_MAX)
311 errx(1, "level too large %s", optname_str);
312 else
313 level = (int)arg;
314 }
315 }
316 /* Determine option name. */
317 if (optname_str == NULL || *optname_str == '\0')
318 return (NULL);
319 arg = strtoll(optname_str, &end, 0);
320 if (*end != '\0') {
321 for (i = 0; so_names[i].name != NULL; i++)
322 if (strcmp(optname_str, so_names[i].name) == 0) {
323 level = so_names[i].level;
324 optname = so_names[i].value;
325 break;
326 }
327 if (so_names[i].name == NULL)
328 errx(1, "unsupported option name %s", optname_str);
329 } else {
330 if (arg < 0)
331 errx(1, "option name negative %s", optname_str);
332 else if (arg > INT_MAX)
333 errx(1, "option name too large %s", optname_str);
334 else
335 optname = (int)arg;
336 }
337 /*
338 * Determine option value. Use int, if can be parsed as an int,
339 * else use a char *.
340 */
341 if (optval_str == NULL || *optval_str == '\0')
342 return (NULL);
343 arg = strtol(optval_str, &end, 0);
344 optval_is_int = (*end == '\0');
345 if (optval_is_int) {
346 if (arg < INT_MIN)
347 errx(1, "option value too small %s", optval_str);
348 else if (arg > INT_MAX)
349 errx(1, "option value too large %s", optval_str);
350 else
351 optval_int = (int)arg;
352 }
353 switch (optname) {
354 case TCP_FUNCTION_BLK:
355 *optlen = sizeof(struct tcp_function_set);
356 break;
357 default:
358 if (optval_is_int)
359 *optlen = sizeof(int);
360 else
361 *optlen = strlen(optval_str) + 1;
362 break;
363 }
364 /* Fill socket option parameters. */
365 params = malloc(sizeof(struct sockopt_parameters) + *optlen);
366 if (params == NULL)
367 return (NULL);
368 memset(params, 0, sizeof(struct sockopt_parameters) + *optlen);
369 params->sop_level = level;
370 params->sop_optname = optname;
371 switch (optname) {
372 case TCP_FUNCTION_BLK:
373 strlcpy(params->sop_optval, optval_str,
374 TCP_FUNCTION_NAME_LEN_MAX);
375 break;
376 default:
377 if (optval_is_int)
378 memcpy(params->sop_optval, &optval_int, *optlen);
379 else
380 memcpy(params->sop_optval, optval_str, *optlen);
381 }
382 return (params);
383 }
384
385 static void
usage(void)386 usage(void)
387 {
388 fprintf(stderr,
389 "usage: tcpsso -i id [level] opt-name opt-value\n"
390 " tcpsso -a [level] opt-name opt-value\n"
391 " tcpsso -C cc-algo [-S stack] [-s state] [level] opt-name opt-value\n"
392 " tcpsso [-C cc-algo] -S stack [-s state] [level] opt-name opt-value\n"
393 " tcpsso [-C cc-algo] [-S stack] -s state [level] opt-name opt-value\n");
394 exit(1);
395 }
396
397 int
main(int argc,char * argv[])398 main(int argc, char *argv[])
399 {
400 struct sockopt_parameters *params;
401 uint64_t id;
402 size_t optlen;
403 int ch, state;
404 char stack[TCP_FUNCTION_NAME_LEN_MAX];
405 char ca_name[TCP_CA_NAME_MAX];
406 bool ok, apply_all, apply_subset, apply_specific;
407
408 apply_all = false;
409 apply_subset = false;
410 apply_specific = false;
411 ca_name[0] = '\0';
412 stack[0] = '\0';
413 state = -1;
414 id = 0;
415
416 while ((ch = getopt(argc, argv, "aC:i:S:s:")) != -1) {
417 switch (ch) {
418 case 'a':
419 apply_all = true;
420 break;
421 case 'C':
422 apply_subset = true;
423 strlcpy(ca_name, optarg, sizeof(ca_name));
424 break;
425 case 'i':
426 apply_specific = true;
427 id = strtoull(optarg, NULL, 0);
428 break;
429 case 'S':
430 apply_subset = true;
431 strlcpy(stack, optarg, sizeof(stack));
432 break;
433 case 's':
434 apply_subset = true;
435 for (state = 0; state < TCP_NSTATES; state++) {
436 if (strcmp(tcpstates[state], optarg) == 0)
437 break;
438 }
439 break;
440 default:
441 usage();
442 }
443 }
444 argc -= optind;
445 argv += optind;
446 if ((state == TCP_NSTATES) ||
447 (argc < 2) || (argc > 3) ||
448 (apply_all && apply_subset) ||
449 (apply_all && apply_specific) ||
450 (apply_subset && apply_specific) ||
451 !(apply_all || apply_subset || apply_specific))
452 usage();
453 if (argc == 2)
454 params = create_parameters(NULL, argv[0], argv[1], &optlen);
455 else
456 params = create_parameters(argv[0], argv[1], argv[2], &optlen);
457 if (params != NULL) {
458 if (apply_specific)
459 ok = tcpsso(id, params, optlen);
460 else
461 ok = tcpssoall(ca_name, stack, state, params, optlen);
462 free(params);
463 } else
464 ok = false;
465 return (ok ? 0 : 1);
466 }
467