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 46struct host_service { 47 char hs_host[NI_MAXHOST]; 48 char hs_service[NI_MAXSERV]; 49}; 50 51static bool tcpswitch_list_commands = false; 52 53static char *findport(const char *); 54static struct xinpgen *getxpcblist(const char *); 55static void sockinfo(const struct sockaddr *, struct host_service *); 56static bool tcpswitch(const struct sockaddr *, const struct sockaddr *, int); 57static bool tcpswitchall(const char *, int); 58static bool tcpswitchbyname(const char *, const char *, const char *, 59 const char *, int); 60static bool tcpswitchconn(const struct in_conninfo *, int); 61static void usage(void) __dead2; 62 63/* 64 * Switch a tcp connection. 65 */ 66int 67main(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 133static char * 134findport(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 151static struct xinpgen * 152getxpcblist(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 177static void 178sockinfo(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 189static bool 190tcpswitch(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 223static bool 224tcpswitchall(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 269static bool 270tcpswitchbyname(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 324static bool 325tcpswitchconn(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 368static void 369usage(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