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