diff --git a/usr.sbin/tcpdrop/tcpdrop.8 b/usr.sbin/tcpdrop/tcpdrop.8 index 5fd3a43e9506..b846257b79b4 100644 --- a/usr.sbin/tcpdrop/tcpdrop.8 +++ b/usr.sbin/tcpdrop/tcpdrop.8 @@ -1,5 +1,6 @@ .\" $OpenBSD: tcpdrop.8,v 1.5 2004/05/24 13:57:31 jmc Exp $ .\" +.\" Copyright (c) 2009 Juli Mallett .\" Copyright (c) 2004 Markus Friedl .\" .\" Permission to use, copy, modify, and distribute this software for any @@ -16,35 +17,58 @@ .\" .\" $FreeBSD$ .\" -.Dd March 21, 2004 +.Dd March 24, 2009 .Dt TCPDROP 8 .Os .Sh NAME .Nm tcpdrop -.Nd drop a TCP connection +.Nd drop TCP connections .Sh SYNOPSIS .Nm tcpdrop -.Ar laddr -.Ar lport -.Ar faddr -.Ar fport +.Ar local-address +.Ar local-port +.Ar foreign-address +.Ar foreign-port +.Nm tcpdrop +.Op Fl l +.Fl a .Sh DESCRIPTION The .Nm -command drops the TCP connection specified by the local address -.Ar laddr , +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 active connections. +The +.Fl l +flag may be given to list the tcpdrop invocation to drop all active +connections one at a time. +.Pp +If +.Fl a +is not specified then only the connection between the given local +address +.Ar local-address , port -.Ar lport +.Ar local-port , and the foreign address -.Ar faddr , +.Ar foreign-address , port -.Ar fport . -Addresses and ports can be specified by name or numeric value. +.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. +.Nm +in case of success or failure. .Sh EXIT STATUS .Ex -std .Sh EXAMPLES If a connection to -.Xr httpd 8 Pq Pa ports/www/apache2 +.Xr httpd 8 is causing congestion on a network link, one can drop the TCP session in charge: .Bd -literal -offset indent @@ -57,8 +81,16 @@ 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 .Sh SEE ALSO .Xr netstat 1 , .Xr sockstat 1 .Sh AUTHORS .An Markus Friedl Aq markus@openbsd.org +.An Juli Mallett Aq jmallett@FreeBSD.org diff --git a/usr.sbin/tcpdrop/tcpdrop.c b/usr.sbin/tcpdrop/tcpdrop.c index 06ca0da84a0f..6aae85ccb4d1 100644 --- a/usr.sbin/tcpdrop/tcpdrop.c +++ b/usr.sbin/tcpdrop/tcpdrop.c @@ -1,6 +1,7 @@ /* $OpenBSD: tcpdrop.c,v 1.4 2004/05/22 23:55:22 deraadt Exp $ */ /*- + * Copyright (c) 2009 Juli Mallett * Copyright (c) 2004 Markus Friedl * * Permission to use, copy, modify, and distribute this software for any @@ -21,15 +22,41 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include + +#include +#define TCPSTATES +#include #include #include #include +#include #include #include #include +#include + +#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 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(void); +static bool tcpdropbyname(const char *, const char *, const char *, + const char *); +static bool tcpdropconn(const struct in_conninfo *); +static void usage(void); /* * Drop a tcp connection. @@ -37,55 +64,259 @@ __FBSDID("$FreeBSD$"); int main(int argc, char *argv[]) { - struct addrinfo hints, *ail, *aif, *laddr, *faddr; - /* addrs[0] is a foreign socket, addrs[1] is a local one. */ - struct sockaddr_storage addrs[2]; - int mib[] = { CTL_NET, PF_INET, IPPROTO_TCP, TCPCTL_DROP }; - int gaierr, rval = 0; - char fhbuf[NI_MAXHOST], fsbuf[NI_MAXSERV], lhbuf[NI_MAXHOST], - lsbuf[NI_MAXSERV]; + bool dropall; + int ch; - if (argc != 5) { - fprintf(stderr, "usage: tcpdrop laddr lport faddr fport\n"); - exit(1); - } - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - if ((gaierr = getaddrinfo(argv[1], argv[2], &hints, &laddr)) != 0) - errx(1, "%s port %s: %s", argv[1], argv[2], - gai_strerror(gaierr)); - if ((gaierr = getaddrinfo(argv[3], argv[4], &hints, &faddr)) != 0) { - freeaddrinfo(laddr); - errx(1, "%s port %s: %s", argv[3], argv[4], - gai_strerror(gaierr)); - } - for (ail = laddr; ail; ail = ail->ai_next) { - for (aif = faddr; aif; aif = aif->ai_next) { - if (ail->ai_family != aif->ai_family) - continue; - memcpy(&addrs[0], aif->ai_addr, aif->ai_addrlen); - memcpy(&addrs[1], ail->ai_addr, ail->ai_addrlen); - if (getnameinfo(aif->ai_addr, aif->ai_addrlen, - fhbuf, sizeof(fhbuf), - fsbuf, sizeof(fsbuf), - NI_NUMERICHOST | NI_NUMERICSERV) == -1) - err(1, "getnameinfo"); - if (getnameinfo(ail->ai_addr, ail->ai_addrlen, - lhbuf, sizeof(lhbuf), - lsbuf, sizeof(lsbuf), - NI_NUMERICHOST | NI_NUMERICSERV) == -1) - err(1, "getnameinfo"); - if (sysctl(mib, sizeof (mib) / sizeof (int), NULL, - NULL, &addrs, sizeof(addrs)) == -1) { - rval = 1; - warn("%s %s %s %s", lhbuf, lsbuf, fhbuf, fsbuf); - } else - printf("%s %s %s %s: dropped\n", - lhbuf, lsbuf, fhbuf, fsbuf); + dropall = false; + + while ((ch = getopt(argc, argv, "al")) != -1) { + switch (ch) { + case 'a': + dropall = true; + break; + case 'l': + tcpdrop_list_commands = true; + break; + default: + usage(); } } - freeaddrinfo(laddr); - freeaddrinfo(faddr); - exit(rval); + argc -= optind; + argv += optind; + + if (dropall) { + if (argc != 0) + usage(); + if (!tcpdropall()) + exit(1); + exit(0); + } + + if (argc != 4 || tcpdrop_list_commands) + usage(); + + if (!tcpdropbyname(argv[0], argv[1], argv[2], argv[3])) + exit(1); + + exit(0); +} + +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(void) +{ + struct xinpgen *head, *xinp; + struct xtcpcb *xpcb; + struct tcpcb *tp; + struct inpcb *inp; + bool ok; + + ok = true; + + head = getxpcblist("net.inet.tcp.pcblist"); + +#define XINP_NEXT(xinp) \ + ((struct xinpgen *)((uintptr_t)(xinp) + (xinp)->xig_len)) + + for (xinp = XINP_NEXT(head); xinp->xig_len > sizeof *xinp; + xinp = XINP_NEXT(xinp)) { + xpcb = (struct xtcpcb *)xinp; + tp = &xpcb->xt_tp; + inp = &xpcb->xt_inp; + + /* + * XXX + * Check protocol, support just v4 or v6, etc. + */ + + /* Ignore PCBs which were freed during copyout. */ + if (inp->inp_gencnt > head->xig_gen) + continue; + + /* Skip listening sockets. */ + if (tp->t_state == TCPS_LISTEN) + continue; + + if (!tcpdropconn(&inp->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 streams in all domains. + */ + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }; + 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", lhost, lport, + 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[TCPDROP_LOCAL], 0, sizeof sin4[TCPDROP_LOCAL]); + + 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 [-l] -a\n"); + exit(1); }