Rewrite sockstat(1) in C.

Sponsored by:	DARPA, NAI Labs
This commit is contained in:
Dag-Erling Smørgrav 2002-07-31 12:32:03 +00:00
parent aefe27a25c
commit ca007d9172
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=101043
4 changed files with 596 additions and 257 deletions

View File

@ -1,6 +1,7 @@
# $FreeBSD$
SCRIPTS= sockstat.pl
PROG= sockstat
WARNS?= 4
MAN= sockstat.1
.include <bsd.prog.mk>

View File

@ -104,18 +104,18 @@ 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

584
usr.bin/sockstat/sockstat.c Normal file
View File

@ -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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sysctl.h>
#include <sys/file.h>
#include <sys/user.h>
#include <sys/un.h>
#include <sys/unpcb.h>
#include <netinet/in.h>
#include <netinet/in_pcb.h>
#include <netinet/tcp.h>
#include <netinet/tcp_seq.h>
#include <netinet/tcp_var.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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);
}

View File

@ -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 = <PIPE>) {
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 = <PIPE>) {
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);
}