diff options
Diffstat (limited to 'usr.sbin/tcpdrop')
| -rw-r--r-- | usr.sbin/tcpdrop/Makefile | 6 | ||||
| -rw-r--r-- | usr.sbin/tcpdrop/Makefile.depend | 15 | ||||
| -rw-r--r-- | usr.sbin/tcpdrop/tcpdrop.8 | 201 | ||||
| -rw-r--r-- | usr.sbin/tcpdrop/tcpdrop.c | 398 |
4 files changed, 620 insertions, 0 deletions
diff --git a/usr.sbin/tcpdrop/Makefile b/usr.sbin/tcpdrop/Makefile new file mode 100644 index 000000000000..58a9e05d46a5 --- /dev/null +++ b/usr.sbin/tcpdrop/Makefile @@ -0,0 +1,6 @@ +# $OpenBSD: Makefile,v 1.1 2004/04/26 19:51:20 markus Exp $ + +PROG= tcpdrop +MAN= tcpdrop.8 + +.include <bsd.prog.mk> diff --git a/usr.sbin/tcpdrop/Makefile.depend b/usr.sbin/tcpdrop/Makefile.depend new file mode 100644 index 000000000000..6ef78fac5cbf --- /dev/null +++ b/usr.sbin/tcpdrop/Makefile.depend @@ -0,0 +1,15 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/tcpdrop/tcpdrop.8 b/usr.sbin/tcpdrop/tcpdrop.8 new file mode 100644 index 000000000000..4b9c654ffb6f --- /dev/null +++ b/usr.sbin/tcpdrop/tcpdrop.8 @@ -0,0 +1,201 @@ +.\" $OpenBSD: tcpdrop.8,v 1.5 2004/05/24 13:57:31 jmc Exp $ +.\" +.\" Copyright (c) 2009 Juli Mallett <jmallett@FreeBSD.org> +.\" Copyright (c) 2004 Markus Friedl <markus@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd December 4, 2021 +.Dt TCPDROP 8 +.Os +.Sh NAME +.Nm tcpdrop +.Nd drop TCP connections +.Sh SYNOPSIS +.Nm tcpdrop +.Ar local-address +.Ar local-port +.Ar foreign-address +.Ar foreign-port +.Nm tcpdrop +.Op Fl l +.Fl a +.Nm tcpdrop +.Op Fl l +.Fl C Ar cc-algo +.Op Fl S Ar stack +.Op Fl s Ar state +.Nm tcpdrop +.Op Fl l +.Op Fl C Ar cc-algo +.Fl S Ar stack +.Op Fl s Ar state +.Nm tcpdrop +.Op Fl l +.Op Fl C Ar cc-algo +.Op Fl S Ar stack +.Fl s Ar state +.Sh DESCRIPTION +The +.Nm +command may be used to drop TCP connections from the command line. +.Pp +If +.Fl a +is specified then +.Nm +will attempt to drop all TCP connections. +.Pp +If +.Fl C Ar cc-algo +is specified then +.Nm +will attempt to drop all connections using the TCP congestion control algorithm +.Ar cc-algo . +.Pp +If +.Fl S Ar stack +is specified then +.Nm +will attempt to drop all connections using the TCP stack +.Ar stack . +.Pp +If +.Fl s Ar state +is specified then +.Nm +will attempt to drop all TCP connections being in the state +.Ar state . +.Ar state +is one of +.Dv SYN_SENT , +.Dv SYN_RCVD , +.Dv ESTABLISHED , +.Dv CLOSE_WAIT , +.Dv FIN_WAIT_1 , +.Dv CLOSING , +.Dv LAST_ACK , +.Dv FIN_WAIT_2 , or +.Dv TIME_WAIT . +.Pp +If multiple of +.Fl C Ar cc-algo , +.Fl S Ar stack , +and +.Fl s Ar state +are specified, +.Nm +will attempt to drop all TCP connections using the congestion control algorithm +.Ar cc-algo , +being in the state +.Ar state , +and using the TCP stack +.Ar stack , +if specified. +Since TCP connections in the +.Dv TIME_WAIT +state are not tied to any TCP stack, using the option +.Fl s Dv TIME_WAIT +in combination with the +.Fl S Ar stack +option results in +.Nm +not dropping any TCP connection. +.Pp +The +.Fl l +flag may be given in addition to the +.Fl a , +.Fl C , +.Fl S , +or +.Fl s +options to list the tcpdrop invocation to drop all corresponding TCP +connections one at a time. +.Pp +If none of the +.Fl a , +.Fl C , +.Fl S , +or +.Fl s +options are specified then only the connection between the given local +address +.Ar local-address , +port +.Ar local-port , +and the foreign address +.Ar foreign-address , +port +.Ar foreign-port , +will be dropped. +.Pp +Addresses and ports may be specified by name or numeric value. +Both IPv4 and IPv6 address formats are supported. +.Pp +The addresses and ports may be separated by periods or colons +instead of spaces. +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +If a connection to +.Xr httpd 8 +is causing congestion on a network link, one can drop the TCP session +in charge: +.Bd -literal -offset indent +# sockstat -c | grep httpd +www httpd 16525 3 tcp4 \e + 192.168.5.41:80 192.168.5.1:26747 +.Ed +.Pp +The following command will drop the connection: +.Bd -literal -offset indent +# tcpdrop 192.168.5.41 80 192.168.5.1 26747 +.Ed +.Pp +The following command will drop all connections but those to or from +port 22, the port used by +.Xr sshd 8 : +.Bd -literal -offset indent +# tcpdrop -l -a | grep -vw 22 | sh +.Ed +.Pp +To drop all TCP connections using the new-reno congestion control algorithm use: +.Bd -literal -offset indent +# tcpdrop -C new-reno +.Ed +.Pp +The following command will drop all connections using the TCP stack +rack: +.Bd -literal -offset indent +# tcpdrop -S rack +.Ed +.Pp +To drop all TCP connections in the LAST_ACK state use: +.Bd -literal -offset indent +# tcpdrop -s LAST_ACK +.Ed +.Pp +To drop all TCP connections using the congestion control algorithm new-reno and +the TCP stack rack and being in the LAST_ACK state use: +.Bd -literal -offset indent +# tcpdrop -C new-reno -S rack -s LAST_ACK +.Ed +.Sh SEE ALSO +.Xr netstat 1 , +.Xr sockstat 1 , +.Xr tcp 4 , +.Xr tcp_functions 9 +.Sh AUTHORS +.An Markus Friedl Aq Mt markus@openbsd.org +.An Juli Mallett Aq Mt jmallett@FreeBSD.org diff --git a/usr.sbin/tcpdrop/tcpdrop.c b/usr.sbin/tcpdrop/tcpdrop.c new file mode 100644 index 000000000000..6f6b94789df1 --- /dev/null +++ b/usr.sbin/tcpdrop/tcpdrop.c @@ -0,0 +1,398 @@ +/* $OpenBSD: tcpdrop.c,v 1.4 2004/05/22 23:55:22 deraadt Exp $ */ + +/*- + * Copyright (c) 2009 Juli Mallett <jmallett@FreeBSD.org> + * Copyright (c) 2004 Markus Friedl <markus@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/sysctl.h> + +#include <netinet/in.h> +#include <netinet/in_pcb.h> +#define TCPSTATES +#include <netinet/tcp_fsm.h> +#include <netinet/tcp_var.h> + +#include <err.h> +#include <netdb.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define TCPDROP_FOREIGN 0 +#define TCPDROP_LOCAL 1 + +struct host_service { + char hs_host[NI_MAXHOST]; + char hs_service[NI_MAXSERV]; +}; + +static bool tcpdrop_list_commands = false; + +static char *findport(const char *); +static struct xinpgen *getxpcblist(const char *); +static void sockinfo(const struct sockaddr *, struct host_service *); +static bool tcpdrop(const struct sockaddr *, const struct sockaddr *); +static bool tcpdropall(const char *, const char *, int); +static bool tcpdropbyname(const char *, const char *, const char *, + const char *); +static bool tcpdropconn(const struct in_conninfo *); +static void usage(void) __dead2; + +/* + * Drop a tcp connection. + */ +int +main(int argc, char *argv[]) +{ + char stack[TCP_FUNCTION_NAME_LEN_MAX]; + char ca_name[TCP_CA_NAME_MAX]; + char *lport, *fport; + bool dropall, dropspecific; + int ch, state; + + dropall = false; + dropspecific = false; + ca_name[0] = '\0'; + stack[0] = '\0'; + state = -1; + + while ((ch = getopt(argc, argv, "aC:lS:s:")) != -1) { + switch (ch) { + case 'a': + dropall = true; + break; + case 'C': + dropspecific = true; + strlcpy(ca_name, optarg, sizeof(ca_name)); + break; + case 'l': + tcpdrop_list_commands = true; + break; + case 'S': + dropspecific = true; + strlcpy(stack, optarg, sizeof(stack)); + break; + case 's': + dropspecific = true; + for (state = 0; state < TCP_NSTATES; state++) { + if (strcmp(tcpstates[state], optarg) == 0) + break; + } + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (state == TCP_NSTATES || + state == TCPS_CLOSED || + state == TCPS_LISTEN) + usage(); + if (dropall && dropspecific) + usage(); + if (dropall || dropspecific) { + if (argc != 0) + usage(); + if (!tcpdropall(ca_name, stack, state)) + exit(1); + exit(0); + } + + if ((argc != 2 && argc != 4) || tcpdrop_list_commands) + usage(); + + if (argc == 2) { + lport = findport(argv[0]); + fport = findport(argv[1]); + if (lport == NULL || lport[1] == '\0' || fport == NULL || + fport[1] == '\0') + usage(); + *lport++ = '\0'; + *fport++ = '\0'; + if (!tcpdropbyname(argv[0], lport, argv[1], fport)) + exit(1); + } else if (!tcpdropbyname(argv[0], argv[1], argv[2], argv[3])) + exit(1); + + exit(0); +} + +static char * +findport(const char *arg) +{ + char *dot, *colon; + + /* A strrspn() or strrpbrk() would be nice. */ + dot = strrchr(arg, '.'); + colon = strrchr(arg, ':'); + if (dot == NULL) + return (colon); + if (colon == NULL) + return (dot); + if (dot < colon) + return (colon); + else + return (dot); +} + +static struct xinpgen * +getxpcblist(const char *name) +{ + struct xinpgen *xinp; + size_t len; + int rv; + + len = 0; + rv = sysctlbyname(name, NULL, &len, NULL, 0); + if (rv == -1) + err(1, "sysctlbyname %s", name); + + if (len == 0) + errx(1, "%s is empty", name); + + xinp = malloc(len); + if (xinp == NULL) + errx(1, "malloc failed"); + + rv = sysctlbyname(name, xinp, &len, NULL, 0); + if (rv == -1) + err(1, "sysctlbyname %s", name); + + return (xinp); +} + +static void +sockinfo(const struct sockaddr *sa, struct host_service *hs) +{ + static const int flags = NI_NUMERICHOST | NI_NUMERICSERV; + int rv; + + rv = getnameinfo(sa, sa->sa_len, hs->hs_host, sizeof hs->hs_host, + hs->hs_service, sizeof hs->hs_service, flags); + if (rv == -1) + err(1, "getnameinfo"); +} + +static bool +tcpdrop(const struct sockaddr *lsa, const struct sockaddr *fsa) +{ + struct host_service local, foreign; + struct sockaddr_storage addrs[2]; + int rv; + + memcpy(&addrs[TCPDROP_FOREIGN], fsa, fsa->sa_len); + memcpy(&addrs[TCPDROP_LOCAL], lsa, lsa->sa_len); + + sockinfo(lsa, &local); + sockinfo(fsa, &foreign); + + if (tcpdrop_list_commands) { + printf("tcpdrop %s %s %s %s\n", local.hs_host, local.hs_service, + foreign.hs_host, foreign.hs_service); + return (true); + } + + rv = sysctlbyname("net.inet.tcp.drop", NULL, NULL, &addrs, + sizeof addrs); + if (rv == -1) { + warn("%s %s %s %s", local.hs_host, local.hs_service, + foreign.hs_host, foreign.hs_service); + return (false); + } + printf("%s %s %s %s: dropped\n", local.hs_host, local.hs_service, + foreign.hs_host, foreign.hs_service); + return (true); +} + +static bool +tcpdropall(const char *ca_name, const char *stack, int state) +{ + struct xinpgen *head, *xinp; + struct xtcpcb *xtp; + struct xinpcb *xip; + bool ok; + + ok = true; + + head = getxpcblist("net.inet.tcp.pcblist"); + +#define XINP_NEXT(xinp) \ + ((struct xinpgen *)(uintptr_t)((uintptr_t)(xinp) + (xinp)->xig_len)) + + for (xinp = XINP_NEXT(head); xinp->xig_len > sizeof *xinp; + xinp = XINP_NEXT(xinp)) { + xtp = (struct xtcpcb *)xinp; + xip = &xtp->xt_inp; + + /* + * XXX + * Check protocol, support just v4 or v6, etc. + */ + + /* Ignore PCBs which were freed during copyout. */ + if (xip->inp_gencnt > head->xig_gen) + continue; + + /* Skip listening sockets. */ + if (xtp->t_state == TCPS_LISTEN) + continue; + + /* If requested, skip sockets not having the requested state. */ + if ((state != -1) && (xtp->t_state != state)) + continue; + + /* + * If requested, skip sockets not having the requested + * congestion control algorithm. + */ + if (ca_name[0] != '\0' && + strncmp(xtp->xt_cc, ca_name, TCP_CA_NAME_MAX)) + continue; + + /* If requested, skip sockets not having the requested stack. */ + if (stack[0] != '\0' && + strncmp(xtp->xt_stack, stack, TCP_FUNCTION_NAME_LEN_MAX)) + continue; + + if (!tcpdropconn(&xip->inp_inc)) + ok = false; + } + free(head); + + return (ok); +} + +static bool +tcpdropbyname(const char *lhost, const char *lport, const char *fhost, + const char *fport) +{ + static const struct addrinfo hints = { + /* + * Look for TCP streams in all domains. + */ + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + struct addrinfo *ail, *local, *aif, *foreign; + int error; + bool ok, infamily; + + error = getaddrinfo(lhost, lport, &hints, &local); + if (error != 0) + errx(1, "getaddrinfo: %s port %s: %s", lhost, lport, + gai_strerror(error)); + + error = getaddrinfo(fhost, fport, &hints, &foreign); + if (error != 0) { + freeaddrinfo(local); /* XXX gratuitous */ + errx(1, "getaddrinfo: %s port %s: %s", fhost, fport, + gai_strerror(error)); + } + + ok = true; + infamily = false; + + /* + * Try every combination of local and foreign address pairs. + */ + for (ail = local; ail != NULL; ail = ail->ai_next) { + for (aif = foreign; aif != NULL; aif = aif->ai_next) { + if (ail->ai_family != aif->ai_family) + continue; + infamily = true; + if (!tcpdrop(ail->ai_addr, aif->ai_addr)) + ok = false; + } + } + + if (!infamily) { + warnx("%s %s %s %s: different address families", lhost, lport, + fhost, fport); + ok = false; + } + + freeaddrinfo(local); + freeaddrinfo(foreign); + + return (ok); +} + +static bool +tcpdropconn(const struct in_conninfo *inc) +{ + struct sockaddr *local, *foreign; + struct sockaddr_in6 sin6[2]; + struct sockaddr_in sin4[2]; + + if ((inc->inc_flags & INC_ISIPV6) != 0) { + memset(sin6, 0, sizeof sin6); + + sin6[TCPDROP_LOCAL].sin6_len = sizeof sin6[TCPDROP_LOCAL]; + sin6[TCPDROP_LOCAL].sin6_family = AF_INET6; + sin6[TCPDROP_LOCAL].sin6_port = inc->inc_lport; + memcpy(&sin6[TCPDROP_LOCAL].sin6_addr, &inc->inc6_laddr, + sizeof inc->inc6_laddr); + local = (struct sockaddr *)&sin6[TCPDROP_LOCAL]; + + sin6[TCPDROP_FOREIGN].sin6_len = sizeof sin6[TCPDROP_FOREIGN]; + sin6[TCPDROP_FOREIGN].sin6_family = AF_INET6; + sin6[TCPDROP_FOREIGN].sin6_port = inc->inc_fport; + memcpy(&sin6[TCPDROP_FOREIGN].sin6_addr, &inc->inc6_faddr, + sizeof inc->inc6_faddr); + foreign = (struct sockaddr *)&sin6[TCPDROP_FOREIGN]; + } else { + memset(sin4, 0, sizeof sin4); + + sin4[TCPDROP_LOCAL].sin_len = sizeof sin4[TCPDROP_LOCAL]; + sin4[TCPDROP_LOCAL].sin_family = AF_INET; + sin4[TCPDROP_LOCAL].sin_port = inc->inc_lport; + memcpy(&sin4[TCPDROP_LOCAL].sin_addr, &inc->inc_laddr, + sizeof inc->inc_laddr); + local = (struct sockaddr *)&sin4[TCPDROP_LOCAL]; + + sin4[TCPDROP_FOREIGN].sin_len = sizeof sin4[TCPDROP_FOREIGN]; + sin4[TCPDROP_FOREIGN].sin_family = AF_INET; + sin4[TCPDROP_FOREIGN].sin_port = inc->inc_fport; + memcpy(&sin4[TCPDROP_FOREIGN].sin_addr, &inc->inc_faddr, + sizeof inc->inc_faddr); + foreign = (struct sockaddr *)&sin4[TCPDROP_FOREIGN]; + } + + return (tcpdrop(local, foreign)); +} + +static void +usage(void) +{ + fprintf(stderr, +"usage: tcpdrop local-address local-port foreign-address foreign-port\n" +" tcpdrop local-address:local-port foreign-address:foreign-port\n" +" tcpdrop local-address.local-port foreign-address.foreign-port\n" +" tcpdrop [-l] -a\n" +" tcpdrop [-l] -C cc-algo [-S stack] [-s state]\n" +" tcpdrop [-l] [-C cc-algo] -S stack [-s state]\n" +" tcpdrop [-l] [-C cc-algo] [-S stack] -s state\n"); + exit(1); +} |
