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/cdefs.h> 21 __FBSDID("$FreeBSD$"); 22 23 #include <sys/param.h> 24 #include <sys/types.h> 25 #include <sys/socket.h> 26 #include <sys/socketvar.h> 27 #include <sys/sysctl.h> 28 29 #include <netinet/in.h> 30 #include <netinet/in_pcb.h> 31 #define TCPSTATES 32 #include <netinet/tcp_fsm.h> 33 #include <netinet/tcp_var.h> 34 35 #include <err.h> 36 #include <netdb.h> 37 #include <stdbool.h> 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <string.h> 41 #include <unistd.h> 42 43 #define TCPDROP_FOREIGN 0 44 #define TCPDROP_LOCAL 1 45 46 struct host_service { 47 char hs_host[NI_MAXHOST]; 48 char hs_service[NI_MAXSERV]; 49 }; 50 51 static bool tcpdrop_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 tcpdrop(const struct sockaddr *, const struct sockaddr *); 57 static bool tcpdropall(const char *, int); 58 static bool tcpdropbyname(const char *, const char *, const char *, 59 const char *); 60 static bool tcpdropconn(const struct in_conninfo *); 61 static void usage(void); 62 63 /* 64 * Drop 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 dropall, dropallstack; 72 int ch, state; 73 74 dropall = false; 75 dropallstack = false; 76 memset(stack, 0, TCP_FUNCTION_NAME_LEN_MAX); 77 state = -1; 78 79 while ((ch = getopt(argc, argv, "alS:s:")) != -1) { 80 switch (ch) { 81 case 'a': 82 dropall = true; 83 break; 84 case 'l': 85 tcpdrop_list_commands = true; 86 break; 87 case 'S': 88 dropallstack = true; 89 strncpy(stack, optarg, TCP_FUNCTION_NAME_LEN_MAX); 90 break; 91 case 's': 92 dropallstack = true; 93 for (state = 0; state < TCP_NSTATES; state++) { 94 if (strcmp(tcpstates[state], optarg) == 0) 95 break; 96 } 97 break; 98 default: 99 usage(); 100 } 101 } 102 argc -= optind; 103 argv += optind; 104 105 if (state == TCP_NSTATES || 106 state == TCPS_CLOSED || 107 state == TCPS_LISTEN) 108 usage(); 109 if (dropall && dropallstack) 110 usage(); 111 if (dropall || dropallstack) { 112 if (argc != 0) 113 usage(); 114 if (!tcpdropall(stack, state)) 115 exit(1); 116 exit(0); 117 } 118 119 if ((argc != 2 && argc != 4) || tcpdrop_list_commands) 120 usage(); 121 122 if (argc == 2) { 123 lport = findport(argv[0]); 124 fport = findport(argv[1]); 125 if (lport == NULL || lport[1] == '\0' || fport == NULL || 126 fport[1] == '\0') 127 usage(); 128 *lport++ = '\0'; 129 *fport++ = '\0'; 130 if (!tcpdropbyname(argv[0], lport, argv[1], fport)) 131 exit(1); 132 } else if (!tcpdropbyname(argv[0], argv[1], argv[2], argv[3])) 133 exit(1); 134 135 exit(0); 136 } 137 138 static char * 139 findport(const char *arg) 140 { 141 char *dot, *colon; 142 143 /* A strrspn() or strrpbrk() would be nice. */ 144 dot = strrchr(arg, '.'); 145 colon = strrchr(arg, ':'); 146 if (dot == NULL) 147 return (colon); 148 if (colon == NULL) 149 return (dot); 150 if (dot < colon) 151 return (colon); 152 else 153 return (dot); 154 } 155 156 static struct xinpgen * 157 getxpcblist(const char *name) 158 { 159 struct xinpgen *xinp; 160 size_t len; 161 int rv; 162 163 len = 0; 164 rv = sysctlbyname(name, NULL, &len, NULL, 0); 165 if (rv == -1) 166 err(1, "sysctlbyname %s", name); 167 168 if (len == 0) 169 errx(1, "%s is empty", name); 170 171 xinp = malloc(len); 172 if (xinp == NULL) 173 errx(1, "malloc failed"); 174 175 rv = sysctlbyname(name, xinp, &len, NULL, 0); 176 if (rv == -1) 177 err(1, "sysctlbyname %s", name); 178 179 return (xinp); 180 } 181 182 static void 183 sockinfo(const struct sockaddr *sa, struct host_service *hs) 184 { 185 static const int flags = NI_NUMERICHOST | NI_NUMERICSERV; 186 int rv; 187 188 rv = getnameinfo(sa, sa->sa_len, hs->hs_host, sizeof hs->hs_host, 189 hs->hs_service, sizeof hs->hs_service, flags); 190 if (rv == -1) 191 err(1, "getnameinfo"); 192 } 193 194 static bool 195 tcpdrop(const struct sockaddr *lsa, const struct sockaddr *fsa) 196 { 197 struct host_service local, foreign; 198 struct sockaddr_storage addrs[2]; 199 int rv; 200 201 memcpy(&addrs[TCPDROP_FOREIGN], fsa, fsa->sa_len); 202 memcpy(&addrs[TCPDROP_LOCAL], lsa, lsa->sa_len); 203 204 sockinfo(lsa, &local); 205 sockinfo(fsa, &foreign); 206 207 if (tcpdrop_list_commands) { 208 printf("tcpdrop %s %s %s %s\n", local.hs_host, local.hs_service, 209 foreign.hs_host, foreign.hs_service); 210 return (true); 211 } 212 213 rv = sysctlbyname("net.inet.tcp.drop", NULL, NULL, &addrs, 214 sizeof addrs); 215 if (rv == -1) { 216 warn("%s %s %s %s", local.hs_host, local.hs_service, 217 foreign.hs_host, foreign.hs_service); 218 return (false); 219 } 220 printf("%s %s %s %s: dropped\n", local.hs_host, local.hs_service, 221 foreign.hs_host, foreign.hs_service); 222 return (true); 223 } 224 225 static bool 226 tcpdropall(const char *stack, int state) 227 { 228 struct xinpgen *head, *xinp; 229 struct xtcpcb *xtp; 230 struct xinpcb *xip; 231 bool ok; 232 233 ok = true; 234 235 head = getxpcblist("net.inet.tcp.pcblist"); 236 237 #define XINP_NEXT(xinp) \ 238 ((struct xinpgen *)((uintptr_t)(xinp) + (xinp)->xig_len)) 239 240 for (xinp = XINP_NEXT(head); xinp->xig_len > sizeof *xinp; 241 xinp = XINP_NEXT(xinp)) { 242 xtp = (struct xtcpcb *)xinp; 243 xip = &xtp->xt_inp; 244 245 /* 246 * XXX 247 * Check protocol, support just v4 or v6, etc. 248 */ 249 250 /* Ignore PCBs which were freed during copyout. */ 251 if (xip->inp_gencnt > head->xig_gen) 252 continue; 253 254 /* Skip listening sockets. */ 255 if (xtp->t_state == TCPS_LISTEN) 256 continue; 257 258 /* If requested, skip sockets not having the requested state. */ 259 if ((state != -1) && (xtp->t_state != state)) 260 continue; 261 262 /* If requested, skip sockets not having the requested stack. */ 263 if (strnlen(stack, TCP_FUNCTION_NAME_LEN_MAX) > 0 && 264 strncmp(xtp->xt_stack, stack, TCP_FUNCTION_NAME_LEN_MAX)) 265 continue; 266 267 if (!tcpdropconn(&xip->inp_inc)) 268 ok = false; 269 } 270 free(head); 271 272 return (ok); 273 } 274 275 static bool 276 tcpdropbyname(const char *lhost, const char *lport, const char *fhost, 277 const char *fport) 278 { 279 static const struct addrinfo hints = { 280 /* 281 * Look for streams in all domains. 282 */ 283 .ai_family = AF_UNSPEC, 284 .ai_socktype = SOCK_STREAM, 285 }; 286 struct addrinfo *ail, *local, *aif, *foreign; 287 int error; 288 bool ok, infamily; 289 290 error = getaddrinfo(lhost, lport, &hints, &local); 291 if (error != 0) 292 errx(1, "getaddrinfo: %s port %s: %s", lhost, lport, 293 gai_strerror(error)); 294 295 error = getaddrinfo(fhost, fport, &hints, &foreign); 296 if (error != 0) { 297 freeaddrinfo(local); /* XXX gratuitous */ 298 errx(1, "getaddrinfo: %s port %s: %s", fhost, fport, 299 gai_strerror(error)); 300 } 301 302 ok = true; 303 infamily = false; 304 305 /* 306 * Try every combination of local and foreign address pairs. 307 */ 308 for (ail = local; ail != NULL; ail = ail->ai_next) { 309 for (aif = foreign; aif != NULL; aif = aif->ai_next) { 310 if (ail->ai_family != aif->ai_family) 311 continue; 312 infamily = true; 313 if (!tcpdrop(ail->ai_addr, aif->ai_addr)) 314 ok = false; 315 } 316 } 317 318 if (!infamily) { 319 warnx("%s %s %s %s: different address families", lhost, lport, 320 fhost, fport); 321 ok = false; 322 } 323 324 freeaddrinfo(local); 325 freeaddrinfo(foreign); 326 327 return (ok); 328 } 329 330 static bool 331 tcpdropconn(const struct in_conninfo *inc) 332 { 333 struct sockaddr *local, *foreign; 334 struct sockaddr_in6 sin6[2]; 335 struct sockaddr_in sin4[2]; 336 337 if ((inc->inc_flags & INC_ISIPV6) != 0) { 338 memset(sin6, 0, sizeof sin6); 339 340 sin6[TCPDROP_LOCAL].sin6_len = sizeof sin6[TCPDROP_LOCAL]; 341 sin6[TCPDROP_LOCAL].sin6_family = AF_INET6; 342 sin6[TCPDROP_LOCAL].sin6_port = inc->inc_lport; 343 memcpy(&sin6[TCPDROP_LOCAL].sin6_addr, &inc->inc6_laddr, 344 sizeof inc->inc6_laddr); 345 local = (struct sockaddr *)&sin6[TCPDROP_LOCAL]; 346 347 sin6[TCPDROP_FOREIGN].sin6_len = sizeof sin6[TCPDROP_FOREIGN]; 348 sin6[TCPDROP_FOREIGN].sin6_family = AF_INET6; 349 sin6[TCPDROP_FOREIGN].sin6_port = inc->inc_fport; 350 memcpy(&sin6[TCPDROP_FOREIGN].sin6_addr, &inc->inc6_faddr, 351 sizeof inc->inc6_faddr); 352 foreign = (struct sockaddr *)&sin6[TCPDROP_FOREIGN]; 353 } else { 354 memset(sin4, 0, sizeof sin4); 355 356 sin4[TCPDROP_LOCAL].sin_len = sizeof sin4[TCPDROP_LOCAL]; 357 sin4[TCPDROP_LOCAL].sin_family = AF_INET; 358 sin4[TCPDROP_LOCAL].sin_port = inc->inc_lport; 359 memcpy(&sin4[TCPDROP_LOCAL].sin_addr, &inc->inc_laddr, 360 sizeof inc->inc_laddr); 361 local = (struct sockaddr *)&sin4[TCPDROP_LOCAL]; 362 363 sin4[TCPDROP_FOREIGN].sin_len = sizeof sin4[TCPDROP_FOREIGN]; 364 sin4[TCPDROP_FOREIGN].sin_family = AF_INET; 365 sin4[TCPDROP_FOREIGN].sin_port = inc->inc_fport; 366 memcpy(&sin4[TCPDROP_FOREIGN].sin_addr, &inc->inc_faddr, 367 sizeof inc->inc_faddr); 368 foreign = (struct sockaddr *)&sin4[TCPDROP_FOREIGN]; 369 } 370 371 return (tcpdrop(local, foreign)); 372 } 373 374 static void 375 usage(void) 376 { 377 fprintf(stderr, 378 "usage: tcpdrop local-address local-port foreign-address foreign-port\n" 379 " tcpdrop local-address:local-port foreign-address:foreign-port\n" 380 " tcpdrop local-address.local-port foreign-address.foreign-port\n" 381 " tcpdrop [-l] -a\n" 382 " tcpdrop [-l] -S stack\n" 383 " tcpdrop [-l] -s state\n" 384 " tcpdrop [-l] -S stack -s state\n"); 385 exit(1); 386 } 387