1 /* $OpenBSD: tcpdrop.c,v 1.4 2004/05/22 23:55:22 deraadt Exp $ */ 2 3 /*- 4 * Copyright (c) 2009 Juli Mallett <jmallett@FreeBSD.org> 5 * Copyright (c) 2004 Markus Friedl <markus@openbsd.org> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <sys/param.h> 21 #include <sys/types.h> 22 #include <sys/socket.h> 23 #include <sys/socketvar.h> 24 #include <sys/sysctl.h> 25 26 #include <netinet/in.h> 27 #include <netinet/in_pcb.h> 28 #define TCPSTATES 29 #include <netinet/tcp_fsm.h> 30 #include <netinet/tcp_var.h> 31 32 #include <err.h> 33 #include <netdb.h> 34 #include <stdbool.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <unistd.h> 39 40 #define TCPDROP_FOREIGN 0 41 #define TCPDROP_LOCAL 1 42 43 #define SW_TLS 0 44 #define IFNET_TLS 1 45 46 struct host_service { 47 char hs_host[NI_MAXHOST]; 48 char hs_service[NI_MAXSERV]; 49 }; 50 51 static bool tcpswitch_list_commands = false; 52 53 static char *findport(const char *); 54 static struct xinpgen *getxpcblist(const char *); 55 static void sockinfo(const struct sockaddr *, struct host_service *); 56 static bool tcpswitch(const struct sockaddr *, const struct sockaddr *, int); 57 static bool tcpswitchall(const char *, int); 58 static bool tcpswitchbyname(const char *, const char *, const char *, 59 const char *, int); 60 static bool tcpswitchconn(const struct in_conninfo *, int); 61 static void usage(void) __dead2; 62 63 /* 64 * Switch a tcp connection. 65 */ 66 int 67 main(int argc, char *argv[]) 68 { 69 char stack[TCP_FUNCTION_NAME_LEN_MAX]; 70 char *lport, *fport; 71 bool switchall, switchallstack; 72 int ch, mode; 73 74 switchall = false; 75 switchallstack = false; 76 stack[0] = '\0'; 77 mode = SW_TLS; 78 79 while ((ch = getopt(argc, argv, "ailS:s")) != -1) { 80 switch (ch) { 81 case 'a': 82 switchall = true; 83 break; 84 case 'i': 85 mode = IFNET_TLS; 86 break; 87 case 'l': 88 tcpswitch_list_commands = true; 89 break; 90 case 'S': 91 switchallstack = true; 92 strlcpy(stack, optarg, sizeof(stack)); 93 break; 94 case 's': 95 mode = SW_TLS; 96 break; 97 default: 98 usage(); 99 } 100 } 101 argc -= optind; 102 argv += optind; 103 104 if (switchall && switchallstack) 105 usage(); 106 if (switchall || switchallstack) { 107 if (argc != 0) 108 usage(); 109 if (!tcpswitchall(stack, mode)) 110 exit(1); 111 exit(0); 112 } 113 114 if ((argc != 2 && argc != 4) || tcpswitch_list_commands) 115 usage(); 116 117 if (argc == 2) { 118 lport = findport(argv[0]); 119 fport = findport(argv[1]); 120 if (lport == NULL || lport[1] == '\0' || fport == NULL || 121 fport[1] == '\0') 122 usage(); 123 *lport++ = '\0'; 124 *fport++ = '\0'; 125 if (!tcpswitchbyname(argv[0], lport, argv[1], fport, mode)) 126 exit(1); 127 } else if (!tcpswitchbyname(argv[0], argv[1], argv[2], argv[3], mode)) 128 exit(1); 129 130 exit(0); 131 } 132 133 static char * 134 findport(const char *arg) 135 { 136 char *dot, *colon; 137 138 /* A strrspn() or strrpbrk() would be nice. */ 139 dot = strrchr(arg, '.'); 140 colon = strrchr(arg, ':'); 141 if (dot == NULL) 142 return (colon); 143 if (colon == NULL) 144 return (dot); 145 if (dot < colon) 146 return (colon); 147 else 148 return (dot); 149 } 150 151 static struct xinpgen * 152 getxpcblist(const char *name) 153 { 154 struct xinpgen *xinp; 155 size_t len; 156 int rv; 157 158 len = 0; 159 rv = sysctlbyname(name, NULL, &len, NULL, 0); 160 if (rv == -1) 161 err(1, "sysctlbyname %s", name); 162 163 if (len == 0) 164 errx(1, "%s is empty", name); 165 166 xinp = malloc(len); 167 if (xinp == NULL) 168 errx(1, "malloc failed"); 169 170 rv = sysctlbyname(name, xinp, &len, NULL, 0); 171 if (rv == -1) 172 err(1, "sysctlbyname %s", name); 173 174 return (xinp); 175 } 176 177 static void 178 sockinfo(const struct sockaddr *sa, struct host_service *hs) 179 { 180 static const int flags = NI_NUMERICHOST | NI_NUMERICSERV; 181 int rv; 182 183 rv = getnameinfo(sa, sa->sa_len, hs->hs_host, sizeof hs->hs_host, 184 hs->hs_service, sizeof hs->hs_service, flags); 185 if (rv == -1) 186 err(1, "getnameinfo"); 187 } 188 189 static bool 190 tcpswitch(const struct sockaddr *lsa, const struct sockaddr *fsa, int mode) 191 { 192 struct host_service local, foreign; 193 struct sockaddr_storage addrs[2]; 194 int rv; 195 196 memcpy(&addrs[TCPDROP_FOREIGN], fsa, fsa->sa_len); 197 memcpy(&addrs[TCPDROP_LOCAL], lsa, lsa->sa_len); 198 199 sockinfo(lsa, &local); 200 sockinfo(fsa, &foreign); 201 202 if (tcpswitch_list_commands) { 203 printf("switch_tls %s %s %s %s %s\n", 204 mode == SW_TLS ? "-s" : "-i", 205 local.hs_host, local.hs_service, 206 foreign.hs_host, foreign.hs_service); 207 return (true); 208 } 209 210 rv = sysctlbyname(mode == SW_TLS ? "net.inet.tcp.switch_to_sw_tls" : 211 "net.inet.tcp.switch_to_ifnet_tls", NULL, NULL, &addrs, 212 sizeof addrs); 213 if (rv == -1) { 214 warn("%s %s %s %s", local.hs_host, local.hs_service, 215 foreign.hs_host, foreign.hs_service); 216 return (false); 217 } 218 printf("%s %s %s %s: switched\n", local.hs_host, local.hs_service, 219 foreign.hs_host, foreign.hs_service); 220 return (true); 221 } 222 223 static bool 224 tcpswitchall(const char *stack, int mode) 225 { 226 struct xinpgen *head, *xinp; 227 struct xtcpcb *xtp; 228 struct xinpcb *xip; 229 bool ok; 230 231 ok = true; 232 233 head = getxpcblist("net.inet.tcp.pcblist"); 234 235 #define XINP_NEXT(xinp) \ 236 ((struct xinpgen *)(uintptr_t)((uintptr_t)(xinp) + (xinp)->xig_len)) 237 238 for (xinp = XINP_NEXT(head); xinp->xig_len > sizeof *xinp; 239 xinp = XINP_NEXT(xinp)) { 240 xtp = (struct xtcpcb *)xinp; 241 xip = &xtp->xt_inp; 242 243 /* 244 * XXX 245 * Check protocol, support just v4 or v6, etc. 246 */ 247 248 /* Ignore PCBs which were freed during copyout. */ 249 if (xip->inp_gencnt > head->xig_gen) 250 continue; 251 252 /* Skip listening sockets. */ 253 if (xtp->t_state == TCPS_LISTEN) 254 continue; 255 256 /* If requested, skip sockets not having the requested stack. */ 257 if (stack[0] != '\0' && 258 strncmp(xtp->xt_stack, stack, TCP_FUNCTION_NAME_LEN_MAX)) 259 continue; 260 261 if (!tcpswitchconn(&xip->inp_inc, mode)) 262 ok = false; 263 } 264 free(head); 265 266 return (ok); 267 } 268 269 static bool 270 tcpswitchbyname(const char *lhost, const char *lport, const char *fhost, 271 const char *fport, int mode) 272 { 273 static const struct addrinfo hints = { 274 /* 275 * Look for streams in all domains. 276 */ 277 .ai_family = AF_UNSPEC, 278 .ai_socktype = SOCK_STREAM, 279 }; 280 struct addrinfo *ail, *local, *aif, *foreign; 281 int error; 282 bool ok, infamily; 283 284 error = getaddrinfo(lhost, lport, &hints, &local); 285 if (error != 0) 286 errx(1, "getaddrinfo: %s port %s: %s", lhost, lport, 287 gai_strerror(error)); 288 289 error = getaddrinfo(fhost, fport, &hints, &foreign); 290 if (error != 0) { 291 freeaddrinfo(local); /* XXX gratuitous */ 292 errx(1, "getaddrinfo: %s port %s: %s", fhost, fport, 293 gai_strerror(error)); 294 } 295 296 ok = true; 297 infamily = false; 298 299 /* 300 * Try every combination of local and foreign address pairs. 301 */ 302 for (ail = local; ail != NULL; ail = ail->ai_next) { 303 for (aif = foreign; aif != NULL; aif = aif->ai_next) { 304 if (ail->ai_family != aif->ai_family) 305 continue; 306 infamily = true; 307 if (!tcpswitch(ail->ai_addr, aif->ai_addr, mode)) 308 ok = false; 309 } 310 } 311 312 if (!infamily) { 313 warnx("%s %s %s %s: different address families", lhost, lport, 314 fhost, fport); 315 ok = false; 316 } 317 318 freeaddrinfo(local); 319 freeaddrinfo(foreign); 320 321 return (ok); 322 } 323 324 static bool 325 tcpswitchconn(const struct in_conninfo *inc, int mode) 326 { 327 struct sockaddr *local, *foreign; 328 struct sockaddr_in6 sin6[2]; 329 struct sockaddr_in sin4[2]; 330 331 if ((inc->inc_flags & INC_ISIPV6) != 0) { 332 memset(sin6, 0, sizeof sin6); 333 334 sin6[TCPDROP_LOCAL].sin6_len = sizeof sin6[TCPDROP_LOCAL]; 335 sin6[TCPDROP_LOCAL].sin6_family = AF_INET6; 336 sin6[TCPDROP_LOCAL].sin6_port = inc->inc_lport; 337 memcpy(&sin6[TCPDROP_LOCAL].sin6_addr, &inc->inc6_laddr, 338 sizeof inc->inc6_laddr); 339 local = (struct sockaddr *)&sin6[TCPDROP_LOCAL]; 340 341 sin6[TCPDROP_FOREIGN].sin6_len = sizeof sin6[TCPDROP_FOREIGN]; 342 sin6[TCPDROP_FOREIGN].sin6_family = AF_INET6; 343 sin6[TCPDROP_FOREIGN].sin6_port = inc->inc_fport; 344 memcpy(&sin6[TCPDROP_FOREIGN].sin6_addr, &inc->inc6_faddr, 345 sizeof inc->inc6_faddr); 346 foreign = (struct sockaddr *)&sin6[TCPDROP_FOREIGN]; 347 } else { 348 memset(sin4, 0, sizeof sin4); 349 350 sin4[TCPDROP_LOCAL].sin_len = sizeof sin4[TCPDROP_LOCAL]; 351 sin4[TCPDROP_LOCAL].sin_family = AF_INET; 352 sin4[TCPDROP_LOCAL].sin_port = inc->inc_lport; 353 memcpy(&sin4[TCPDROP_LOCAL].sin_addr, &inc->inc_laddr, 354 sizeof inc->inc_laddr); 355 local = (struct sockaddr *)&sin4[TCPDROP_LOCAL]; 356 357 sin4[TCPDROP_FOREIGN].sin_len = sizeof sin4[TCPDROP_FOREIGN]; 358 sin4[TCPDROP_FOREIGN].sin_family = AF_INET; 359 sin4[TCPDROP_FOREIGN].sin_port = inc->inc_fport; 360 memcpy(&sin4[TCPDROP_FOREIGN].sin_addr, &inc->inc_faddr, 361 sizeof inc->inc_faddr); 362 foreign = (struct sockaddr *)&sin4[TCPDROP_FOREIGN]; 363 } 364 365 return (tcpswitch(local, foreign, mode)); 366 } 367 368 static void 369 usage(void) 370 { 371 fprintf(stderr, 372 "usage: switch_tls [-i | -s] local-address local-port foreign-address foreign-port\n" 373 " switch_tls [-i | -s] local-address:local-port foreign-address:foreign-port\n" 374 " switch_tls [-i | -s] local-address.local-port foreign-address.foreign-port\n" 375 " switch_tls [-l | -i | -s] -a\n" 376 " switch_tls [-l | -i | -s] -S stack\n"); 377 exit(1); 378 } 379