diff --git a/usr.bin/sockstat/Makefile b/usr.bin/sockstat/Makefile index ab20a8c0f5f9..f2b82bb91ad7 100644 --- a/usr.bin/sockstat/Makefile +++ b/usr.bin/sockstat/Makefile @@ -1,6 +1,7 @@ # $FreeBSD$ -SCRIPTS= sockstat.pl +PROG= sockstat +WARNS?= 4 MAN= sockstat.1 .include diff --git a/usr.bin/sockstat/sockstat.1 b/usr.bin/sockstat/sockstat.1 index 383849ce9f6f..a2f544aaecd6 100644 --- a/usr.bin/sockstat/sockstat.1 +++ b/usr.bin/sockstat/sockstat.1 @@ -1,138 +1,138 @@ .\"- .\" Copyright (c) 1999 Dag-Erling Coïdan Smørgrav .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer .\" in this position and unchanged. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" 3. The name of the author may not be used to endorse or promote products .\" derived from this software without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES .\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, .\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT .\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" .\" $FreeBSD$ .\" .Dd August 7, 2001 .Dt SOCKSTAT 1 .Os .Sh NAME .Nm sockstat .Nd list open sockets .Sh SYNOPSIS .Nm .Op Fl 46clu .Op Fl p Ar ports .Sh DESCRIPTION The .Nm command lists open Internet or .Ux domain sockets. .Pp The following options are available: .Bl -tag -width Fl .It Fl 4 Show .Dv AF_INET (IPv4) sockets. .It Fl 6 Show .Dv AF_INET6 (IPv6) sockets. .It Fl c Show connected sockets. .It Fl l Show listening sockets. .It Fl p Ar ports Only show Internet sockets if either the local or foreign port number is on the specified list. The .Ar ports argument is a comma-separated list of port numbers and ranges specified as first and last port separated by a dash. .It Fl u Show .Dv AF_LOCAL .Pq Ux sockets. .El .Pp If neither .Fl 4 , 6 or .Fl u is specified, .Nm will list sockets in all three domains. .Pp If neither .Fl c or .Fl l is specified, .Nm will list both listening and connected sockets. .Pp The information listed for each socket is: .Bl -tag -width "FOREIGN ADDRESS" .It Li USER The user who owns the socket. .It Li COMMAND The command which holds the socket. .It Li PID The process ID of the command which holds the socket. .It Li FD The file descriptor number of the socket. .It Li PROTO The transport protocol associated with the socket for Internet sockets, or the type of socket (stream or datagram) for .Ux sockets. -.It Li ADDRESS -.No ( Ux -sockets only) -For bound sockets, this is the filename of the socket. -For other sockets, it is the name, PID and file descriptor number of -the peer, or -.Dq Li "(none)" -if the socket is neither bound nor connected. .It Li LOCAL ADDRESS -(Internet sockets only) -The address the local end of the socket is bound to (see +For Internet sockets, this is the address the local end of the socket +is bound to (see .Xr getsockname 2 ) . +For bound +.Ux +sockets, it is the socket's filename. +For other +.Ux +sockets, it is a right arrow followed by the endpoint's filename, or +.Dq Li ?? +if the endpoint could not be determined. .It Li FOREIGN ADDRESS (Internet sockets only) The address the foreign end of the socket is bound to (see .Xr getpeername 2 ) . .El .Sh SEE ALSO .Xr fstat 1 , .Xr netstat 1 , .Xr inet 4 , .Xr inet6 4 .Sh HISTORY The .Nm command appeared in .Fx 3.1 . .Sh AUTHORS The .Nm command and this manual page were written by .An Dag-Erling Sm\(/orgrav Aq des@FreeBSD.org . diff --git a/usr.bin/sockstat/sockstat.c b/usr.bin/sockstat/sockstat.c new file mode 100644 index 000000000000..3c28bb2ea34f --- /dev/null +++ b/usr.bin/sockstat/sockstat.c @@ -0,0 +1,584 @@ +/*- + * Copyright (c) 2002 Dag-Erling Coïdan Smørgrav + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int opt_4; /* Show IPv4 sockets */ +static int opt_6; /* Show IPv6 sockets */ +static int opt_c; /* Show connected sockets */ +static int opt_l; /* Show listening sockets */ +static int opt_u; /* Show Unix domain sockets */ +static int opt_v; /* Verbose mode */ + +static int *ports; + +#define INT_BIT (sizeof(int)*CHAR_BIT) +#define SET_PORT(p) do { ports[p / INT_BIT] |= 1 << (p % INT_BIT); } while (0) +#define CHK_PORT(p) (ports[p / INT_BIT] & (1 << (p % INT_BIT))) + +struct sock { + void *socket; + void *pcb; + int vflag; + int family; + int proto; + const char *protoname; + struct sockaddr_storage laddr; + struct sockaddr_storage faddr; + struct sock *next; +}; + +#define HASHSIZE 1009 +static struct sock *sockhash[HASHSIZE]; + +static struct xfile *xfiles; +static int nxfiles; + +static int +xprintf(const char *fmt, ...) +{ + va_list ap; + int len; + + va_start(ap, fmt); + len = vprintf(fmt, ap); + va_end(ap); + if (len < 0) + err(1, "printf()"); + return (len); +} + +static void +parse_ports(const char *portspec) +{ + const char *p, *q; + int port, end; + + if (ports == NULL) + if ((ports = calloc(1, 65536 / INT_BIT)) == NULL) + err(1, "calloc()"); + p = portspec; + while (*p != '\0') { + if (!isdigit(*p)) + errx(1, "syntax error in port range"); + for (q = p; *q != '\0' && isdigit(*q); ++q) + /* nothing */ ; + for (port = 0; p < q; ++p) + port = port * 10 + digittoint(*p); + if (port < 0 || port > 65535) + errx(1, "invalid port number"); + SET_PORT(port); + switch (*p) { + case '-': + ++p; + break; + case ',': + ++p; + /* fall through */ + case '\0': + default: + continue; + } + for (q = p; *q != '\0' && isdigit(*q); ++q) + /* nothing */ ; + for (end = 0; p < q; ++p) + end = end * 10 + digittoint(*p); + if (end < port || end > 65535) + errx(1, "invalid port number"); + while (port++ < end) + SET_PORT(port); + if (*p == ',') + ++p; + } +} + +static void +sockaddr(struct sockaddr_storage *sa, int af, void *addr, int port) +{ + struct sockaddr_in *sin4; + struct sockaddr_in6 *sin6; + + bzero(sa, sizeof *sa); + switch (af) { + case AF_INET: + sin4 = (struct sockaddr_in *)sa; + sin4->sin_len = sizeof *sin4; + sin4->sin_family = af; + sin4->sin_port = port; + sin4->sin_addr = *(struct in_addr *)addr; + break; + case AF_INET6: + sin6 = (struct sockaddr_in6 *)sa; + sin6->sin6_len = sizeof *sin6; + sin6->sin6_family = af; + sin6->sin6_port = port; + sin6->sin6_addr = *(struct in6_addr *)addr; + break; + default: + abort(); + } +} + +static void +gather_inet(int proto) +{ + struct xinpgen *xig, *exig; + struct xinpcb *xip; + struct xtcpcb *xtp; + struct inpcb *inp; + struct xsocket *so; + struct sock *sock; + const char *varname, *protoname; + size_t len, bufsize; + void *buf; + int hash, retry, vflag; + + vflag = 0; + if (opt_4) + vflag |= INP_IPV4; + if (opt_6) + vflag |= INP_IPV6; + + switch (proto) { + case IPPROTO_TCP: + varname = "net.inet.tcp.pcblist"; + protoname = "tcp"; + break; + case IPPROTO_UDP: + varname = "net.inet.udp.pcblist"; + protoname = "udp"; + break; + default: + abort(); + } + + buf = NULL; + bufsize = 8192; + retry = 5; + do { + for (;;) { + if ((buf = realloc(buf, bufsize)) == NULL) + err(1, "realloc()"); + len = bufsize; + if (sysctlbyname(varname, buf, &len, NULL, 0) == 0) + break; + if (errno != ENOMEM) + err(1, "sysctlbyname()"); + bufsize *= 2; + } + xig = (struct xinpgen *)buf; + exig = (struct xinpgen *)((char *)buf + len - sizeof *exig); + if (xig->xig_len != sizeof *xig || + exig->xig_len != sizeof *exig) + errx(1, "struct xinpgen size mismatch"); + } while (xig->xig_gen != exig->xig_gen && retry--); + + if (xig->xig_gen != exig->xig_gen && opt_v) + warnx("warning: data may be inconsistent"); + + for (;;) { + xig = (struct xinpgen *)((char *)xig + xig->xig_len); + if (xig >= exig) + break; + switch (proto) { + case IPPROTO_TCP: + xtp = (struct xtcpcb *)xig; + if (xtp->xt_len != sizeof *xtp) { + warnx("struct xtcpcb size mismatch"); + goto out; + } + inp = &xtp->xt_inp; + so = &xtp->xt_socket; + break; + case IPPROTO_UDP: + xip = (struct xinpcb *)xig; + if (xip->xi_len != sizeof *xip) { + warnx("struct xinpcb size mismatch"); + goto out; + } + inp = &xip->xi_inp; + so = &xip->xi_socket; + break; + default: + abort(); + } + if ((inp->inp_vflag & vflag) == 0) + continue; + if ((sock = calloc(1, sizeof *sock)) == NULL) + err(1, "malloc()"); + sock->socket = so->xso_so; + sock->proto = proto; + if (inp->inp_vflag & INP_IPV4) { + sock->family = AF_INET; + sockaddr(&sock->laddr, sock->family, + &inp->inp_laddr, inp->inp_lport); + sockaddr(&sock->faddr, sock->family, + &inp->inp_faddr, inp->inp_fport); + } else if (inp->inp_vflag & INP_IPV6) { + sock->family = AF_INET6; + sockaddr(&sock->laddr, sock->family, + &inp->in6p_laddr, inp->in6p_lport); + sockaddr(&sock->faddr, sock->family, + &inp->in6p_faddr, inp->in6p_fport); + } else { + if (opt_v) + warnx("invalid vflag 0x%x", inp->inp_vflag); + free(sock); + continue; + } + sock->vflag = inp->inp_vflag; + sock->protoname = protoname; + hash = (int)((uintptr_t)sock->socket % HASHSIZE); + sock->next = sockhash[hash]; + sockhash[hash] = sock; + } +out: + free(buf); +} + +static void +gather_unix(int proto) +{ + struct xunpgen *xug, *exug; + struct xunpcb *xup; + struct sock *sock; + const char *varname, *protoname; + size_t len, bufsize; + void *buf; + int hash, retry; + + switch (proto) { + case SOCK_STREAM: + varname = "net.local.stream.pcblist"; + protoname = "stream"; + break; + case SOCK_DGRAM: + varname = "net.local.dgram.pcblist"; + protoname = "dgram"; + break; + default: + abort(); + } + buf = NULL; + bufsize = 8192; + retry = 5; + do { + for (;;) { + if ((buf = realloc(buf, bufsize)) == NULL) + err(1, "realloc()"); + len = bufsize; + if (sysctlbyname(varname, buf, &len, NULL, 0) == 0) + break; + if (errno != ENOMEM) + err(1, "sysctlbyname()"); + bufsize *= 2; + } + xug = (struct xunpgen *)buf; + exug = (struct xunpgen *)((char *)buf + len - sizeof *exug); + if (xug->xug_len != sizeof *xug || + exug->xug_len != sizeof *exug) { + warnx("struct xinpgen size mismatch"); + goto out; + } + } while (xug->xug_gen != exug->xug_gen && retry--); + + if (xug->xug_gen != exug->xug_gen && opt_v) + warnx("warning: data may be inconsistent"); + + for (;;) { + xug = (struct xunpgen *)((char *)xug + xug->xug_len); + if (xug >= exug) + break; + xup = (struct xunpcb *)xug; + if (xup->xu_len != sizeof *xup) { + warnx("struct xunpcb size mismatch"); + goto out; + } + if ((sock = calloc(1, sizeof *sock)) == NULL) + err(1, "malloc()"); + sock->socket = xup->xu_socket.xso_so; + sock->pcb = xup->xu_unpp; + sock->proto = proto; + sock->family = AF_UNIX; + sock->protoname = protoname; + if (xup->xu_unp.unp_addr != NULL) + sock->laddr = *(struct sockaddr_storage *)&xup->xu_addr; + else if (xup->xu_unp.unp_conn != NULL) + *(void **)&sock->faddr = xup->xu_unp.unp_conn; + hash = (int)((uintptr_t)sock->socket % HASHSIZE); + sock->next = sockhash[hash]; + sockhash[hash] = sock; + } +out: + free(buf); +} + +static void +getfiles(void) +{ + size_t len; + + if ((xfiles = malloc(len = sizeof *xfiles)) == NULL) + err(1, "malloc()"); + while (sysctlbyname("kern.file", xfiles, &len, 0, 0) == -1) { + if (errno != ENOMEM) + err(1, "sysctlbyname()"); + len *= 2; + if ((xfiles = realloc(xfiles, len)) == NULL) + err(1, "realloc()"); + } + if (len > 0 && xfiles->xf_size != sizeof *xfiles) + errx(1, "struct xfile size mismatch"); + nxfiles = len / sizeof *xfiles; +} + +static int +printaddr(int af, struct sockaddr_storage *ss) +{ + char addrstr[INET6_ADDRSTRLEN] = { '\0', '\0' }; + struct sockaddr_un *sun; + void *addr; + int off, port; + + switch (af) { + case AF_INET: + addr = &((struct sockaddr_in *)ss)->sin_addr; + if (inet_lnaof(*(struct in_addr *)addr) == INADDR_ANY) + addrstr[0] = '*'; + port = ntohs(((struct sockaddr_in *)ss)->sin_port); + break; + case AF_INET6: + addr = &((struct sockaddr_in6 *)ss)->sin6_addr; + if (IN6_IS_ADDR_UNSPECIFIED((struct in6_addr *)addr)) + addrstr[0] = '*'; + port = ntohs(((struct sockaddr_in6 *)ss)->sin6_port); + break; + case AF_UNIX: + sun = (struct sockaddr_un *)ss; + off = (int)((char *)&sun->sun_path - (char *)sun); + return (xprintf("%.*s", sun->sun_len - off, sun->sun_path)); + } + if (addrstr[0] == '\0') + inet_ntop(af, addr, addrstr, sizeof addrstr); + if (port == 0) + return xprintf("%s:*", addrstr); + else + return xprintf("%s:%d", addrstr, port); +} + +static const char * +getprocname(pid_t pid) +{ + static struct kinfo_proc proc; + size_t len; + int mib[4]; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = (int)pid; + len = sizeof proc; + if (sysctl(mib, 4, &proc, &len, NULL, 0) == -1) { + warn("sysctl()"); + return ("??"); + } + return (proc.ki_ocomm); +} + +static void +display(void) +{ + struct passwd *pwd; + struct xfile *xf; + struct sock *s; + void *p; + int hash, n, pos; + + printf("%-8s %-10s %-5s %-2s %-6s %-21s %-21s\n", + "USER", "COMMAND", "PID", "FD", "PROTO", + "LOCAL ADDRESS", "FOREIGN ADDRESS"); + setpassent(1); + for (xf = xfiles, n = 0; n < nxfiles; ++n, ++xf) { + hash = (int)((uintptr_t)xf->xf_data % HASHSIZE); + /*xprintf("%p %d\n", xf->xf_data, hash);*/ + for (s = sockhash[hash]; s != NULL; s = s->next) + if (s->socket == xf->xf_data) + break; + if (s == NULL) + continue; + pos = 0; + if ((pwd = getpwuid(xf->xf_uid)) == NULL) + pos += xprintf("%lu", (u_long)xf->xf_uid); + else + pos += xprintf("%s", pwd->pw_name); + while (pos < 9) + pos += xprintf(" "); + pos += xprintf("%.10s", getprocname(xf->xf_pid)); + while (pos < 20) + pos += xprintf(" "); + pos += xprintf("%lu", (u_long)xf->xf_pid); + while (pos < 26) + pos += xprintf(" "); + pos += xprintf("%d", xf->xf_fd); + while (pos < 29) + pos += xprintf(" "); + pos += xprintf("%s", s->protoname); + if (s->vflag & INP_IPV4) + pos += xprintf("4"); + if (s->vflag & INP_IPV6) + pos += xprintf("6"); + while (pos < 36) + pos += xprintf(" "); + switch (s->family) { + case AF_INET: + case AF_INET6: + pos += printaddr(s->family, &s->laddr); + while (pos < 58) + pos += xprintf(" "); + pos += printaddr(s->family, &s->faddr); + break; + case AF_UNIX: + /* server */ + if (s->laddr.ss_len > 0) { + pos += printaddr(s->family, &s->laddr); + break; + } + /* client */ + pos += xprintf("-> "); + p = *(void **)&s->faddr; + for (hash = 0; hash < HASHSIZE; ++hash) { + for (s = sockhash[hash]; s != NULL; s = s->next) + if (s->pcb == p) + break; + if (s != NULL) + break; + } + if (s == NULL || s->laddr.ss_len == 0) + pos += xprintf("??"); + else + pos += printaddr(s->family, &s->laddr); + break; + default: + abort(); + } + xprintf("\n"); + } +} + +static void +usage(void) +{ + fprintf(stderr, "Usage: sockstat [-46clu] [-p ports]\n"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int o; + + while ((o = getopt(argc, argv, "46clp:uv")) != -1) + switch (o) { + case '4': + opt_4 = 1; + break; + case '6': + opt_6 = 1; + break; + case 'c': + opt_c = 1; + break; + case 'l': + opt_l = 1; + break; + case 'p': + parse_ports(optarg); + break; + case 'u': + opt_u = 1; + break; + case 'v': + ++opt_v; + break; + default: + usage(); + } + + argc -= optind; + argv += optind; + + if (argc > 0) + usage(); + + if (!opt_4 && !opt_6 && !opt_u) + opt_4 = opt_6 = opt_u = 1; + if (!opt_c && !opt_l) + opt_c = opt_l = 1; + + if (opt_4 || opt_6) { + gather_inet(IPPROTO_TCP); + gather_inet(IPPROTO_UDP); + } + if (opt_u) { + gather_unix(SOCK_STREAM); + gather_unix(SOCK_DGRAM); + } + getfiles(); + display(); + + exit(0); +} diff --git a/usr.bin/sockstat/sockstat.pl b/usr.bin/sockstat/sockstat.pl deleted file mode 100644 index 6d1ac0f2d550..000000000000 --- a/usr.bin/sockstat/sockstat.pl +++ /dev/null @@ -1,246 +0,0 @@ -#!/usr/bin/perl -w -#- -# Copyright (c) 1999 Dag-Erling Coïdan Smørgrav -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer -# in this position and unchanged. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. The name of the author may not be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# $FreeBSD$ -# - -use strict; -use Getopt::Std; - -my %netstat; -my %fstat; -my $unknown = [ "?", "?", "?", "?", "?", "?", "?", "?", "?" ]; - -my $inet_fmt = "%-8.8s %-8.8s %5.5s %4.4s %-6.6s %-21.21s %-21.21s\n"; -my $unix_fmt = "%-8.8s %-8.8s %5.5s %4.4s %-6.6s %-43.43s\n"; - -my @ranges; - -# -# Parse a port range specification -# -sub parse_port_ranges($) { - my $spec = shift; # Range spec - - my $range; # Range - my ($low, $high); # Low/high ends of range - - foreach $range (split(/\s*,\s*/, $spec)) { - if ($range =~ m/^(\d+)-(\d+)$/) { - ($low, $high) = ($1, $2); - } elsif ($range =~ m/^-(\d+)$/) { - ($low, $high) = (1, $1); - } elsif ($range =~ m/^(\d+)-$/) { - ($low, $high) = ($1, 65535); - } elsif ($range =~ m/^(\d+)$/) { - $low = $high = $1; - } else { - die("invalid range specification: $range\n"); - } - if ($low < 0 || $low > 65535 || $high < 0 || $high > 65535) { - die("valid ports numbers are 1-65535 inclusive\n"); - } - if ($low > $high) { - $low ^= $high; - $high ^= $low; - $low ^= $high; - } - push(@ranges, [ $low, $high ]); - } -} - -# -# Check if a port is in an allowed range -# -sub check_range($) { - my $addr = shift; # Address to check - - my $port; # Port number - my $range; # Range - - if (@ranges == 0) { - return 1; - } - - if ($addr !~ m/\.(\d+)$/) { - return undef; - } - $port = $1; - - foreach $range (@ranges) { - if ($port >= $range->[0] && $port <= $range->[1]) { - return 1; - } - } - return undef; -} - -# -# Gather information about sockets -# -sub gather() { - - local *PIPE; # Pipe - my $pid; # Child PID - my $line; # Input line - my @fields; # Fields - - # Netstat - if (!defined($pid = open(PIPE, "-|"))) { - die("open(netstat): $!\n"); - } elsif ($pid == 0) { - exec("/usr/bin/netstat", "-AanW"); - die("exec(netstat): $!\n"); - } - while ($line = ) { - next unless ($line =~ m/^[0-9a-f]{8} /) || ($line =~ m/^[0-9a-f]{16} /); - chomp($line); - @fields = split(' ', $line); - $netstat{$fields[0]} = [ @fields ]; - } - close(PIPE) - or die("close(netstat): $!\n"); - - # Fstat - if (!defined($pid = open(PIPE, "-|"))) { - die("open(fstat): $!\n"); - } elsif ($pid == 0) { - exec("/usr/bin/fstat"); - die("exec(fstat): $!\n"); - } - while ($line = ) { - chomp($line); - @fields = split(' ', $line); - next if ($fields[4] eq "-"); - push(@{$fstat{$fields[4]}}, [ @fields ]); - } - close(PIPE) - or die("close(fstat): $!\n"); -} - -# -# Replace the last dot in an "address.port" string with a colon -# -sub addr($) { - my $addr = shift; # Address - - $addr =~ s/^(.*)\.([^\.]*)$/$1:$2/; - return $addr; -} - -# -# Print information about Internet sockets -# -sub print_inet($$$) { - my $af = shift; # Address family - my $conn = shift || 0; # Show connected sockets - my $listen = shift || 0; # Show listen sockets - - my $fsd; # Fstat data - my $nsd; # Netstat data - - printf($inet_fmt, "USER", "COMMAND", "PID", "FD", - "PROTO", "LOCAL ADDRESS", "FOREIGN ADDRESS"); - foreach $fsd (@{$fstat{$af}}) { - next unless defined($fsd->[7]); - $nsd = $netstat{$fsd->[7]} || $unknown; - next unless (check_range($nsd->[4]) || check_range($nsd->[5])); - next if (!$conn && $nsd->[5] ne '*.*'); - next if (!$listen && $nsd->[5] eq '*.*'); - printf($inet_fmt, $fsd->[0], $fsd->[1], $fsd->[2], - substr($fsd->[3], 0, -1), - $nsd->[1], addr($nsd->[4]), addr($nsd->[5])); - } - print("\n"); -} - -# -# Print information about Unix domain sockets -# -sub print_unix($$) { - my $conn = shift || 0; # Show connected sockets - my $listen = shift || 0; # Show listen sockets - - my %endpoint; # Mad PCB to process/fd - my $fsd; # Fstat data - my $nsd; # Netstat data - - foreach $fsd (@{$fstat{"local"}}) { - $endpoint{$fsd->[6]} = "$fsd->[1]\[$fsd->[2]\]:" . - substr($fsd->[3], 0, -1); - } - printf($unix_fmt, "USER", "COMMAND", "PID", "FD", "PROTO", "ADDRESS"); - foreach $fsd (@{$fstat{"local"}}) { - next unless defined($fsd->[6]); - next if (!$conn && defined($fsd->[8])); - next if (!$listen && !defined($fsd->[8])); - $nsd = $netstat{$fsd->[6]} || $unknown; - printf($unix_fmt, $fsd->[0], $fsd->[1], $fsd->[2], - substr($fsd->[3], 0, -1), $fsd->[5], - $nsd->[8] || ($fsd->[8] ? $endpoint{$fsd->[8]} : "(none)")); - } - print("\n"); -} - -# -# Print usage message and exit -# -sub usage() { - print(STDERR "usage: sockstat [-46clu] [-p ports]\n"); - exit(1); -} - -MAIN:{ - my %opts; # Command-line options - - getopts("46clp:u", \%opts) - or usage(); - - gather(); - - if (!$opts{'4'} && !$opts{'6'} && !$opts{'u'}) { - $opts{'4'} = $opts{'6'} = $opts{'u'} = 1; - } - if (!$opts{'c'} && !$opts{'l'}) { - $opts{'c'} = $opts{'l'} = 1; - } - if ($opts{'p'}) { - parse_port_ranges($opts{'p'}); - } - if ($opts{'4'}) { - print_inet("internet", $opts{'c'}, $opts{'l'}); - } - if ($opts{'6'}) { - print_inet("internet6", $opts{'c'}, $opts{'l'}); - } - if ($opts{'u'}) { - print_unix($opts{'c'}, $opts{'l'}); - } - - exit(0); -}