aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/sockstat
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/sockstat')
-rw-r--r--usr.bin/sockstat/Makefile4
-rw-r--r--usr.bin/sockstat/main.c1883
-rw-r--r--usr.bin/sockstat/sockstat.c1883
-rw-r--r--usr.bin/sockstat/sockstat.h35
-rw-r--r--usr.bin/sockstat/tests/Makefile8
-rw-r--r--usr.bin/sockstat/tests/sockstat_test.c190
6 files changed, 2142 insertions, 1861 deletions
diff --git a/usr.bin/sockstat/Makefile b/usr.bin/sockstat/Makefile
index 7254511f21c6..c6e7a078162b 100644
--- a/usr.bin/sockstat/Makefile
+++ b/usr.bin/sockstat/Makefile
@@ -1,6 +1,7 @@
.include <src.opts.mk>
PROG= sockstat
+SRCS= main.c sockstat.c
LIBADD= jail xo
@@ -13,4 +14,7 @@ LIBADD+= cap_sysctl
CFLAGS+= -DWITH_CASPER
.endif
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
.include <bsd.prog.mk>
diff --git a/usr.bin/sockstat/main.c b/usr.bin/sockstat/main.c
new file mode 100644
index 000000000000..b5e0248b743a
--- /dev/null
+++ b/usr.bin/sockstat/main.c
@@ -0,0 +1,1883 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2002 Dag-Erling 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/param.h>
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+#include <sys/jail.h>
+#include <sys/user.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+
+#include <sys/un.h>
+#include <sys/unpcb.h>
+
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet/in_pcb.h>
+#include <netinet/sctp.h>
+#include <netinet/tcp.h>
+#define TCPSTATES /* load state names */
+#include <netinet/tcp_fsm.h>
+#include <netinet/tcp_seq.h>
+#include <netinet/tcp_var.h>
+#include <arpa/inet.h>
+
+#include <capsicum_helpers.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <jail.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+
+#include <libcasper.h>
+#include <casper/cap_net.h>
+#include <casper/cap_netdb.h>
+#include <casper/cap_pwd.h>
+#include <casper/cap_sysctl.h>
+
+#include "sockstat.h"
+
+#define SOCKSTAT_XO_VERSION "1"
+#define sstosin(ss) ((struct sockaddr_in *)(ss))
+#define sstosin6(ss) ((struct sockaddr_in6 *)(ss))
+#define sstosun(ss) ((struct sockaddr_un *)(ss))
+#define sstosa(ss) ((struct sockaddr *)(ss))
+
+static bool opt_4; /* Show IPv4 sockets */
+static bool opt_6; /* Show IPv6 sockets */
+static bool opt_A; /* Show kernel address of pcb */
+static bool opt_C; /* Show congestion control */
+static bool opt_c; /* Show connected sockets */
+static bool opt_f; /* Show FIB numbers */
+static bool opt_I; /* Show spliced socket addresses */
+static bool opt_i; /* Show inp_gencnt */
+static int opt_j; /* Show specified jail */
+static bool opt_L; /* Don't show IPv4 or IPv6 loopback sockets */
+static bool opt_l; /* Show listening sockets */
+static bool opt_n; /* Don't resolve UIDs to user names */
+static bool opt_q; /* Don't show header */
+static bool opt_S; /* Show protocol stack if applicable */
+static bool opt_s; /* Show protocol state if applicable */
+static bool opt_U; /* Show remote UDP encapsulation port number */
+static bool opt_u; /* Show Unix domain sockets */
+static u_int opt_v; /* Verbose mode */
+static bool opt_w; /* Automatically size the columns */
+static bool is_xo_style_encoding;
+
+/*
+ * Default protocols to use if no -P was defined.
+ */
+static const char *default_protos[] = {"sctp", "tcp", "udp", "divert" };
+static size_t default_numprotos = nitems(default_protos);
+
+static int *protos; /* protocols to use */
+static size_t numprotos; /* allocated size of protos[] */
+
+struct addr {
+ union {
+ struct sockaddr_storage address;
+ struct { /* unix(4) faddr */
+ kvaddr_t conn;
+ kvaddr_t firstref;
+ kvaddr_t nextref;
+ };
+ };
+ unsigned int encaps_port;
+ int state;
+ struct addr *next;
+};
+
+struct sock {
+ union {
+ RB_ENTRY(sock) socket_tree; /* tree of pcbs with socket */
+ SLIST_ENTRY(sock) socket_list; /* list of pcbs w/o socket */
+ };
+ RB_ENTRY(sock) pcb_tree;
+ kvaddr_t socket;
+ kvaddr_t pcb;
+ kvaddr_t splice_socket;
+ uint64_t inp_gencnt;
+ int shown;
+ int vflag;
+ int family;
+ int proto;
+ int state;
+ int fibnum;
+ const char *protoname;
+ char stack[TCP_FUNCTION_NAME_LEN_MAX];
+ char cc[TCP_CA_NAME_MAX];
+ struct addr *laddr;
+ struct addr *faddr;
+};
+
+static RB_HEAD(socks_t, sock) socks = RB_INITIALIZER(&socks);
+static int64_t
+socket_compare(const struct sock *a, const struct sock *b)
+{
+ return ((int64_t)(a->socket/2 - b->socket/2));
+}
+RB_GENERATE_STATIC(socks_t, sock, socket_tree, socket_compare);
+
+static RB_HEAD(pcbs_t, sock) pcbs = RB_INITIALIZER(&pcbs);
+static int64_t
+pcb_compare(const struct sock *a, const struct sock *b)
+{
+ return ((int64_t)(a->pcb/2 - b->pcb/2));
+}
+RB_GENERATE_STATIC(pcbs_t, sock, pcb_tree, pcb_compare);
+
+static SLIST_HEAD(, sock) nosocks = SLIST_HEAD_INITIALIZER(&nosocks);
+
+struct file {
+ RB_ENTRY(file) file_tree;
+ kvaddr_t xf_data;
+ pid_t xf_pid;
+ uid_t xf_uid;
+ int xf_fd;
+};
+
+static RB_HEAD(files_t, file) ftree = RB_INITIALIZER(&ftree);
+static int64_t
+file_compare(const struct file *a, const struct file *b)
+{
+ return ((int64_t)(a->xf_data/2 - b->xf_data/2));
+}
+RB_GENERATE_STATIC(files_t, file, file_tree, file_compare);
+
+static struct file *files;
+static int nfiles;
+
+static cap_channel_t *capnet;
+static cap_channel_t *capnetdb;
+static cap_channel_t *capsysctl;
+static cap_channel_t *cappwd;
+
+static bool
+_check_ksize(size_t received_size, size_t expected_size, const char *struct_name)
+{
+ if (received_size != expected_size) {
+ xo_warnx("%s size mismatch: expected %zd, received %zd",
+ struct_name, expected_size, received_size);
+ return false;
+ }
+ return true;
+}
+#define check_ksize(_sz, _struct) (_check_ksize(_sz, sizeof(_struct), #_struct))
+
+static void
+_enforce_ksize(size_t received_size, size_t expected_size, const char *struct_name)
+{
+ if (received_size != expected_size) {
+ xo_errx(1, "fatal: struct %s size mismatch: expected %zd, received %zd",
+ struct_name, expected_size, received_size);
+ }
+}
+#define enforce_ksize(_sz, _struct) (_enforce_ksize(_sz, sizeof(_struct), #_struct))
+
+static int
+get_proto_type(const char *proto)
+{
+ struct protoent *pent;
+
+ if (strlen(proto) == 0)
+ return (0);
+ if (capnetdb != NULL)
+ pent = cap_getprotobyname(capnetdb, proto);
+ else
+ pent = getprotobyname(proto);
+ if (pent == NULL) {
+ xo_warn("cap_getprotobyname");
+ return (-1);
+ }
+ return (pent->p_proto);
+}
+
+static void
+init_protos(int num)
+{
+ int proto_count = 0;
+
+ if (num > 0) {
+ proto_count = num;
+ } else {
+ /* Find the maximum number of possible protocols. */
+ while (getprotoent() != NULL)
+ proto_count++;
+ endprotoent();
+ }
+
+ if ((protos = malloc(sizeof(int) * proto_count)) == NULL)
+ xo_err(1, "malloc");
+ numprotos = proto_count;
+}
+
+static int
+parse_protos(char *protospec)
+{
+ char *prot;
+ int proto_type, proto_index;
+
+ if (protospec == NULL)
+ return (-1);
+
+ init_protos(0);
+ proto_index = 0;
+ while ((prot = strsep(&protospec, ",")) != NULL) {
+ if (strlen(prot) == 0)
+ continue;
+ proto_type = get_proto_type(prot);
+ if (proto_type != -1)
+ protos[proto_index++] = proto_type;
+ }
+ numprotos = proto_index;
+ return (proto_index);
+}
+
+static void
+sockaddr(struct sockaddr_storage *ss, int af, void *addr, int port)
+{
+ struct sockaddr_in *sin4;
+ struct sockaddr_in6 *sin6;
+
+ bzero(ss, sizeof(*ss));
+ switch (af) {
+ case AF_INET:
+ sin4 = sstosin(ss);
+ 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 = sstosin6(ss);
+ sin6->sin6_len = sizeof(*sin6);
+ sin6->sin6_family = af;
+ sin6->sin6_port = port;
+ sin6->sin6_addr = *(struct in6_addr *)addr;
+#define s6_addr16 __u6_addr.__u6_addr16
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ sin6->sin6_scope_id =
+ ntohs(sin6->sin6_addr.s6_addr16[1]);
+ sin6->sin6_addr.s6_addr16[1] = 0;
+ }
+ break;
+ default:
+ abort();
+ }
+}
+
+static void
+free_socket(struct sock *sock)
+{
+ struct addr *cur, *next;
+
+ cur = sock->laddr;
+ while (cur != NULL) {
+ next = cur->next;
+ free(cur);
+ cur = next;
+ }
+ cur = sock->faddr;
+ while (cur != NULL) {
+ next = cur->next;
+ free(cur);
+ cur = next;
+ }
+ free(sock);
+}
+
+static void
+gather_sctp(void)
+{
+ struct sock *sock;
+ struct addr *laddr, *prev_laddr, *faddr, *prev_faddr;
+ struct xsctp_inpcb *xinpcb;
+ struct xsctp_tcb *xstcb;
+ struct xsctp_raddr *xraddr;
+ struct xsctp_laddr *xladdr;
+ const char *varname;
+ size_t len, offset;
+ char *buf;
+ int vflag;
+ int no_stcb, local_all_loopback, foreign_all_loopback;
+
+ vflag = 0;
+ if (opt_4)
+ vflag |= INP_IPV4;
+ if (opt_6)
+ vflag |= INP_IPV6;
+
+ varname = "net.inet.sctp.assoclist";
+ if (cap_sysctlbyname(capsysctl, varname, 0, &len, 0, 0) < 0) {
+ if (errno != ENOENT)
+ xo_err(1, "cap_sysctlbyname()");
+ return;
+ }
+ if ((buf = (char *)malloc(len)) == NULL) {
+ xo_err(1, "malloc()");
+ return;
+ }
+ if (cap_sysctlbyname(capsysctl, varname, buf, &len, 0, 0) < 0) {
+ xo_err(1, "cap_sysctlbyname()");
+ free(buf);
+ return;
+ }
+ xinpcb = (struct xsctp_inpcb *)(void *)buf;
+ offset = sizeof(struct xsctp_inpcb);
+ while ((offset < len) && (xinpcb->last == 0)) {
+ if ((sock = calloc(1, sizeof *sock)) == NULL)
+ xo_err(1, "malloc()");
+ sock->socket = xinpcb->socket;
+ sock->proto = IPPROTO_SCTP;
+ sock->protoname = "sctp";
+ if (xinpcb->maxqlen == 0)
+ sock->state = SCTP_CLOSED;
+ else
+ sock->state = SCTP_LISTEN;
+ if (xinpcb->flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ sock->family = AF_INET6;
+ /*
+ * Currently there is no way to distinguish between
+ * IPv6 only sockets or dual family sockets.
+ * So mark it as dual socket.
+ */
+ sock->vflag = INP_IPV6 | INP_IPV4;
+ } else {
+ sock->family = AF_INET;
+ sock->vflag = INP_IPV4;
+ }
+ prev_laddr = NULL;
+ local_all_loopback = 1;
+ while (offset < len) {
+ xladdr = (struct xsctp_laddr *)(void *)(buf + offset);
+ offset += sizeof(struct xsctp_laddr);
+ if (xladdr->last == 1)
+ break;
+ if ((laddr = calloc(1, sizeof(struct addr))) == NULL)
+ xo_err(1, "malloc()");
+ switch (xladdr->address.sa.sa_family) {
+ case AF_INET:
+#define __IN_IS_ADDR_LOOPBACK(pina) \
+ ((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)
+ if (!__IN_IS_ADDR_LOOPBACK(
+ &xladdr->address.sin.sin_addr))
+ local_all_loopback = 0;
+#undef __IN_IS_ADDR_LOOPBACK
+ sockaddr(&laddr->address, AF_INET,
+ &xladdr->address.sin.sin_addr,
+ htons(xinpcb->local_port));
+ break;
+ case AF_INET6:
+ if (!IN6_IS_ADDR_LOOPBACK(
+ &xladdr->address.sin6.sin6_addr))
+ local_all_loopback = 0;
+ sockaddr(&laddr->address, AF_INET6,
+ &xladdr->address.sin6.sin6_addr,
+ htons(xinpcb->local_port));
+ break;
+ default:
+ xo_errx(1, "address family %d not supported",
+ xladdr->address.sa.sa_family);
+ }
+ laddr->next = NULL;
+ if (prev_laddr == NULL)
+ sock->laddr = laddr;
+ else
+ prev_laddr->next = laddr;
+ prev_laddr = laddr;
+ }
+ if (sock->laddr == NULL) {
+ if ((sock->laddr =
+ calloc(1, sizeof(struct addr))) == NULL)
+ xo_err(1, "malloc()");
+ sock->laddr->address.ss_family = sock->family;
+ if (sock->family == AF_INET)
+ sock->laddr->address.ss_len =
+ sizeof(struct sockaddr_in);
+ else
+ sock->laddr->address.ss_len =
+ sizeof(struct sockaddr_in6);
+ local_all_loopback = 0;
+ }
+ if ((sock->faddr = calloc(1, sizeof(struct addr))) == NULL)
+ xo_err(1, "malloc()");
+ sock->faddr->address.ss_family = sock->family;
+ if (sock->family == AF_INET)
+ sock->faddr->address.ss_len =
+ sizeof(struct sockaddr_in);
+ else
+ sock->faddr->address.ss_len =
+ sizeof(struct sockaddr_in6);
+ no_stcb = 1;
+ while (offset < len) {
+ xstcb = (struct xsctp_tcb *)(void *)(buf + offset);
+ offset += sizeof(struct xsctp_tcb);
+ if (no_stcb) {
+ if (opt_l && (sock->vflag & vflag) &&
+ (!opt_L || !local_all_loopback) &&
+ ((xinpcb->flags & SCTP_PCB_FLAGS_UDPTYPE) ||
+ (xstcb->last == 1))) {
+ RB_INSERT(socks_t, &socks, sock);
+ } else {
+ free_socket(sock);
+ }
+ }
+ if (xstcb->last == 1)
+ break;
+ no_stcb = 0;
+ if (opt_c) {
+ if ((sock = calloc(1, sizeof *sock)) == NULL)
+ xo_err(1, "malloc()");
+ sock->socket = xinpcb->socket;
+ sock->proto = IPPROTO_SCTP;
+ sock->protoname = "sctp";
+ sock->state = (int)xstcb->state;
+ if (xinpcb->flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ sock->family = AF_INET6;
+ /*
+ * Currently there is no way to distinguish
+ * between IPv6 only sockets or dual family
+ * sockets. So mark it as dual socket.
+ */
+ sock->vflag = INP_IPV6 | INP_IPV4;
+ } else {
+ sock->family = AF_INET;
+ sock->vflag = INP_IPV4;
+ }
+ }
+ prev_laddr = NULL;
+ local_all_loopback = 1;
+ while (offset < len) {
+ xladdr = (struct xsctp_laddr *)(void *)(buf +
+ offset);
+ offset += sizeof(struct xsctp_laddr);
+ if (xladdr->last == 1)
+ break;
+ if (!opt_c)
+ continue;
+ laddr = calloc(1, sizeof(struct addr));
+ if (laddr == NULL)
+ xo_err(1, "malloc()");
+ switch (xladdr->address.sa.sa_family) {
+ case AF_INET:
+#define __IN_IS_ADDR_LOOPBACK(pina) \
+ ((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)
+ if (!__IN_IS_ADDR_LOOPBACK(
+ &xladdr->address.sin.sin_addr))
+ local_all_loopback = 0;
+#undef __IN_IS_ADDR_LOOPBACK
+ sockaddr(&laddr->address, AF_INET,
+ &xladdr->address.sin.sin_addr,
+ htons(xstcb->local_port));
+ break;
+ case AF_INET6:
+ if (!IN6_IS_ADDR_LOOPBACK(
+ &xladdr->address.sin6.sin6_addr))
+ local_all_loopback = 0;
+ sockaddr(&laddr->address, AF_INET6,
+ &xladdr->address.sin6.sin6_addr,
+ htons(xstcb->local_port));
+ break;
+ default:
+ xo_errx(1,
+ "address family %d not supported",
+ xladdr->address.sa.sa_family);
+ }
+ laddr->next = NULL;
+ if (prev_laddr == NULL)
+ sock->laddr = laddr;
+ else
+ prev_laddr->next = laddr;
+ prev_laddr = laddr;
+ }
+ prev_faddr = NULL;
+ foreign_all_loopback = 1;
+ while (offset < len) {
+ xraddr = (struct xsctp_raddr *)(void *)(buf +
+ offset);
+ offset += sizeof(struct xsctp_raddr);
+ if (xraddr->last == 1)
+ break;
+ if (!opt_c)
+ continue;
+ faddr = calloc(1, sizeof(struct addr));
+ if (faddr == NULL)
+ xo_err(1, "malloc()");
+ switch (xraddr->address.sa.sa_family) {
+ case AF_INET:
+#define __IN_IS_ADDR_LOOPBACK(pina) \
+ ((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)
+ if (!__IN_IS_ADDR_LOOPBACK(
+ &xraddr->address.sin.sin_addr))
+ foreign_all_loopback = 0;
+#undef __IN_IS_ADDR_LOOPBACK
+ sockaddr(&faddr->address, AF_INET,
+ &xraddr->address.sin.sin_addr,
+ htons(xstcb->remote_port));
+ break;
+ case AF_INET6:
+ if (!IN6_IS_ADDR_LOOPBACK(
+ &xraddr->address.sin6.sin6_addr))
+ foreign_all_loopback = 0;
+ sockaddr(&faddr->address, AF_INET6,
+ &xraddr->address.sin6.sin6_addr,
+ htons(xstcb->remote_port));
+ break;
+ default:
+ xo_errx(1,
+ "address family %d not supported",
+ xraddr->address.sa.sa_family);
+ }
+ faddr->encaps_port = xraddr->encaps_port;
+ faddr->state = xraddr->state;
+ faddr->next = NULL;
+ if (prev_faddr == NULL)
+ sock->faddr = faddr;
+ else
+ prev_faddr->next = faddr;
+ prev_faddr = faddr;
+ }
+ if (opt_c) {
+ if ((sock->vflag & vflag) &&
+ (!opt_L ||
+ !(local_all_loopback ||
+ foreign_all_loopback))) {
+ RB_INSERT(socks_t, &socks, sock);
+ } else {
+ free_socket(sock);
+ }
+ }
+ }
+ xinpcb = (struct xsctp_inpcb *)(void *)(buf + offset);
+ offset += sizeof(struct xsctp_inpcb);
+ }
+ free(buf);
+}
+
+static void
+gather_inet(int proto)
+{
+ struct xinpgen *xig, *exig;
+ struct xinpcb *xip;
+ struct xtcpcb *xtp = NULL;
+ struct xsocket *so;
+ struct sock *sock;
+ struct addr *laddr, *faddr;
+ const char *varname, *protoname;
+ size_t len, bufsize;
+ void *buf;
+ int 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;
+ case IPPROTO_DIVERT:
+ varname = "net.inet.divert.pcblist";
+ protoname = "div";
+ break;
+ default:
+ xo_errx(1, "protocol %d not supported", proto);
+ }
+
+ buf = NULL;
+ bufsize = 8192;
+ retry = 5;
+ do {
+ for (;;) {
+ if ((buf = realloc(buf, bufsize)) == NULL)
+ xo_err(1, "realloc()");
+ len = bufsize;
+ if (cap_sysctlbyname(capsysctl, varname, buf, &len,
+ NULL, 0) == 0)
+ break;
+ if (errno == ENOENT)
+ goto out;
+ if (errno != ENOMEM || len != bufsize)
+ xo_err(1, "cap_sysctlbyname()");
+ bufsize *= 2;
+ }
+ xig = (struct xinpgen *)buf;
+ exig = (struct xinpgen *)(void *)
+ ((char *)buf + len - sizeof *exig);
+ enforce_ksize(xig->xig_len, struct xinpgen);
+ enforce_ksize(exig->xig_len, struct xinpgen);
+ } while (xig->xig_gen != exig->xig_gen && retry--);
+
+ if (xig->xig_gen != exig->xig_gen && opt_v)
+ xo_warnx("warning: data may be inconsistent");
+
+ for (;;) {
+ xig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len);
+ if (xig >= exig)
+ break;
+ switch (proto) {
+ case IPPROTO_TCP:
+ xtp = (struct xtcpcb *)xig;
+ xip = &xtp->xt_inp;
+ if (!check_ksize(xtp->xt_len, struct xtcpcb))
+ goto out;
+ protoname = xtp->t_flags & TF_TOE ? "toe" : "tcp";
+ break;
+ case IPPROTO_UDP:
+ case IPPROTO_DIVERT:
+ xip = (struct xinpcb *)xig;
+ if (!check_ksize(xip->xi_len, struct xinpcb))
+ goto out;
+ break;
+ default:
+ xo_errx(1, "protocol %d not supported", proto);
+ }
+ so = &xip->xi_socket;
+ if ((xip->inp_vflag & vflag) == 0)
+ continue;
+ if (xip->inp_vflag & INP_IPV4) {
+ if ((xip->inp_fport == 0 && !opt_l) ||
+ (xip->inp_fport != 0 && !opt_c))
+ continue;
+#define __IN_IS_ADDR_LOOPBACK(pina) \
+ ((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)
+ if (opt_L &&
+ (__IN_IS_ADDR_LOOPBACK(&xip->inp_faddr) ||
+ __IN_IS_ADDR_LOOPBACK(&xip->inp_laddr)))
+ continue;
+#undef __IN_IS_ADDR_LOOPBACK
+ } else if (xip->inp_vflag & INP_IPV6) {
+ if ((xip->inp_fport == 0 && !opt_l) ||
+ (xip->inp_fport != 0 && !opt_c))
+ continue;
+ if (opt_L &&
+ (IN6_IS_ADDR_LOOPBACK(&xip->in6p_faddr) ||
+ IN6_IS_ADDR_LOOPBACK(&xip->in6p_laddr)))
+ continue;
+ } else {
+ if (opt_v)
+ xo_warnx("invalid vflag 0x%x", xip->inp_vflag);
+ continue;
+ }
+ if ((sock = calloc(1, sizeof(*sock))) == NULL)
+ xo_err(1, "malloc()");
+ if ((laddr = calloc(1, sizeof *laddr)) == NULL)
+ xo_err(1, "malloc()");
+ if ((faddr = calloc(1, sizeof *faddr)) == NULL)
+ xo_err(1, "malloc()");
+ sock->socket = so->xso_so;
+ sock->pcb = so->so_pcb;
+ sock->splice_socket = so->so_splice_so;
+ sock->proto = proto;
+ sock->inp_gencnt = xip->inp_gencnt;
+ sock->fibnum = so->so_fibnum;
+ if (xip->inp_vflag & INP_IPV4) {
+ sock->family = AF_INET;
+ sockaddr(&laddr->address, sock->family,
+ &xip->inp_laddr, xip->inp_lport);
+ sockaddr(&faddr->address, sock->family,
+ &xip->inp_faddr, xip->inp_fport);
+ } else if (xip->inp_vflag & INP_IPV6) {
+ sock->family = AF_INET6;
+ sockaddr(&laddr->address, sock->family,
+ &xip->in6p_laddr, xip->inp_lport);
+ sockaddr(&faddr->address, sock->family,
+ &xip->in6p_faddr, xip->inp_fport);
+ }
+ if (proto == IPPROTO_TCP)
+ faddr->encaps_port = xtp->xt_encaps_port;
+ laddr->next = NULL;
+ faddr->next = NULL;
+ sock->laddr = laddr;
+ sock->faddr = faddr;
+ sock->vflag = xip->inp_vflag;
+ if (proto == IPPROTO_TCP) {
+ sock->state = xtp->t_state;
+ memcpy(sock->stack, xtp->xt_stack,
+ TCP_FUNCTION_NAME_LEN_MAX);
+ memcpy(sock->cc, xtp->xt_cc, TCP_CA_NAME_MAX);
+ }
+ sock->protoname = protoname;
+ if (sock->socket != 0)
+ RB_INSERT(socks_t, &socks, sock);
+ else
+ SLIST_INSERT_HEAD(&nosocks, sock, socket_list);
+ }
+out:
+ free(buf);
+}
+
+static void
+gather_unix(int proto)
+{
+ struct xunpgen *xug, *exug;
+ struct xunpcb *xup;
+ struct sock *sock;
+ struct addr *laddr, *faddr;
+ const char *varname, *protoname;
+ size_t len, bufsize;
+ void *buf;
+ int 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;
+ case SOCK_SEQPACKET:
+ varname = "net.local.seqpacket.pcblist";
+ protoname = is_xo_style_encoding ? "seqpacket" : "seqpack";
+ break;
+ default:
+ abort();
+ }
+ buf = NULL;
+ bufsize = 8192;
+ retry = 5;
+ do {
+ for (;;) {
+ if ((buf = realloc(buf, bufsize)) == NULL)
+ xo_err(1, "realloc()");
+ len = bufsize;
+ if (cap_sysctlbyname(capsysctl, varname, buf, &len,
+ NULL, 0) == 0)
+ break;
+ if (errno != ENOMEM || len != bufsize)
+ xo_err(1, "cap_sysctlbyname()");
+ bufsize *= 2;
+ }
+ xug = (struct xunpgen *)buf;
+ exug = (struct xunpgen *)(void *)
+ ((char *)buf + len - sizeof(*exug));
+ if (!check_ksize(xug->xug_len, struct xunpgen) ||
+ !check_ksize(exug->xug_len, struct xunpgen))
+ goto out;
+ } while (xug->xug_gen != exug->xug_gen && retry--);
+
+ if (xug->xug_gen != exug->xug_gen && opt_v)
+ xo_warnx("warning: data may be inconsistent");
+
+ for (;;) {
+ xug = (struct xunpgen *)(void *)((char *)xug + xug->xug_len);
+ if (xug >= exug)
+ break;
+ xup = (struct xunpcb *)xug;
+ if (!check_ksize(xup->xu_len, struct xunpcb))
+ goto out;
+ if ((xup->unp_conn == 0 && !opt_l) ||
+ (xup->unp_conn != 0 && !opt_c))
+ continue;
+ if ((sock = calloc(1, sizeof(*sock))) == NULL)
+ xo_err(1, "malloc()");
+ if ((laddr = calloc(1, sizeof *laddr)) == NULL)
+ xo_err(1, "malloc()");
+ if ((faddr = calloc(1, sizeof *faddr)) == NULL)
+ xo_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_addr.sun_family == AF_UNIX)
+ laddr->address =
+ *(struct sockaddr_storage *)(void *)&xup->xu_addr;
+ faddr->conn = xup->unp_conn;
+ faddr->firstref = xup->xu_firstref;
+ faddr->nextref = xup->xu_nextref;
+ laddr->next = NULL;
+ faddr->next = NULL;
+ sock->laddr = laddr;
+ sock->faddr = faddr;
+ RB_INSERT(socks_t, &socks, sock);
+ RB_INSERT(pcbs_t, &pcbs, sock);
+ }
+out:
+ free(buf);
+}
+
+static void
+getfiles(void)
+{
+ struct xfile *xfiles;
+ size_t len, olen;
+
+ olen = len = sizeof(*xfiles);
+ if ((xfiles = malloc(len)) == NULL)
+ xo_err(1, "malloc()");
+ while (cap_sysctlbyname(capsysctl, "kern.file", xfiles, &len, 0, 0)
+ == -1) {
+ if (errno != ENOMEM || len != olen)
+ xo_err(1, "cap_sysctlbyname()");
+ olen = len *= 2;
+ if ((xfiles = realloc(xfiles, len)) == NULL)
+ xo_err(1, "realloc()");
+ }
+ if (len > 0)
+ enforce_ksize(xfiles->xf_size, struct xfile);
+ nfiles = len / sizeof(*xfiles);
+
+ if ((files = malloc(nfiles * sizeof(struct file))) == NULL)
+ xo_err(1, "malloc()");
+
+ for (int i = 0; i < nfiles; i++) {
+ files[i].xf_data = xfiles[i].xf_data;
+ files[i].xf_pid = xfiles[i].xf_pid;
+ files[i].xf_uid = xfiles[i].xf_uid;
+ files[i].xf_fd = xfiles[i].xf_fd;
+ RB_INSERT(files_t, &ftree, &files[i]);
+ }
+
+ free(xfiles);
+}
+
+static int
+formataddr(struct sockaddr_storage *ss, char *buf, size_t bufsize)
+{
+ struct sockaddr_un *sun;
+ char addrstr[NI_MAXHOST] = { '\0', '\0' };
+ int error, off, port = 0;
+
+ switch (ss->ss_family) {
+ case AF_INET:
+ if (sstosin(ss)->sin_addr.s_addr == INADDR_ANY)
+ addrstr[0] = '*';
+ port = ntohs(sstosin(ss)->sin_port);
+ break;
+ case AF_INET6:
+ if (IN6_IS_ADDR_UNSPECIFIED(&sstosin6(ss)->sin6_addr))
+ addrstr[0] = '*';
+ port = ntohs(sstosin6(ss)->sin6_port);
+ break;
+ case AF_UNIX:
+ sun = sstosun(ss);
+ off = (int)((char *)&sun->sun_path - (char *)sun);
+ if (is_xo_style_encoding) {
+ xo_emit("{:path/%.*s}", sun->sun_len - off,
+ sun->sun_path);
+ return 0;
+ }
+ return snprintf(buf, bufsize, "%.*s",
+ sun->sun_len - off, sun->sun_path);
+ }
+ if (addrstr[0] == '\0') {
+ error = cap_getnameinfo(capnet, sstosa(ss), ss->ss_len,
+ addrstr, sizeof(addrstr), NULL, 0, NI_NUMERICHOST);
+ if (error)
+ xo_errx(1, "cap_getnameinfo()");
+ }
+ if (is_xo_style_encoding) {
+ xo_emit("{:address/%s}", addrstr);
+ xo_emit("{:port/%d}", port);
+ return 0;
+ }
+ if (port == 0)
+ return snprintf(buf, bufsize, "%s:*", addrstr);
+ return snprintf(buf, bufsize, "%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 (cap_sysctl(capsysctl, mib, nitems(mib), &proc, &len, NULL, 0)
+ == -1) {
+ /* Do not warn if the process exits before we get its name. */
+ if (errno != ESRCH)
+ xo_warn("cap_sysctl()");
+ return ("??");
+ }
+ return (proc.ki_comm);
+}
+
+static int
+getprocjid(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 (cap_sysctl(capsysctl, mib, nitems(mib), &proc, &len, NULL, 0)
+ == -1) {
+ /* Do not warn if the process exits before we get its jid. */
+ if (errno != ESRCH)
+ xo_warn("cap_sysctl()");
+ return (-1);
+ }
+ return (proc.ki_jid);
+}
+
+static int
+check_ports(struct sock *s)
+{
+ int port;
+ struct addr *addr;
+
+ if (ports == NULL)
+ return (1);
+ if ((s->family != AF_INET) && (s->family != AF_INET6))
+ return (1);
+ for (addr = s->laddr; addr != NULL; addr = addr->next) {
+ if (s->family == AF_INET)
+ port = ntohs(sstosin(&addr->address)->sin_port);
+ else
+ port = ntohs(sstosin6(&addr->address)->sin6_port);
+ if (CHK_PORT(port))
+ return (1);
+ }
+ for (addr = s->faddr; addr != NULL; addr = addr->next) {
+ if (s->family == AF_INET)
+ port = ntohs(sstosin(&addr->address)->sin_port);
+ else
+ port = ntohs(sstosin6(&addr->address)->sin6_port);
+ if (CHK_PORT(port))
+ return (1);
+ }
+ return (0);
+}
+
+static const char *
+sctp_conn_state(int state)
+{
+ switch (state) {
+ case SCTP_CLOSED:
+ return "CLOSED";
+ break;
+ case SCTP_BOUND:
+ return "BOUND";
+ break;
+ case SCTP_LISTEN:
+ return "LISTEN";
+ break;
+ case SCTP_COOKIE_WAIT:
+ return "COOKIE_WAIT";
+ break;
+ case SCTP_COOKIE_ECHOED:
+ return "COOKIE_ECHOED";
+ break;
+ case SCTP_ESTABLISHED:
+ return "ESTABLISHED";
+ break;
+ case SCTP_SHUTDOWN_SENT:
+ return "SHUTDOWN_SENT";
+ break;
+ case SCTP_SHUTDOWN_RECEIVED:
+ return "SHUTDOWN_RECEIVED";
+ break;
+ case SCTP_SHUTDOWN_ACK_SENT:
+ return "SHUTDOWN_ACK_SENT";
+ break;
+ case SCTP_SHUTDOWN_PENDING:
+ return "SHUTDOWN_PENDING";
+ break;
+ default:
+ return "UNKNOWN";
+ break;
+ }
+}
+
+static const char *
+sctp_path_state(int state)
+{
+ switch (state) {
+ case SCTP_UNCONFIRMED:
+ return "UNCONFIRMED";
+ break;
+ case SCTP_ACTIVE:
+ return "ACTIVE";
+ break;
+ case SCTP_INACTIVE:
+ return "INACTIVE";
+ break;
+ default:
+ return "UNKNOWN";
+ break;
+ }
+}
+
+static int
+format_unix_faddr(struct addr *faddr, char *buf, size_t bufsize) {
+ #define SAFEBUF (buf == NULL ? NULL : buf + pos)
+ #define SAFESIZE (buf == NULL ? 0 : bufsize - pos)
+
+ size_t pos = 0;
+ if (faddr->conn != 0) {
+ /* Remote peer we connect(2) to, if any. */
+ struct sock *p;
+ if (!is_xo_style_encoding)
+ pos += strlcpy(SAFEBUF, "-> ", SAFESIZE);
+ p = RB_FIND(pcbs_t, &pcbs,
+ &(struct sock){ .pcb = faddr->conn });
+ if (__predict_false(p == NULL) && !is_xo_style_encoding) {
+ /* XXGL: can this happen at all? */
+ pos += snprintf(SAFEBUF, SAFESIZE, "??");
+ } else if (p->laddr->address.ss_len == 0) {
+ struct file *f;
+ f = RB_FIND(files_t, &ftree,
+ &(struct file){ .xf_data =
+ p->socket });
+ if (f != NULL) {
+ if (!is_xo_style_encoding) {
+ pos += snprintf(SAFEBUF, SAFESIZE,
+ "[%lu %d]", (u_long)f->xf_pid,
+ f->xf_fd);
+ } else {
+ xo_open_list("connections");
+ xo_open_instance("connections");
+ xo_emit("{:pid/%lu}", (u_long)f->xf_pid);
+ xo_emit("{:fd/%d}", f->xf_fd);
+ xo_close_instance("connections");
+ xo_close_list("connections");
+ }
+ }
+ } else
+ pos += formataddr(&p->laddr->address,
+ SAFEBUF, SAFESIZE);
+ } else if (faddr->firstref != 0) {
+ /* Remote peer(s) connect(2)ed to us, if any. */
+ struct sock *p;
+ struct file *f;
+ kvaddr_t ref = faddr->firstref;
+ bool fref = true;
+
+ if (!is_xo_style_encoding)
+ pos += snprintf(SAFEBUF, SAFESIZE, " <- ");
+ xo_open_list("connections");
+ while ((p = RB_FIND(pcbs_t, &pcbs,
+ &(struct sock){ .pcb = ref })) != 0) {
+ f = RB_FIND(files_t, &ftree,
+ &(struct file){ .xf_data = p->socket });
+ if (f != NULL) {
+ if (!is_xo_style_encoding) {
+ pos += snprintf(SAFEBUF, SAFESIZE,
+ "%s[%lu %d]", fref ? "" : ",",
+ (u_long)f->xf_pid, f->xf_fd);
+ } else {
+ xo_open_instance("connections");
+ xo_emit("{:pid/%lu}", (u_long)f->xf_pid);
+ xo_emit("{:fd/%d}", f->xf_fd);
+ xo_close_instance("connections");
+ }
+ }
+ ref = p->faddr->nextref;
+ fref = false;
+ }
+ xo_close_list("connections");
+ }
+ return pos;
+}
+
+struct col_widths {
+ int user;
+ int command;
+ int pid;
+ int fd;
+ int proto;
+ int local_addr;
+ int foreign_addr;
+ int pcb_kva;
+ int fib;
+ int splice_address;
+ int inp_gencnt;
+ int encaps;
+ int path_state;
+ int conn_state;
+ int stack;
+ int cc;
+};
+
+static void
+calculate_sock_column_widths(struct col_widths *cw, struct sock *s)
+{
+ struct addr *laddr, *faddr;
+ bool first = true;
+ int len = 0;
+ laddr = s->laddr;
+ faddr = s->faddr;
+ first = true;
+
+ len = strlen(s->protoname);
+ if (s->vflag & (INP_IPV4 | INP_IPV6))
+ len += 1;
+ cw->proto = MAX(cw->proto, len);
+
+ while (laddr != NULL || faddr != NULL) {
+ if (opt_w && s->family == AF_UNIX) {
+ if ((laddr == NULL) || (faddr == NULL))
+ xo_errx(1, "laddr = %p or faddr = %p is NULL",
+ (void *)laddr, (void *)faddr);
+ if (laddr->address.ss_len > 0)
+ len = formataddr(&laddr->address, NULL, 0);
+ cw->local_addr = MAX(cw->local_addr, len);
+ len = format_unix_faddr(faddr, NULL, 0);
+ cw->foreign_addr = MAX(cw->foreign_addr, len);
+ } else if (opt_w) {
+ if (laddr != NULL) {
+ len = formataddr(&laddr->address, NULL, 0);
+ cw->local_addr = MAX(cw->local_addr, len);
+ }
+ if (faddr != NULL) {
+ len = formataddr(&faddr->address, NULL, 0);
+ cw->foreign_addr = MAX(cw->foreign_addr, len);
+ }
+ }
+ if (opt_f) {
+ len = snprintf(NULL, 0, "%d", s->fibnum);
+ cw->fib = MAX(cw->fib, len);
+ }
+ if (opt_I) {
+ if (s->splice_socket != 0) {
+ struct sock *sp;
+
+ sp = RB_FIND(socks_t, &socks, &(struct sock)
+ { .socket = s->splice_socket });
+ if (sp != NULL) {
+ len = formataddr(&sp->laddr->address,
+ NULL, 0);
+ cw->splice_address = MAX(
+ cw->splice_address, len);
+ }
+ }
+ }
+ if (opt_i) {
+ if (s->proto == IPPROTO_TCP || s->proto == IPPROTO_UDP)
+ {
+ len = snprintf(NULL, 0,
+ "%" PRIu64, s->inp_gencnt);
+ cw->inp_gencnt = MAX(cw->inp_gencnt, len);
+ }
+ }
+ if (opt_U) {
+ if (faddr != NULL &&
+ ((s->proto == IPPROTO_SCTP &&
+ s->state != SCTP_CLOSED &&
+ s->state != SCTP_BOUND &&
+ s->state != SCTP_LISTEN) ||
+ (s->proto == IPPROTO_TCP &&
+ s->state != TCPS_CLOSED &&
+ s->state != TCPS_LISTEN))) {
+ len = snprintf(NULL, 0, "%u",
+ ntohs(faddr->encaps_port));
+ cw->encaps = MAX(cw->encaps, len);
+ }
+ }
+ if (opt_s) {
+ if (faddr != NULL &&
+ s->proto == IPPROTO_SCTP &&
+ s->state != SCTP_CLOSED &&
+ s->state != SCTP_BOUND &&
+ s->state != SCTP_LISTEN) {
+ len = strlen(sctp_path_state(faddr->state));
+ cw->path_state = MAX(cw->path_state, len);
+ }
+ }
+ if (first) {
+ if (opt_s) {
+ if (s->proto == IPPROTO_SCTP ||
+ s->proto == IPPROTO_TCP) {
+ switch (s->proto) {
+ case IPPROTO_SCTP:
+ len = strlen(
+ sctp_conn_state(s->state));
+ cw->conn_state = MAX(
+ cw->conn_state, len);
+ break;
+ case IPPROTO_TCP:
+ if (s->state >= 0 &&
+ s->state < TCP_NSTATES) {
+ len = strlen(
+ tcpstates[s->state]);
+ cw->conn_state = MAX(
+ cw->conn_state, len);
+ }
+ break;
+ }
+ }
+ }
+ if (opt_S && s->proto == IPPROTO_TCP) {
+ len = strlen(s->stack);
+ cw->stack = MAX(cw->stack, len);
+ }
+ if (opt_C && s->proto == IPPROTO_TCP) {
+ len = strlen(s->cc);
+ cw->cc = MAX(cw->cc, len);
+ }
+ }
+ if (laddr != NULL)
+ laddr = laddr->next;
+ if (faddr != NULL)
+ faddr = faddr->next;
+ first = false;
+ }
+}
+
+static void
+calculate_column_widths(struct col_widths *cw)
+{
+ int n, len;
+ struct file *xf;
+ struct sock *s;
+ struct passwd *pwd;
+
+ cap_setpassent(cappwd, 1);
+ for (xf = files, n = 0; n < nfiles; ++n, ++xf) {
+ if (xf->xf_data == 0)
+ continue;
+ if (opt_j >= 0 && opt_j != getprocjid(xf->xf_pid))
+ continue;
+ s = RB_FIND(socks_t, &socks,
+ &(struct sock){ .socket = xf->xf_data});
+ if (s == NULL || (!check_ports(s)))
+ continue;
+ s->shown = 1;
+ if (opt_n ||
+ (pwd = cap_getpwuid(cappwd, xf->xf_uid)) == NULL)
+ len = snprintf(NULL, 0, "%lu", (u_long)xf->xf_uid);
+ else
+ len = snprintf(NULL, 0, "%s", pwd->pw_name);
+ cw->user = MAX(cw->user, len);
+ len = snprintf(NULL, 0, "%lu", (u_long)xf->xf_pid);
+ cw->pid = MAX(cw->pid, len);
+ len = snprintf(NULL, 0, "%d", xf->xf_fd);
+ cw->fd = MAX(cw->fd, len);
+
+ calculate_sock_column_widths(cw, s);
+ }
+ if (opt_j >= 0)
+ return;
+ SLIST_FOREACH(s, &nosocks, socket_list) {
+ if (!check_ports(s))
+ continue;
+ calculate_sock_column_widths(cw, s);
+ }
+ RB_FOREACH(s, socks_t, &socks) {
+ if (s->shown)
+ continue;
+ if (!check_ports(s))
+ continue;
+ calculate_sock_column_widths(cw, s);
+ }
+}
+
+static void
+display_sock(struct sock *s, struct col_widths *cw, char *buf, size_t bufsize)
+{
+ struct addr *laddr, *faddr;
+ bool first;
+ laddr = s->laddr;
+ faddr = s->faddr;
+ first = true;
+
+ snprintf(buf, bufsize, "%s%s%s",
+ s->protoname,
+ s->vflag & INP_IPV4 ? "4" : "",
+ s->vflag & INP_IPV6 ? "6" : "");
+ xo_emit(" {:proto/%-*s}", cw->proto, buf);
+ while (laddr != NULL || faddr != NULL) {
+ if (s->family == AF_UNIX) {
+ if ((laddr == NULL) || (faddr == NULL))
+ xo_errx(1, "laddr = %p or faddr = %p is NULL",
+ (void *)laddr, (void *)faddr);
+ if (laddr->address.ss_len > 0) {
+ xo_open_container("local");
+ formataddr(&laddr->address, buf, bufsize);
+ if (!is_xo_style_encoding) {
+ xo_emit(" {:local-address/%-*.*s}",
+ cw->local_addr, cw->local_addr,
+ buf);
+ }
+ xo_close_container("local");
+ } else if (laddr->address.ss_len == 0 &&
+ faddr->conn == 0 && !is_xo_style_encoding) {
+ xo_emit(" {:local-address/%-*.*s}",
+ cw->local_addr, cw->local_addr,
+ "(not connected)");
+ } else if (!is_xo_style_encoding) {
+ xo_emit(" {:local-address/%-*.*s}",
+ cw->local_addr, cw->local_addr, "??");
+ }
+ if (faddr->conn != 0 || faddr->firstref != 0) {
+ xo_open_container("foreign");
+ int len = format_unix_faddr(faddr, buf,
+ bufsize);
+ if (len == 0 && !is_xo_style_encoding)
+ xo_emit(" {:foreign-address/%-*s}",
+ cw->foreign_addr, "??");
+ else if (!is_xo_style_encoding)
+ xo_emit(" {:foreign-address/%-*.*s}",
+ cw->foreign_addr,
+ cw->foreign_addr, buf);
+ xo_close_container("foreign");
+ } else if (!is_xo_style_encoding)
+ xo_emit(" {:foreign-address/%-*s}",
+ cw->foreign_addr, "??");
+ } else {
+ if (laddr != NULL) {
+ xo_open_container("local");
+ formataddr(&laddr->address, buf, bufsize);
+ if (!is_xo_style_encoding) {
+ xo_emit(" {:local-address/%-*.*s}",
+ cw->local_addr, cw->local_addr,
+ buf);
+ }
+ xo_close_container("local");
+ } else if (!is_xo_style_encoding)
+ xo_emit(" {:local-address/%-*.*s}",
+ cw->local_addr, cw->local_addr, "??");
+ if (faddr != NULL) {
+ xo_open_container("foreign");
+ formataddr(&faddr->address, buf, bufsize);
+ if (!is_xo_style_encoding) {
+ xo_emit(" {:foreign-address/%-*.*s}",
+ cw->foreign_addr,
+ cw->foreign_addr, buf);
+ }
+ xo_close_container("foreign");
+ } else if (!is_xo_style_encoding) {
+ xo_emit(" {:foreign-address/%-*.*s}",
+ cw->foreign_addr, cw->foreign_addr,
+ "??");
+ }
+ }
+ if (opt_A) {
+ snprintf(buf, bufsize, "%#*" PRIx64,
+ cw->pcb_kva, s->pcb);
+ xo_emit(" {:pcb-kva/%s}", buf);
+ }
+ if (opt_f)
+ xo_emit(" {:fib/%*d}", cw->fib, s->fibnum);
+ if (opt_I) {
+ if (s->splice_socket != 0) {
+ struct sock *sp;
+ sp = RB_FIND(socks_t, &socks, &(struct sock)
+ { .socket = s->splice_socket });
+ if (sp != NULL) {
+ xo_open_container("splice");
+ formataddr(&sp->laddr->address,
+ buf, bufsize);
+ xo_close_container("splice");
+ } else if (!is_xo_style_encoding)
+ strlcpy(buf, "??", bufsize);
+ } else if (!is_xo_style_encoding)
+ strlcpy(buf, "??", bufsize);
+ if (!is_xo_style_encoding)
+ xo_emit(" {:splice-address/%-*s}",
+ cw->splice_address, buf);
+ }
+ if (opt_i) {
+ if (s->proto == IPPROTO_TCP || s->proto == IPPROTO_UDP)
+ {
+ snprintf(buf, bufsize, "%" PRIu64,
+ s->inp_gencnt);
+ xo_emit(" {:id/%*s}", cw->inp_gencnt, buf);
+ } else if (!is_xo_style_encoding)
+ xo_emit(" {:id/%*s}", cw->inp_gencnt, "??");
+ }
+ if (opt_U) {
+ if (faddr != NULL &&
+ ((s->proto == IPPROTO_SCTP &&
+ s->state != SCTP_CLOSED &&
+ s->state != SCTP_BOUND &&
+ s->state != SCTP_LISTEN) ||
+ (s->proto == IPPROTO_TCP &&
+ s->state != TCPS_CLOSED &&
+ s->state != TCPS_LISTEN))) {
+ xo_emit(" {:encaps/%*u}", cw->encaps,
+ ntohs(faddr->encaps_port));
+ } else if (!is_xo_style_encoding)
+ xo_emit(" {:encaps/%*s}", cw->encaps, "??");
+ }
+ if (opt_s) {
+ if (faddr != NULL &&
+ s->proto == IPPROTO_SCTP &&
+ s->state != SCTP_CLOSED &&
+ s->state != SCTP_BOUND &&
+ s->state != SCTP_LISTEN) {
+ xo_emit(" {:path-state/%-*s}", cw->path_state,
+ sctp_path_state(faddr->state));
+ } else if (!is_xo_style_encoding)
+ xo_emit(" {:path-state/%-*s}", cw->path_state,
+ "??");
+ }
+ if (first) {
+ if (opt_s) {
+ if (s->proto == IPPROTO_SCTP ||
+ s->proto == IPPROTO_TCP) {
+ switch (s->proto) {
+ case IPPROTO_SCTP:
+ xo_emit(" {:conn-state/%-*s}",
+ cw->conn_state,
+ sctp_conn_state(s->state));
+ break;
+ case IPPROTO_TCP:
+ if (s->state >= 0 &&
+ s->state < TCP_NSTATES)
+ xo_emit(" {:conn-state/%-*s}",
+ cw->conn_state,
+ tcpstates[s->state]);
+ else if (!is_xo_style_encoding)
+ xo_emit(" {:conn-state/%-*s}",
+ cw->conn_state, "??");
+ break;
+ }
+ } else if (!is_xo_style_encoding)
+ xo_emit(" {:conn-state/%-*s}",
+ cw->conn_state, "??");
+ }
+ if (opt_S) {
+ if (s->proto == IPPROTO_TCP)
+ xo_emit(" {:stack/%-*s}",
+ cw->stack, s->stack);
+ else if (!is_xo_style_encoding)
+ xo_emit(" {:stack/%-*s}",
+ cw->stack, "??");
+ }
+ if (opt_C) {
+ if (s->proto == IPPROTO_TCP)
+ xo_emit(" {:cc/%-*s}", cw->cc, s->cc);
+ else if (!is_xo_style_encoding)
+ xo_emit(" {:cc/%-*s}", cw->cc, "??");
+ }
+ }
+ if (laddr != NULL)
+ laddr = laddr->next;
+ if (faddr != NULL)
+ faddr = faddr->next;
+ if (!is_xo_style_encoding && (laddr != NULL || faddr != NULL))
+ xo_emit("{:user/%-*s} {:command/%-*s} {:pid/%*s}"
+ " {:fd/%*s}", cw->user, "??", cw->command, "??",
+ cw->pid, "??", cw->fd, "??");
+ first = false;
+ }
+ xo_emit("\n");
+}
+
+static void
+display(void)
+{
+ struct passwd *pwd;
+ struct file *xf;
+ struct sock *s;
+ int n;
+ struct col_widths cw;
+ const size_t bufsize = 512;
+ void *buf;
+ if ((buf = (char *)malloc(bufsize)) == NULL) {
+ xo_err(1, "malloc()");
+ return;
+ }
+
+ if (!is_xo_style_encoding) {
+ cw = (struct col_widths) {
+ .user = strlen("USER"),
+ .command = 10,
+ .pid = strlen("PID"),
+ .fd = strlen("FD"),
+ .proto = strlen("PROTO"),
+ .local_addr = opt_w ? strlen("LOCAL ADDRESS") : 21,
+ .foreign_addr = opt_w ? strlen("FOREIGN ADDRESS") : 21,
+ .pcb_kva = 18,
+ .fib = strlen("FIB"),
+ .splice_address = strlen("SPLICE ADDRESS"),
+ .inp_gencnt = strlen("ID"),
+ .encaps = strlen("ENCAPS"),
+ .path_state = strlen("PATH STATE"),
+ .conn_state = strlen("CONN STATE"),
+ .stack = strlen("STACK"),
+ .cc = strlen("CC"),
+ };
+ calculate_column_widths(&cw);
+ } else
+ memset(&cw, 0, sizeof(cw));
+
+ xo_set_version(SOCKSTAT_XO_VERSION);
+ xo_open_container("sockstat");
+ xo_open_list("socket");
+ if (!opt_q) {
+ xo_emit("{T:/%-*s} {T:/%-*s} {T:/%*s} {T:/%*s} {T:/%-*s} "
+ "{T:/%-*s} {T:/%-*s}", cw.user, "USER", cw.command,
+ "COMMAND", cw.pid, "PID", cw.fd, "FD", cw.proto,
+ "PROTO", cw.local_addr, "LOCAL ADDRESS",
+ cw.foreign_addr, "FOREIGN ADDRESS");
+ if (opt_A)
+ xo_emit(" {T:/%-*s}", cw.pcb_kva, "PCB KVA");
+ if (opt_f)
+ /* RT_MAXFIBS is 65535. */
+ xo_emit(" {T:/%*s}", cw.fib, "FIB");
+ if (opt_I)
+ xo_emit(" {T:/%-*s}", cw.splice_address,
+ "SPLICE ADDRESS");
+ if (opt_i)
+ xo_emit(" {T:/%*s}", cw.inp_gencnt, "ID");
+ if (opt_U)
+ xo_emit(" {T:/%*s}", cw.encaps, "ENCAPS");
+ if (opt_s) {
+ xo_emit(" {T:/%-*s}", cw.path_state, "PATH STATE");
+ xo_emit(" {T:/%-*s}", cw.conn_state, "CONN STATE");
+ }
+ if (opt_S)
+ xo_emit(" {T:/%-*s}", cw.stack, "STACK");
+ if (opt_C)
+ xo_emit(" {T:/%-*s}", cw.cc, "CC");
+ xo_emit("\n");
+ }
+ cap_setpassent(cappwd, 1);
+ for (xf = files, n = 0; n < nfiles; ++n, ++xf) {
+ if (xf->xf_data == 0)
+ continue;
+ if (opt_j >= 0 && opt_j != getprocjid(xf->xf_pid))
+ continue;
+ s = RB_FIND(socks_t, &socks,
+ &(struct sock){ .socket = xf->xf_data});
+ if (s != NULL && check_ports(s)) {
+ xo_open_instance("socket");
+ s->shown = 1;
+ if (opt_n ||
+ (pwd = cap_getpwuid(cappwd, xf->xf_uid)) == NULL)
+ xo_emit("{:user/%-*lu}", cw.user,
+ (u_long)xf->xf_uid);
+ else
+ xo_emit("{:user/%-*s}", cw.user, pwd->pw_name);
+ if (!is_xo_style_encoding)
+ xo_emit(" {:command/%-*.10s}", cw.command,
+ getprocname(xf->xf_pid));
+ else
+ xo_emit(" {:command/%-*s}", cw.command,
+ getprocname(xf->xf_pid));
+ xo_emit(" {:pid/%*lu}", cw.pid, (u_long)xf->xf_pid);
+ xo_emit(" {:fd/%*d}", cw.fd, xf->xf_fd);
+ display_sock(s, &cw, buf, bufsize);
+ xo_close_instance("socket");
+ }
+ }
+ if (opt_j >= 0)
+ return;
+ SLIST_FOREACH(s, &nosocks, socket_list) {
+ if (!check_ports(s))
+ continue;
+ xo_open_instance("socket");
+ if (!is_xo_style_encoding)
+ xo_emit("{:user/%-*s} {:command/%-*s} {:pid/%*s}"
+ " {:fd/%*s}", cw.user, "??", cw.command, "??",
+ cw.pid, "??", cw.fd, "??");
+ display_sock(s, &cw, buf, bufsize);
+ xo_close_instance("socket");
+ }
+ RB_FOREACH(s, socks_t, &socks) {
+ if (s->shown)
+ continue;
+ if (!check_ports(s))
+ continue;
+ xo_open_instance("socket");
+ if (!is_xo_style_encoding)
+ xo_emit("{:user/%-*s} {:command/%-*s} {:pid/%*s}"
+ " {:fd/%*s}", cw.user, "??", cw.command, "??",
+ cw.pid, "??", cw.fd, "??");
+ display_sock(s, &cw, buf, bufsize);
+ xo_close_instance("socket");
+ }
+ xo_close_list("socket");
+ xo_close_container("sockstat");
+ if (xo_finish() < 0)
+ xo_err(1, "stdout");
+ free(buf);
+ cap_endpwent(cappwd);
+}
+
+static int
+set_default_protos(void)
+{
+ struct protoent *prot;
+ const char *pname;
+ size_t pindex;
+
+ init_protos(default_numprotos);
+
+ for (pindex = 0; pindex < default_numprotos; pindex++) {
+ pname = default_protos[pindex];
+ prot = cap_getprotobyname(capnetdb, pname);
+ if (prot == NULL)
+ xo_err(1, "cap_getprotobyname: %s", pname);
+ protos[pindex] = prot->p_proto;
+ }
+ numprotos = pindex;
+ return (pindex);
+}
+
+/*
+ * Return the vnet property of the jail, or -1 on error.
+ */
+static int
+jail_getvnet(int jid)
+{
+ struct iovec jiov[6];
+ int vnet;
+ size_t len = sizeof(vnet);
+
+ if (sysctlbyname("kern.features.vimage", &vnet, &len, NULL, 0) != 0)
+ return (0);
+
+ vnet = -1;
+ jiov[0].iov_base = __DECONST(char *, "jid");
+ jiov[0].iov_len = sizeof("jid");
+ jiov[1].iov_base = &jid;
+ jiov[1].iov_len = sizeof(jid);
+ jiov[2].iov_base = __DECONST(char *, "vnet");
+ jiov[2].iov_len = sizeof("vnet");
+ jiov[3].iov_base = &vnet;
+ jiov[3].iov_len = sizeof(vnet);
+ jiov[4].iov_base = __DECONST(char *, "errmsg");
+ jiov[4].iov_len = sizeof("errmsg");
+ jiov[5].iov_base = jail_errmsg;
+ jiov[5].iov_len = JAIL_ERRMSGLEN;
+ jail_errmsg[0] = '\0';
+ if (jail_get(jiov, nitems(jiov), 0) < 0) {
+ if (!jail_errmsg[0])
+ snprintf(jail_errmsg, JAIL_ERRMSGLEN,
+ "jail_get: %s", strerror(errno));
+ return (-1);
+ }
+ return (vnet);
+}
+
+static void
+usage(void)
+{
+ xo_error(
+"usage: sockstat [--libxo ...] [-46ACcfIiLlnqSsUuvw] [-j jid] [-p ports]\n"
+" [-P protocols]\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ cap_channel_t *capcas;
+ cap_net_limit_t *limit;
+ const char *pwdcmds[] = { "setpassent", "getpwuid" };
+ const char *pwdfields[] = { "pw_name" };
+ int protos_defined = -1;
+ int o, i, err;
+
+ argc = xo_parse_args(argc, argv);
+ if (argc < 0)
+ exit(1);
+ if (xo_get_style(NULL) != XO_STYLE_TEXT &&
+ xo_get_style(NULL) != XO_STYLE_HTML)
+ is_xo_style_encoding = true;
+ opt_j = -1;
+ while ((o = getopt(argc, argv, "46ACcfIij:Llnp:P:qSsUuvw")) != -1)
+ switch (o) {
+ case '4':
+ opt_4 = true;
+ break;
+ case '6':
+ opt_6 = true;
+ break;
+ case 'A':
+ opt_A = true;
+ break;
+ case 'C':
+ opt_C = true;
+ break;
+ case 'c':
+ opt_c = true;
+ break;
+ case 'f':
+ opt_f = true;
+ break;
+ case 'I':
+ opt_I = true;
+ break;
+ case 'i':
+ opt_i = true;
+ break;
+ case 'j':
+ opt_j = jail_getid(optarg);
+ if (opt_j < 0)
+ xo_errx(1, "jail_getid: %s", jail_errmsg);
+ break;
+ case 'L':
+ opt_L = true;
+ break;
+ case 'l':
+ opt_l = true;
+ break;
+ case 'n':
+ opt_n = true;
+ break;
+ case 'p':
+ err = parse_ports(optarg);
+ switch (err) {
+ case EINVAL:
+ xo_errx(1, "syntax error in port range");
+ break;
+ case ERANGE:
+ xo_errx(1, "invalid port number");
+ break;
+ }
+ break;
+ case 'P':
+ protos_defined = parse_protos(optarg);
+ break;
+ case 'q':
+ opt_q = true;
+ break;
+ case 'S':
+ opt_S = true;
+ break;
+ case 's':
+ opt_s = true;
+ break;
+ case 'U':
+ opt_U = true;
+ break;
+ case 'u':
+ opt_u = true;
+ break;
+ case 'v':
+ ++opt_v;
+ break;
+ case 'w':
+ opt_w = true;
+ break;
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ usage();
+
+ if (opt_j > 0) {
+ switch (jail_getvnet(opt_j)) {
+ case -1:
+ xo_errx(2, "jail_getvnet: %s", jail_errmsg);
+ case JAIL_SYS_NEW:
+ if (jail_attach(opt_j) < 0)
+ xo_err(3, "jail_attach()");
+ /* Set back to -1 for normal output in vnet jail. */
+ opt_j = -1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ capcas = cap_init();
+ if (capcas == NULL)
+ xo_err(1, "Unable to contact Casper");
+ if (caph_enter_casper() < 0)
+ xo_err(1, "Unable to enter capability mode");
+ capnet = cap_service_open(capcas, "system.net");
+ if (capnet == NULL)
+ xo_err(1, "Unable to open system.net service");
+ capnetdb = cap_service_open(capcas, "system.netdb");
+ if (capnetdb == NULL)
+ xo_err(1, "Unable to open system.netdb service");
+ capsysctl = cap_service_open(capcas, "system.sysctl");
+ if (capsysctl == NULL)
+ xo_err(1, "Unable to open system.sysctl service");
+ cappwd = cap_service_open(capcas, "system.pwd");
+ if (cappwd == NULL)
+ xo_err(1, "Unable to open system.pwd service");
+ cap_close(capcas);
+ limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
+ if (limit == NULL)
+ xo_err(1, "Unable to init cap_net limits");
+ if (cap_net_limit(limit) < 0)
+ xo_err(1, "Unable to apply limits");
+ if (cap_pwd_limit_cmds(cappwd, pwdcmds, nitems(pwdcmds)) < 0)
+ xo_err(1, "Unable to apply pwd commands limits");
+ if (cap_pwd_limit_fields(cappwd, pwdfields, nitems(pwdfields)) < 0)
+ xo_err(1, "Unable to apply pwd commands limits");
+
+ if ((!opt_4 && !opt_6) && protos_defined != -1)
+ opt_4 = opt_6 = true;
+ if (!opt_4 && !opt_6 && !opt_u)
+ opt_4 = opt_6 = opt_u = true;
+ if ((opt_4 || opt_6) && protos_defined == -1)
+ protos_defined = set_default_protos();
+ if (!opt_c && !opt_l)
+ opt_c = opt_l = true;
+
+ if (opt_4 || opt_6) {
+ for (i = 0; i < protos_defined; i++)
+ if (protos[i] == IPPROTO_SCTP)
+ gather_sctp();
+ else
+ gather_inet(protos[i]);
+ }
+
+ if (opt_u || (protos_defined == -1 && !opt_4 && !opt_6)) {
+ gather_unix(SOCK_STREAM);
+ gather_unix(SOCK_DGRAM);
+ gather_unix(SOCK_SEQPACKET);
+ }
+ getfiles();
+ display();
+ exit(0);
+}
diff --git a/usr.bin/sockstat/sockstat.c b/usr.bin/sockstat/sockstat.c
index 6761faae5210..7bb7f6a66e3f 100644
--- a/usr.bin/sockstat/sockstat.c
+++ b/usr.bin/sockstat/sockstat.c
@@ -1,7 +1,7 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
- * Copyright (c) 2002 Dag-Erling Smørgrav
+ * Copyright (c) 2025 ConnectWise
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -13,8 +13,6 @@
* 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
@@ -28,1889 +26,52 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include <sys/param.h>
-#include <sys/file.h>
-#include <sys/socket.h>
-#include <sys/socketvar.h>
-#include <sys/sysctl.h>
-#include <sys/jail.h>
-#include <sys/user.h>
-#include <sys/queue.h>
-#include <sys/tree.h>
-
-#include <sys/un.h>
-#include <sys/unpcb.h>
-
-#include <net/route.h>
-
-#include <netinet/in.h>
-#include <netinet/in_pcb.h>
-#include <netinet/sctp.h>
-#include <netinet/tcp.h>
-#define TCPSTATES /* load state names */
-#include <netinet/tcp_fsm.h>
-#include <netinet/tcp_seq.h>
-#include <netinet/tcp_var.h>
-#include <arpa/inet.h>
-
-#include <capsicum_helpers.h>
#include <ctype.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <jail.h>
-#include <netdb.h>
-#include <pwd.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stdio.h>
#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
#include <libxo/xo.h>
-#include <libcasper.h>
-#include <casper/cap_net.h>
-#include <casper/cap_netdb.h>
-#include <casper/cap_pwd.h>
-#include <casper/cap_sysctl.h>
-
-#define SOCKSTAT_XO_VERSION "1"
-#define sstosin(ss) ((struct sockaddr_in *)(ss))
-#define sstosin6(ss) ((struct sockaddr_in6 *)(ss))
-#define sstosun(ss) ((struct sockaddr_un *)(ss))
-#define sstosa(ss) ((struct sockaddr *)(ss))
-
-static bool opt_4; /* Show IPv4 sockets */
-static bool opt_6; /* Show IPv6 sockets */
-static bool opt_A; /* Show kernel address of pcb */
-static bool opt_C; /* Show congestion control */
-static bool opt_c; /* Show connected sockets */
-static bool opt_f; /* Show FIB numbers */
-static bool opt_I; /* Show spliced socket addresses */
-static bool opt_i; /* Show inp_gencnt */
-static int opt_j; /* Show specified jail */
-static bool opt_L; /* Don't show IPv4 or IPv6 loopback sockets */
-static bool opt_l; /* Show listening sockets */
-static bool opt_n; /* Don't resolve UIDs to user names */
-static bool opt_q; /* Don't show header */
-static bool opt_S; /* Show protocol stack if applicable */
-static bool opt_s; /* Show protocol state if applicable */
-static bool opt_U; /* Show remote UDP encapsulation port number */
-static bool opt_u; /* Show Unix domain sockets */
-static u_int opt_v; /* Verbose mode */
-static bool opt_w; /* Automatically size the columns */
-
-/*
- * Default protocols to use if no -P was defined.
- */
-static const char *default_protos[] = {"sctp", "tcp", "udp", "divert" };
-static size_t default_numprotos = nitems(default_protos);
-
-static int *protos; /* protocols to use */
-static size_t numprotos; /* allocated size of protos[] */
+#include "sockstat.h"
-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 addr {
- union {
- struct sockaddr_storage address;
- struct { /* unix(4) faddr */
- kvaddr_t conn;
- kvaddr_t firstref;
- kvaddr_t nextref;
- };
- };
- unsigned int encaps_port;
- int state;
- struct addr *next;
-};
-
-struct sock {
- union {
- RB_ENTRY(sock) socket_tree; /* tree of pcbs with socket */
- SLIST_ENTRY(sock) socket_list; /* list of pcbs w/o socket */
- };
- RB_ENTRY(sock) pcb_tree;
- kvaddr_t socket;
- kvaddr_t pcb;
- kvaddr_t splice_socket;
- uint64_t inp_gencnt;
- int shown;
- int vflag;
- int family;
- int proto;
- int state;
- int fibnum;
- const char *protoname;
- char stack[TCP_FUNCTION_NAME_LEN_MAX];
- char cc[TCP_CA_NAME_MAX];
- struct addr *laddr;
- struct addr *faddr;
-};
-
-static RB_HEAD(socks_t, sock) socks = RB_INITIALIZER(&socks);
-static int64_t
-socket_compare(const struct sock *a, const struct sock *b)
-{
- return ((int64_t)(a->socket/2 - b->socket/2));
-}
-RB_GENERATE_STATIC(socks_t, sock, socket_tree, socket_compare);
-
-static RB_HEAD(pcbs_t, sock) pcbs = RB_INITIALIZER(&pcbs);
-static int64_t
-pcb_compare(const struct sock *a, const struct sock *b)
-{
- return ((int64_t)(a->pcb/2 - b->pcb/2));
-}
-RB_GENERATE_STATIC(pcbs_t, sock, pcb_tree, pcb_compare);
-
-static SLIST_HEAD(, sock) nosocks = SLIST_HEAD_INITIALIZER(&nosocks);
-
-struct file {
- RB_ENTRY(file) file_tree;
- kvaddr_t xf_data;
- pid_t xf_pid;
- uid_t xf_uid;
- int xf_fd;
-};
-
-static RB_HEAD(files_t, file) ftree = RB_INITIALIZER(&ftree);
-static int64_t
-file_compare(const struct file *a, const struct file *b)
-{
- return ((int64_t)(a->xf_data/2 - b->xf_data/2));
-}
-RB_GENERATE_STATIC(files_t, file, file_tree, file_compare);
-
-static struct file *files;
-static int nfiles;
-
-static cap_channel_t *capnet;
-static cap_channel_t *capnetdb;
-static cap_channel_t *capsysctl;
-static cap_channel_t *cappwd;
-
-static bool
-_check_ksize(size_t received_size, size_t expected_size, const char *struct_name)
-{
- if (received_size != expected_size) {
- xo_warnx("%s size mismatch: expected %zd, received %zd",
- struct_name, expected_size, received_size);
- return false;
- }
- return true;
-}
-#define check_ksize(_sz, _struct) (_check_ksize(_sz, sizeof(_struct), #_struct))
-
-static void
-_enforce_ksize(size_t received_size, size_t expected_size, const char *struct_name)
-{
- if (received_size != expected_size) {
- xo_errx(1, "fatal: struct %s size mismatch: expected %zd, received %zd",
- struct_name, expected_size, received_size);
- }
-}
-#define enforce_ksize(_sz, _struct) (_enforce_ksize(_sz, sizeof(_struct), #_struct))
-
-static int
-get_proto_type(const char *proto)
-{
- struct protoent *pent;
+int *ports;
- if (strlen(proto) == 0)
- return (0);
- if (capnetdb != NULL)
- pent = cap_getprotobyname(capnetdb, proto);
- else
- pent = getprotobyname(proto);
- if (pent == NULL) {
- xo_warn("cap_getprotobyname");
- return (-1);
- }
- return (pent->p_proto);
-}
-
-static void
-init_protos(int num)
-{
- int proto_count = 0;
-
- if (num > 0) {
- proto_count = num;
- } else {
- /* Find the maximum number of possible protocols. */
- while (getprotoent() != NULL)
- proto_count++;
- endprotoent();
- }
-
- if ((protos = malloc(sizeof(int) * proto_count)) == NULL)
- xo_err(1, "malloc");
- numprotos = proto_count;
-}
-
-static int
-parse_protos(char *protospec)
-{
- char *prot;
- int proto_type, proto_index;
-
- if (protospec == NULL)
- return (-1);
-
- init_protos(0);
- proto_index = 0;
- while ((prot = strsep(&protospec, ",")) != NULL) {
- if (strlen(prot) == 0)
- continue;
- proto_type = get_proto_type(prot);
- if (proto_type != -1)
- protos[proto_index++] = proto_type;
- }
- numprotos = proto_index;
- return (proto_index);
-}
-
-static void
+int
parse_ports(const char *portspec)
{
- const char *p, *q;
- int port, end;
+ const char *p;
if (ports == NULL)
if ((ports = calloc(65536 / INT_BIT, sizeof(int))) == NULL)
xo_err(1, "calloc()");
p = portspec;
while (*p != '\0') {
- if (!isdigit(*p))
- xo_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);
+ long port, end;
+ char *endptr = NULL;
+
+ errno = 0;
+ port = strtol(p, &endptr, 10);
+ if (errno)
+ return (errno);
if (port < 0 || port > 65535)
- xo_errx(1, "invalid port number");
+ return (ERANGE);
SET_PORT(port);
- switch (*p) {
+ switch (*endptr) {
case '-':
- ++p;
+ p = endptr + 1;
+ end = strtol(p, &endptr, 10);
break;
case ',':
- ++p;
- /* fall through */
- case '\0':
+ p = endptr + 1;
+ continue;
default:
+ p = endptr;
continue;
}
- for (q = p; *q != '\0' && isdigit(*q); ++q)
- /* nothing */ ;
- for (end = 0; p < q; ++p)
- end = end * 10 + digittoint(*p);
+ if (errno)
+ return (errno);
if (end < port || end > 65535)
- xo_errx(1, "invalid port number");
+ return (ERANGE);
while (port++ < end)
SET_PORT(port);
- if (*p == ',')
- ++p;
- }
-}
-
-static void
-sockaddr(struct sockaddr_storage *ss, int af, void *addr, int port)
-{
- struct sockaddr_in *sin4;
- struct sockaddr_in6 *sin6;
-
- bzero(ss, sizeof(*ss));
- switch (af) {
- case AF_INET:
- sin4 = sstosin(ss);
- 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 = sstosin6(ss);
- sin6->sin6_len = sizeof(*sin6);
- sin6->sin6_family = af;
- sin6->sin6_port = port;
- sin6->sin6_addr = *(struct in6_addr *)addr;
-#define s6_addr16 __u6_addr.__u6_addr16
- if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
- sin6->sin6_scope_id =
- ntohs(sin6->sin6_addr.s6_addr16[1]);
- sin6->sin6_addr.s6_addr16[1] = 0;
- }
- break;
- default:
- abort();
- }
-}
-
-static void
-free_socket(struct sock *sock)
-{
- struct addr *cur, *next;
-
- cur = sock->laddr;
- while (cur != NULL) {
- next = cur->next;
- free(cur);
- cur = next;
- }
- cur = sock->faddr;
- while (cur != NULL) {
- next = cur->next;
- free(cur);
- cur = next;
- }
- free(sock);
-}
-
-static void
-gather_sctp(void)
-{
- struct sock *sock;
- struct addr *laddr, *prev_laddr, *faddr, *prev_faddr;
- struct xsctp_inpcb *xinpcb;
- struct xsctp_tcb *xstcb;
- struct xsctp_raddr *xraddr;
- struct xsctp_laddr *xladdr;
- const char *varname;
- size_t len, offset;
- char *buf;
- int vflag;
- int no_stcb, local_all_loopback, foreign_all_loopback;
-
- vflag = 0;
- if (opt_4)
- vflag |= INP_IPV4;
- if (opt_6)
- vflag |= INP_IPV6;
-
- varname = "net.inet.sctp.assoclist";
- if (cap_sysctlbyname(capsysctl, varname, 0, &len, 0, 0) < 0) {
- if (errno != ENOENT)
- xo_err(1, "cap_sysctlbyname()");
- return;
- }
- if ((buf = (char *)malloc(len)) == NULL) {
- xo_err(1, "malloc()");
- return;
- }
- if (cap_sysctlbyname(capsysctl, varname, buf, &len, 0, 0) < 0) {
- xo_err(1, "cap_sysctlbyname()");
- free(buf);
- return;
- }
- xinpcb = (struct xsctp_inpcb *)(void *)buf;
- offset = sizeof(struct xsctp_inpcb);
- while ((offset < len) && (xinpcb->last == 0)) {
- if ((sock = calloc(1, sizeof *sock)) == NULL)
- xo_err(1, "malloc()");
- sock->socket = xinpcb->socket;
- sock->proto = IPPROTO_SCTP;
- sock->protoname = "sctp";
- if (xinpcb->maxqlen == 0)
- sock->state = SCTP_CLOSED;
- else
- sock->state = SCTP_LISTEN;
- if (xinpcb->flags & SCTP_PCB_FLAGS_BOUND_V6) {
- sock->family = AF_INET6;
- /*
- * Currently there is no way to distinguish between
- * IPv6 only sockets or dual family sockets.
- * So mark it as dual socket.
- */
- sock->vflag = INP_IPV6 | INP_IPV4;
- } else {
- sock->family = AF_INET;
- sock->vflag = INP_IPV4;
- }
- prev_laddr = NULL;
- local_all_loopback = 1;
- while (offset < len) {
- xladdr = (struct xsctp_laddr *)(void *)(buf + offset);
- offset += sizeof(struct xsctp_laddr);
- if (xladdr->last == 1)
- break;
- if ((laddr = calloc(1, sizeof(struct addr))) == NULL)
- xo_err(1, "malloc()");
- switch (xladdr->address.sa.sa_family) {
- case AF_INET:
-#define __IN_IS_ADDR_LOOPBACK(pina) \
- ((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)
- if (!__IN_IS_ADDR_LOOPBACK(
- &xladdr->address.sin.sin_addr))
- local_all_loopback = 0;
-#undef __IN_IS_ADDR_LOOPBACK
- sockaddr(&laddr->address, AF_INET,
- &xladdr->address.sin.sin_addr,
- htons(xinpcb->local_port));
- break;
- case AF_INET6:
- if (!IN6_IS_ADDR_LOOPBACK(
- &xladdr->address.sin6.sin6_addr))
- local_all_loopback = 0;
- sockaddr(&laddr->address, AF_INET6,
- &xladdr->address.sin6.sin6_addr,
- htons(xinpcb->local_port));
- break;
- default:
- xo_errx(1, "address family %d not supported",
- xladdr->address.sa.sa_family);
- }
- laddr->next = NULL;
- if (prev_laddr == NULL)
- sock->laddr = laddr;
- else
- prev_laddr->next = laddr;
- prev_laddr = laddr;
- }
- if (sock->laddr == NULL) {
- if ((sock->laddr =
- calloc(1, sizeof(struct addr))) == NULL)
- xo_err(1, "malloc()");
- sock->laddr->address.ss_family = sock->family;
- if (sock->family == AF_INET)
- sock->laddr->address.ss_len =
- sizeof(struct sockaddr_in);
- else
- sock->laddr->address.ss_len =
- sizeof(struct sockaddr_in6);
- local_all_loopback = 0;
- }
- if ((sock->faddr = calloc(1, sizeof(struct addr))) == NULL)
- xo_err(1, "malloc()");
- sock->faddr->address.ss_family = sock->family;
- if (sock->family == AF_INET)
- sock->faddr->address.ss_len =
- sizeof(struct sockaddr_in);
- else
- sock->faddr->address.ss_len =
- sizeof(struct sockaddr_in6);
- no_stcb = 1;
- while (offset < len) {
- xstcb = (struct xsctp_tcb *)(void *)(buf + offset);
- offset += sizeof(struct xsctp_tcb);
- if (no_stcb) {
- if (opt_l && (sock->vflag & vflag) &&
- (!opt_L || !local_all_loopback) &&
- ((xinpcb->flags & SCTP_PCB_FLAGS_UDPTYPE) ||
- (xstcb->last == 1))) {
- RB_INSERT(socks_t, &socks, sock);
- } else {
- free_socket(sock);
- }
- }
- if (xstcb->last == 1)
- break;
- no_stcb = 0;
- if (opt_c) {
- if ((sock = calloc(1, sizeof *sock)) == NULL)
- xo_err(1, "malloc()");
- sock->socket = xinpcb->socket;
- sock->proto = IPPROTO_SCTP;
- sock->protoname = "sctp";
- sock->state = (int)xstcb->state;
- if (xinpcb->flags & SCTP_PCB_FLAGS_BOUND_V6) {
- sock->family = AF_INET6;
- /*
- * Currently there is no way to distinguish
- * between IPv6 only sockets or dual family
- * sockets. So mark it as dual socket.
- */
- sock->vflag = INP_IPV6 | INP_IPV4;
- } else {
- sock->family = AF_INET;
- sock->vflag = INP_IPV4;
- }
- }
- prev_laddr = NULL;
- local_all_loopback = 1;
- while (offset < len) {
- xladdr = (struct xsctp_laddr *)(void *)(buf +
- offset);
- offset += sizeof(struct xsctp_laddr);
- if (xladdr->last == 1)
- break;
- if (!opt_c)
- continue;
- laddr = calloc(1, sizeof(struct addr));
- if (laddr == NULL)
- xo_err(1, "malloc()");
- switch (xladdr->address.sa.sa_family) {
- case AF_INET:
-#define __IN_IS_ADDR_LOOPBACK(pina) \
- ((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)
- if (!__IN_IS_ADDR_LOOPBACK(
- &xladdr->address.sin.sin_addr))
- local_all_loopback = 0;
-#undef __IN_IS_ADDR_LOOPBACK
- sockaddr(&laddr->address, AF_INET,
- &xladdr->address.sin.sin_addr,
- htons(xstcb->local_port));
- break;
- case AF_INET6:
- if (!IN6_IS_ADDR_LOOPBACK(
- &xladdr->address.sin6.sin6_addr))
- local_all_loopback = 0;
- sockaddr(&laddr->address, AF_INET6,
- &xladdr->address.sin6.sin6_addr,
- htons(xstcb->local_port));
- break;
- default:
- xo_errx(1,
- "address family %d not supported",
- xladdr->address.sa.sa_family);
- }
- laddr->next = NULL;
- if (prev_laddr == NULL)
- sock->laddr = laddr;
- else
- prev_laddr->next = laddr;
- prev_laddr = laddr;
- }
- prev_faddr = NULL;
- foreign_all_loopback = 1;
- while (offset < len) {
- xraddr = (struct xsctp_raddr *)(void *)(buf +
- offset);
- offset += sizeof(struct xsctp_raddr);
- if (xraddr->last == 1)
- break;
- if (!opt_c)
- continue;
- faddr = calloc(1, sizeof(struct addr));
- if (faddr == NULL)
- xo_err(1, "malloc()");
- switch (xraddr->address.sa.sa_family) {
- case AF_INET:
-#define __IN_IS_ADDR_LOOPBACK(pina) \
- ((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)
- if (!__IN_IS_ADDR_LOOPBACK(
- &xraddr->address.sin.sin_addr))
- foreign_all_loopback = 0;
-#undef __IN_IS_ADDR_LOOPBACK
- sockaddr(&faddr->address, AF_INET,
- &xraddr->address.sin.sin_addr,
- htons(xstcb->remote_port));
- break;
- case AF_INET6:
- if (!IN6_IS_ADDR_LOOPBACK(
- &xraddr->address.sin6.sin6_addr))
- foreign_all_loopback = 0;
- sockaddr(&faddr->address, AF_INET6,
- &xraddr->address.sin6.sin6_addr,
- htons(xstcb->remote_port));
- break;
- default:
- xo_errx(1,
- "address family %d not supported",
- xraddr->address.sa.sa_family);
- }
- faddr->encaps_port = xraddr->encaps_port;
- faddr->state = xraddr->state;
- faddr->next = NULL;
- if (prev_faddr == NULL)
- sock->faddr = faddr;
- else
- prev_faddr->next = faddr;
- prev_faddr = faddr;
- }
- if (opt_c) {
- if ((sock->vflag & vflag) &&
- (!opt_L ||
- !(local_all_loopback ||
- foreign_all_loopback))) {
- RB_INSERT(socks_t, &socks, sock);
- } else {
- free_socket(sock);
- }
- }
- }
- xinpcb = (struct xsctp_inpcb *)(void *)(buf + offset);
- offset += sizeof(struct xsctp_inpcb);
- }
- free(buf);
-}
-
-static void
-gather_inet(int proto)
-{
- struct xinpgen *xig, *exig;
- struct xinpcb *xip;
- struct xtcpcb *xtp = NULL;
- struct xsocket *so;
- struct sock *sock;
- struct addr *laddr, *faddr;
- const char *varname, *protoname;
- size_t len, bufsize;
- void *buf;
- int 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;
- case IPPROTO_DIVERT:
- varname = "net.inet.divert.pcblist";
- protoname = "div";
- break;
- default:
- xo_errx(1, "protocol %d not supported", proto);
- }
-
- buf = NULL;
- bufsize = 8192;
- retry = 5;
- do {
- for (;;) {
- if ((buf = realloc(buf, bufsize)) == NULL)
- xo_err(1, "realloc()");
- len = bufsize;
- if (cap_sysctlbyname(capsysctl, varname, buf, &len,
- NULL, 0) == 0)
- break;
- if (errno == ENOENT)
- goto out;
- if (errno != ENOMEM || len != bufsize)
- xo_err(1, "cap_sysctlbyname()");
- bufsize *= 2;
- }
- xig = (struct xinpgen *)buf;
- exig = (struct xinpgen *)(void *)
- ((char *)buf + len - sizeof *exig);
- enforce_ksize(xig->xig_len, struct xinpgen);
- enforce_ksize(exig->xig_len, struct xinpgen);
- } while (xig->xig_gen != exig->xig_gen && retry--);
-
- if (xig->xig_gen != exig->xig_gen && opt_v)
- xo_warnx("warning: data may be inconsistent");
-
- for (;;) {
- xig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len);
- if (xig >= exig)
- break;
- switch (proto) {
- case IPPROTO_TCP:
- xtp = (struct xtcpcb *)xig;
- xip = &xtp->xt_inp;
- if (!check_ksize(xtp->xt_len, struct xtcpcb))
- goto out;
- protoname = xtp->t_flags & TF_TOE ? "toe" : "tcp";
- break;
- case IPPROTO_UDP:
- case IPPROTO_DIVERT:
- xip = (struct xinpcb *)xig;
- if (!check_ksize(xip->xi_len, struct xinpcb))
- goto out;
- break;
- default:
- xo_errx(1, "protocol %d not supported", proto);
- }
- so = &xip->xi_socket;
- if ((xip->inp_vflag & vflag) == 0)
- continue;
- if (xip->inp_vflag & INP_IPV4) {
- if ((xip->inp_fport == 0 && !opt_l) ||
- (xip->inp_fport != 0 && !opt_c))
- continue;
-#define __IN_IS_ADDR_LOOPBACK(pina) \
- ((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)
- if (opt_L &&
- (__IN_IS_ADDR_LOOPBACK(&xip->inp_faddr) ||
- __IN_IS_ADDR_LOOPBACK(&xip->inp_laddr)))
- continue;
-#undef __IN_IS_ADDR_LOOPBACK
- } else if (xip->inp_vflag & INP_IPV6) {
- if ((xip->inp_fport == 0 && !opt_l) ||
- (xip->inp_fport != 0 && !opt_c))
- continue;
- if (opt_L &&
- (IN6_IS_ADDR_LOOPBACK(&xip->in6p_faddr) ||
- IN6_IS_ADDR_LOOPBACK(&xip->in6p_laddr)))
- continue;
- } else {
- if (opt_v)
- xo_warnx("invalid vflag 0x%x", xip->inp_vflag);
- continue;
- }
- if ((sock = calloc(1, sizeof(*sock))) == NULL)
- xo_err(1, "malloc()");
- if ((laddr = calloc(1, sizeof *laddr)) == NULL)
- xo_err(1, "malloc()");
- if ((faddr = calloc(1, sizeof *faddr)) == NULL)
- xo_err(1, "malloc()");
- sock->socket = so->xso_so;
- sock->pcb = so->so_pcb;
- sock->splice_socket = so->so_splice_so;
- sock->proto = proto;
- sock->inp_gencnt = xip->inp_gencnt;
- sock->fibnum = so->so_fibnum;
- if (xip->inp_vflag & INP_IPV4) {
- sock->family = AF_INET;
- sockaddr(&laddr->address, sock->family,
- &xip->inp_laddr, xip->inp_lport);
- sockaddr(&faddr->address, sock->family,
- &xip->inp_faddr, xip->inp_fport);
- } else if (xip->inp_vflag & INP_IPV6) {
- sock->family = AF_INET6;
- sockaddr(&laddr->address, sock->family,
- &xip->in6p_laddr, xip->inp_lport);
- sockaddr(&faddr->address, sock->family,
- &xip->in6p_faddr, xip->inp_fport);
- }
- if (proto == IPPROTO_TCP)
- faddr->encaps_port = xtp->xt_encaps_port;
- laddr->next = NULL;
- faddr->next = NULL;
- sock->laddr = laddr;
- sock->faddr = faddr;
- sock->vflag = xip->inp_vflag;
- if (proto == IPPROTO_TCP) {
- sock->state = xtp->t_state;
- memcpy(sock->stack, xtp->xt_stack,
- TCP_FUNCTION_NAME_LEN_MAX);
- memcpy(sock->cc, xtp->xt_cc, TCP_CA_NAME_MAX);
- }
- sock->protoname = protoname;
- if (sock->socket != 0)
- RB_INSERT(socks_t, &socks, sock);
- else
- SLIST_INSERT_HEAD(&nosocks, sock, socket_list);
- }
-out:
- free(buf);
-}
-
-static void
-gather_unix(int proto)
-{
- struct xunpgen *xug, *exug;
- struct xunpcb *xup;
- struct sock *sock;
- struct addr *laddr, *faddr;
- const char *varname, *protoname;
- size_t len, bufsize;
- void *buf;
- int 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;
- case SOCK_SEQPACKET:
- varname = "net.local.seqpacket.pcblist";
- protoname = (xo_get_style(NULL) == XO_STYLE_TEXT)
- ? "seqpac"
- : "seqpacket";
- break;
- default:
- abort();
- }
- buf = NULL;
- bufsize = 8192;
- retry = 5;
- do {
- for (;;) {
- if ((buf = realloc(buf, bufsize)) == NULL)
- xo_err(1, "realloc()");
- len = bufsize;
- if (cap_sysctlbyname(capsysctl, varname, buf, &len,
- NULL, 0) == 0)
- break;
- if (errno != ENOMEM || len != bufsize)
- xo_err(1, "cap_sysctlbyname()");
- bufsize *= 2;
- }
- xug = (struct xunpgen *)buf;
- exug = (struct xunpgen *)(void *)
- ((char *)buf + len - sizeof(*exug));
- if (!check_ksize(xug->xug_len, struct xunpgen) ||
- !check_ksize(exug->xug_len, struct xunpgen))
- goto out;
- } while (xug->xug_gen != exug->xug_gen && retry--);
-
- if (xug->xug_gen != exug->xug_gen && opt_v)
- xo_warnx("warning: data may be inconsistent");
-
- for (;;) {
- xug = (struct xunpgen *)(void *)((char *)xug + xug->xug_len);
- if (xug >= exug)
- break;
- xup = (struct xunpcb *)xug;
- if (!check_ksize(xup->xu_len, struct xunpcb))
- goto out;
- if ((xup->unp_conn == 0 && !opt_l) ||
- (xup->unp_conn != 0 && !opt_c))
- continue;
- if ((sock = calloc(1, sizeof(*sock))) == NULL)
- xo_err(1, "malloc()");
- if ((laddr = calloc(1, sizeof *laddr)) == NULL)
- xo_err(1, "malloc()");
- if ((faddr = calloc(1, sizeof *faddr)) == NULL)
- xo_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_addr.sun_family == AF_UNIX)
- laddr->address =
- *(struct sockaddr_storage *)(void *)&xup->xu_addr;
- faddr->conn = xup->unp_conn;
- faddr->firstref = xup->xu_firstref;
- faddr->nextref = xup->xu_nextref;
- laddr->next = NULL;
- faddr->next = NULL;
- sock->laddr = laddr;
- sock->faddr = faddr;
- RB_INSERT(socks_t, &socks, sock);
- RB_INSERT(pcbs_t, &pcbs, sock);
- }
-out:
- free(buf);
-}
-
-static void
-getfiles(void)
-{
- struct xfile *xfiles;
- size_t len, olen;
-
- olen = len = sizeof(*xfiles);
- if ((xfiles = malloc(len)) == NULL)
- xo_err(1, "malloc()");
- while (cap_sysctlbyname(capsysctl, "kern.file", xfiles, &len, 0, 0)
- == -1) {
- if (errno != ENOMEM || len != olen)
- xo_err(1, "cap_sysctlbyname()");
- olen = len *= 2;
- if ((xfiles = realloc(xfiles, len)) == NULL)
- xo_err(1, "realloc()");
- }
- if (len > 0)
- enforce_ksize(xfiles->xf_size, struct xfile);
- nfiles = len / sizeof(*xfiles);
-
- if ((files = malloc(nfiles * sizeof(struct file))) == NULL)
- xo_err(1, "malloc()");
-
- for (int i = 0; i < nfiles; i++) {
- files[i].xf_data = xfiles[i].xf_data;
- files[i].xf_pid = xfiles[i].xf_pid;
- files[i].xf_uid = xfiles[i].xf_uid;
- files[i].xf_fd = xfiles[i].xf_fd;
- RB_INSERT(files_t, &ftree, &files[i]);
- }
-
- free(xfiles);
-}
-
-static int
-formataddr(struct sockaddr_storage *ss, char *buf, size_t bufsize)
-{
- struct sockaddr_un *sun;
- char addrstr[NI_MAXHOST] = { '\0', '\0' };
- int error, off, port = 0;
- const bool is_text_style = (xo_get_style(NULL) == XO_STYLE_TEXT);
-
- switch (ss->ss_family) {
- case AF_INET:
- if (sstosin(ss)->sin_addr.s_addr == INADDR_ANY)
- addrstr[0] = '*';
- port = ntohs(sstosin(ss)->sin_port);
- break;
- case AF_INET6:
- if (IN6_IS_ADDR_UNSPECIFIED(&sstosin6(ss)->sin6_addr))
- addrstr[0] = '*';
- port = ntohs(sstosin6(ss)->sin6_port);
- break;
- case AF_UNIX:
- sun = sstosun(ss);
- off = (int)((char *)&sun->sun_path - (char *)sun);
- if (!is_text_style) {
- xo_emit("{:path/%.*s}", sun->sun_len - off,
- sun->sun_path);
- return 0;
- }
- return snprintf(buf, bufsize, "%.*s",
- sun->sun_len - off, sun->sun_path);
- }
- if (addrstr[0] == '\0') {
- error = cap_getnameinfo(capnet, sstosa(ss), ss->ss_len,
- addrstr, sizeof(addrstr), NULL, 0, NI_NUMERICHOST);
- if (error)
- xo_errx(1, "cap_getnameinfo()");
- }
- if (!is_text_style) {
- xo_emit("{:address/%s}", addrstr);
- xo_emit("{:port/%d}", port);
- return 0;
- }
- if (port == 0)
- return snprintf(buf, bufsize, "%s:*", addrstr);
- return snprintf(buf, bufsize, "%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 (cap_sysctl(capsysctl, mib, nitems(mib), &proc, &len, NULL, 0)
- == -1) {
- /* Do not warn if the process exits before we get its name. */
- if (errno != ESRCH)
- xo_warn("cap_sysctl()");
- return ("??");
- }
- return (proc.ki_comm);
-}
-
-static int
-getprocjid(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 (cap_sysctl(capsysctl, mib, nitems(mib), &proc, &len, NULL, 0)
- == -1) {
- /* Do not warn if the process exits before we get its jid. */
- if (errno != ESRCH)
- xo_warn("cap_sysctl()");
- return (-1);
- }
- return (proc.ki_jid);
-}
-
-static int
-check_ports(struct sock *s)
-{
- int port;
- struct addr *addr;
-
- if (ports == NULL)
- return (1);
- if ((s->family != AF_INET) && (s->family != AF_INET6))
- return (1);
- for (addr = s->laddr; addr != NULL; addr = addr->next) {
- if (s->family == AF_INET)
- port = ntohs(sstosin(&addr->address)->sin_port);
- else
- port = ntohs(sstosin6(&addr->address)->sin6_port);
- if (CHK_PORT(port))
- return (1);
- }
- for (addr = s->faddr; addr != NULL; addr = addr->next) {
- if (s->family == AF_INET)
- port = ntohs(sstosin(&addr->address)->sin_port);
- else
- port = ntohs(sstosin6(&addr->address)->sin6_port);
- if (CHK_PORT(port))
- return (1);
}
return (0);
}
-
-static const char *
-sctp_conn_state(int state)
-{
- switch (state) {
- case SCTP_CLOSED:
- return "CLOSED";
- break;
- case SCTP_BOUND:
- return "BOUND";
- break;
- case SCTP_LISTEN:
- return "LISTEN";
- break;
- case SCTP_COOKIE_WAIT:
- return "COOKIE_WAIT";
- break;
- case SCTP_COOKIE_ECHOED:
- return "COOKIE_ECHOED";
- break;
- case SCTP_ESTABLISHED:
- return "ESTABLISHED";
- break;
- case SCTP_SHUTDOWN_SENT:
- return "SHUTDOWN_SENT";
- break;
- case SCTP_SHUTDOWN_RECEIVED:
- return "SHUTDOWN_RECEIVED";
- break;
- case SCTP_SHUTDOWN_ACK_SENT:
- return "SHUTDOWN_ACK_SENT";
- break;
- case SCTP_SHUTDOWN_PENDING:
- return "SHUTDOWN_PENDING";
- break;
- default:
- return "UNKNOWN";
- break;
- }
-}
-
-static const char *
-sctp_path_state(int state)
-{
- switch (state) {
- case SCTP_UNCONFIRMED:
- return "UNCONFIRMED";
- break;
- case SCTP_ACTIVE:
- return "ACTIVE";
- break;
- case SCTP_INACTIVE:
- return "INACTIVE";
- break;
- default:
- return "UNKNOWN";
- break;
- }
-}
-
-static int
-format_unix_faddr(struct addr *faddr, char *buf, size_t bufsize) {
- #define SAFEBUF (buf == NULL ? NULL : buf + pos)
- #define SAFESIZE (buf == NULL ? 0 : bufsize - pos)
-
- size_t pos = 0;
- const bool is_text_style = (xo_get_style(NULL) == XO_STYLE_TEXT);
- if (faddr->conn != 0) {
- /* Remote peer we connect(2) to, if any. */
- struct sock *p;
- if (is_text_style)
- pos += strlcpy(SAFEBUF, "-> ", SAFESIZE);
- p = RB_FIND(pcbs_t, &pcbs,
- &(struct sock){ .pcb = faddr->conn });
- if (__predict_false(p == NULL) && is_text_style) {
- /* XXGL: can this happen at all? */
- pos += snprintf(SAFEBUF, SAFESIZE, "??");
- } else if (p->laddr->address.ss_len == 0) {
- struct file *f;
- f = RB_FIND(files_t, &ftree,
- &(struct file){ .xf_data =
- p->socket });
- if (f != NULL) {
- if (is_text_style) {
- pos += snprintf(SAFEBUF, SAFESIZE,
- "[%lu %d]", (u_long)f->xf_pid,
- f->xf_fd);
- } else {
- xo_open_list("connections");
- xo_open_instance("connections");
- xo_emit("{:pid/%lu}", (u_long)f->xf_pid);
- xo_emit("{:fd/%d}", f->xf_fd);
- xo_close_instance("connections");
- xo_close_list("connections");
- }
- }
- } else
- pos += formataddr(&p->laddr->address,
- SAFEBUF, SAFESIZE);
- } else if (faddr->firstref != 0) {
- /* Remote peer(s) connect(2)ed to us, if any. */
- struct sock *p;
- struct file *f;
- kvaddr_t ref = faddr->firstref;
- bool fref = true;
-
- if (is_text_style)
- pos += snprintf(SAFEBUF, SAFESIZE, " <- ");
- xo_open_list("connections");
- while ((p = RB_FIND(pcbs_t, &pcbs,
- &(struct sock){ .pcb = ref })) != 0) {
- f = RB_FIND(files_t, &ftree,
- &(struct file){ .xf_data = p->socket });
- if (f != NULL) {
- if (is_text_style) {
- pos += snprintf(SAFEBUF, SAFESIZE,
- "%s[%lu %d]", fref ? "" : ",",
- (u_long)f->xf_pid, f->xf_fd);
- } else {
- xo_open_instance("connections");
- xo_emit("{:pid/%lu}", (u_long)f->xf_pid);
- xo_emit("{:fd/%d}", f->xf_fd);
- xo_close_instance("connections");
- }
- }
- ref = p->faddr->nextref;
- fref = false;
- }
- xo_close_list("connections");
- }
- return pos;
-}
-
-struct col_widths {
- int user;
- int command;
- int pid;
- int fd;
- int proto;
- int local_addr;
- int foreign_addr;
- int pcb_kva;
- int fib;
- int splice_address;
- int inp_gencnt;
- int encaps;
- int path_state;
- int conn_state;
- int stack;
- int cc;
-};
-
-static void
-calculate_sock_column_widths(struct col_widths *cw, struct sock *s)
-{
- struct addr *laddr, *faddr;
- bool first = true;
- int len = 0;
- laddr = s->laddr;
- faddr = s->faddr;
- first = true;
-
- len = strlen(s->protoname);
- if (s->vflag & (INP_IPV4 | INP_IPV6))
- len += 1;
- cw->proto = MAX(cw->proto, len);
-
- while (laddr != NULL || faddr != NULL) {
- if (opt_w && s->family == AF_UNIX) {
- if ((laddr == NULL) || (faddr == NULL))
- xo_errx(1, "laddr = %p or faddr = %p is NULL",
- (void *)laddr, (void *)faddr);
- if (laddr->address.ss_len > 0)
- len = formataddr(&laddr->address, NULL, 0);
- cw->local_addr = MAX(cw->local_addr, len);
- len = format_unix_faddr(faddr, NULL, 0);
- cw->foreign_addr = MAX(cw->foreign_addr, len);
- } else if (opt_w) {
- if (laddr != NULL) {
- len = formataddr(&laddr->address, NULL, 0);
- cw->local_addr = MAX(cw->local_addr, len);
- }
- if (faddr != NULL) {
- len = formataddr(&faddr->address, NULL, 0);
- cw->foreign_addr = MAX(cw->foreign_addr, len);
- }
- }
- if (opt_f) {
- len = snprintf(NULL, 0, "%d", s->fibnum);
- cw->fib = MAX(cw->fib, len);
- }
- if (opt_I) {
- if (s->splice_socket != 0) {
- struct sock *sp;
-
- sp = RB_FIND(socks_t, &socks, &(struct sock)
- { .socket = s->splice_socket });
- if (sp != NULL) {
- len = formataddr(&sp->laddr->address,
- NULL, 0);
- cw->splice_address = MAX(
- cw->splice_address, len);
- }
- }
- }
- if (opt_i) {
- if (s->proto == IPPROTO_TCP || s->proto == IPPROTO_UDP)
- {
- len = snprintf(NULL, 0,
- "%" PRIu64, s->inp_gencnt);
- cw->inp_gencnt = MAX(cw->inp_gencnt, len);
- }
- }
- if (opt_U) {
- if (faddr != NULL &&
- ((s->proto == IPPROTO_SCTP &&
- s->state != SCTP_CLOSED &&
- s->state != SCTP_BOUND &&
- s->state != SCTP_LISTEN) ||
- (s->proto == IPPROTO_TCP &&
- s->state != TCPS_CLOSED &&
- s->state != TCPS_LISTEN))) {
- len = snprintf(NULL, 0, "%u",
- ntohs(faddr->encaps_port));
- cw->encaps = MAX(cw->encaps, len);
- }
- }
- if (opt_s) {
- if (faddr != NULL &&
- s->proto == IPPROTO_SCTP &&
- s->state != SCTP_CLOSED &&
- s->state != SCTP_BOUND &&
- s->state != SCTP_LISTEN) {
- len = strlen(sctp_path_state(faddr->state));
- cw->path_state = MAX(cw->path_state, len);
- }
- }
- if (first) {
- if (opt_s) {
- if (s->proto == IPPROTO_SCTP ||
- s->proto == IPPROTO_TCP) {
- switch (s->proto) {
- case IPPROTO_SCTP:
- len = strlen(
- sctp_conn_state(s->state));
- cw->conn_state = MAX(
- cw->conn_state, len);
- break;
- case IPPROTO_TCP:
- if (s->state >= 0 &&
- s->state < TCP_NSTATES) {
- len = strlen(
- tcpstates[s->state]);
- cw->conn_state = MAX(
- cw->conn_state, len);
- }
- break;
- }
- }
- }
- if (opt_S && s->proto == IPPROTO_TCP) {
- len = strlen(s->stack);
- cw->stack = MAX(cw->stack, len);
- }
- if (opt_C && s->proto == IPPROTO_TCP) {
- len = strlen(s->cc);
- cw->cc = MAX(cw->cc, len);
- }
- }
- if (laddr != NULL)
- laddr = laddr->next;
- if (faddr != NULL)
- faddr = faddr->next;
- first = false;
- }
-}
-
-static void
-calculate_column_widths(struct col_widths *cw)
-{
- int n, len;
- struct file *xf;
- struct sock *s;
- struct passwd *pwd;
-
- cap_setpassent(cappwd, 1);
- for (xf = files, n = 0; n < nfiles; ++n, ++xf) {
- if (xf->xf_data == 0)
- continue;
- if (opt_j >= 0 && opt_j != getprocjid(xf->xf_pid))
- continue;
- s = RB_FIND(socks_t, &socks,
- &(struct sock){ .socket = xf->xf_data});
- if (s == NULL || (!check_ports(s)))
- continue;
- s->shown = 1;
- if (opt_n ||
- (pwd = cap_getpwuid(cappwd, xf->xf_uid)) == NULL)
- len = snprintf(NULL, 0, "%lu", (u_long)xf->xf_uid);
- else
- len = snprintf(NULL, 0, "%s", pwd->pw_name);
- cw->user = MAX(cw->user, len);
- len = snprintf(NULL, 0, "%lu", (u_long)xf->xf_pid);
- cw->pid = MAX(cw->pid, len);
- len = snprintf(NULL, 0, "%d", xf->xf_fd);
- cw->fd = MAX(cw->fd, len);
-
- calculate_sock_column_widths(cw, s);
- }
- if (opt_j >= 0)
- return;
- SLIST_FOREACH(s, &nosocks, socket_list) {
- if (!check_ports(s))
- continue;
- calculate_sock_column_widths(cw, s);
- }
- RB_FOREACH(s, socks_t, &socks) {
- if (s->shown)
- continue;
- if (!check_ports(s))
- continue;
- calculate_sock_column_widths(cw, s);
- }
-}
-
-static void
-display_sock(struct sock *s, struct col_widths *cw, char *buf, size_t bufsize)
-{
- struct addr *laddr, *faddr;
- bool first;
- laddr = s->laddr;
- faddr = s->faddr;
- first = true;
- const bool is_text_style = (xo_get_style(NULL) == XO_STYLE_TEXT);
-
- snprintf(buf, bufsize, "%s%s%s",
- s->protoname,
- s->vflag & INP_IPV4 ? "4" : "",
- s->vflag & INP_IPV6 ? "6" : "");
- xo_emit(" {:proto/%-*s}", cw->proto, buf);
- while (laddr != NULL || faddr != NULL) {
- if (s->family == AF_UNIX) {
- if ((laddr == NULL) || (faddr == NULL))
- xo_errx(1, "laddr = %p or faddr = %p is NULL",
- (void *)laddr, (void *)faddr);
- if (laddr->address.ss_len > 0) {
- xo_open_container("local");
- formataddr(&laddr->address, buf, bufsize);
- if (is_text_style) {
- xo_emit(" {:/%-*.*s}", cw->local_addr,
- cw->local_addr, buf);
- }
- xo_close_container("local");
- } else if (laddr->address.ss_len == 0 &&
- faddr->conn == 0 && is_text_style) {
- xo_emit(" {:/%-*.*s}", cw->local_addr,
- cw->local_addr, "(not connected)");
- } else if (is_text_style) {
- xo_emit(" {:/%-*.*s}", cw->local_addr,
- cw->local_addr, "??");
- }
- if (faddr->conn != 0 || faddr->firstref != 0) {
- xo_open_container("foreign");
- int len = format_unix_faddr(faddr, buf,
- bufsize);
- if (len == 0 && is_text_style)
- xo_emit(" {:/%-*s}",
- cw->foreign_addr, "??");
- else if (is_text_style)
- xo_emit(" {:/%-*.*s}", cw->foreign_addr,
- cw->foreign_addr, buf);
- xo_close_container("foreign");
- } else if (is_text_style)
- xo_emit(" {:/%-*s}", cw->foreign_addr, "??");
- } else {
- if (laddr != NULL) {
- xo_open_container("local");
- formataddr(&laddr->address, buf, bufsize);
- if (is_text_style) {
- xo_emit(" {:/%-*.*s}", cw->local_addr,
- cw->local_addr, buf);
- }
- xo_close_container("local");
- } else if (is_text_style)
- xo_emit(" {:/%-*.*s}", cw->local_addr,
- cw->local_addr, "??");
- if (faddr != NULL) {
- xo_open_container("foreign");
- formataddr(&faddr->address, buf, bufsize);
- if (is_text_style) {
- xo_emit(" {:/%-*.*s}", cw->foreign_addr,
- cw->foreign_addr, buf);
- }
- xo_close_container("foreign");
- } else if (is_text_style) {
- xo_emit(" {:/%-*.*s}", cw->foreign_addr,
- cw->foreign_addr, "??");
- }
- }
- if (opt_A) {
- snprintf(buf, bufsize, "%#*" PRIx64,
- cw->pcb_kva, s->pcb);
- xo_emit(" {:pcb-kva/%s}", buf);
- }
- if (opt_f)
- xo_emit(" {:fib/%*d}", cw->fib, s->fibnum);
- if (opt_I) {
- if (s->splice_socket != 0) {
- struct sock *sp;
- sp = RB_FIND(socks_t, &socks, &(struct sock)
- { .socket = s->splice_socket });
- if (sp != NULL) {
- xo_open_container("splice");
- formataddr(&sp->laddr->address,
- buf, bufsize);
- xo_close_container("splice");
- } else if (is_text_style)
- strlcpy(buf, "??", bufsize);
- } else if (is_text_style)
- strlcpy(buf, "??", bufsize);
- if (is_text_style)
- xo_emit(" {:/%-*s}", cw->splice_address, buf);
- }
- if (opt_i) {
- if (s->proto == IPPROTO_TCP || s->proto == IPPROTO_UDP)
- {
- snprintf(buf, bufsize, "%" PRIu64,
- s->inp_gencnt);
- xo_emit(" {:id/%*s}", cw->inp_gencnt, buf);
- } else if (is_text_style)
- xo_emit(" {:/%*s}", cw->inp_gencnt, "??");
- }
- if (opt_U) {
- if (faddr != NULL &&
- ((s->proto == IPPROTO_SCTP &&
- s->state != SCTP_CLOSED &&
- s->state != SCTP_BOUND &&
- s->state != SCTP_LISTEN) ||
- (s->proto == IPPROTO_TCP &&
- s->state != TCPS_CLOSED &&
- s->state != TCPS_LISTEN))) {
- xo_emit(" {:encaps/%*u}", cw->encaps,
- ntohs(faddr->encaps_port));
- } else if (is_text_style)
- xo_emit(" {:/%*s}", cw->encaps, "??");
- }
- if (opt_s) {
- if (faddr != NULL &&
- s->proto == IPPROTO_SCTP &&
- s->state != SCTP_CLOSED &&
- s->state != SCTP_BOUND &&
- s->state != SCTP_LISTEN) {
- xo_emit(" {:path-state/%-*s}", cw->path_state,
- sctp_path_state(faddr->state));
- } else if (is_text_style)
- xo_emit(" {:/%-*s}", cw->path_state, "??");
- }
- if (first) {
- if (opt_s) {
- if (s->proto == IPPROTO_SCTP ||
- s->proto == IPPROTO_TCP) {
- switch (s->proto) {
- case IPPROTO_SCTP:
- xo_emit(" {:conn-state/%-*s}",
- cw->conn_state,
- sctp_conn_state(s->state));
- break;
- case IPPROTO_TCP:
- if (s->state >= 0 &&
- s->state < TCP_NSTATES)
- xo_emit(" {:conn-state/%-*s}",
- cw->conn_state,
- tcpstates[s->state]);
- else if (is_text_style)
- xo_emit(" {:/%-*s}",
- cw->conn_state, "??");
- break;
- }
- } else if (is_text_style)
- xo_emit(" {:/%-*s}",
- cw->conn_state, "??");
- }
- if (opt_S) {
- if (s->proto == IPPROTO_TCP)
- xo_emit(" {:stack/%-*s}",
- cw->stack, s->stack);
- else if (is_text_style)
- xo_emit(" {:/%-*s}",
- cw->stack, "??");
- }
- if (opt_C) {
- if (s->proto == IPPROTO_TCP)
- xo_emit(" {:cc/%-*s}", cw->cc, s->cc);
- else if (is_text_style)
- xo_emit(" {:/%-*s}", cw->cc, "??");
- }
- }
- if (laddr != NULL)
- laddr = laddr->next;
- if (faddr != NULL)
- faddr = faddr->next;
- if (is_text_style && (laddr != NULL || faddr != NULL))
- xo_emit("{:/%-*s} {:/%-*s} {:/%*s} {:/%*s}",
- cw->user, "??", cw->command, "??",
- cw->pid, "??", cw->fd, "??");
- first = false;
- }
- xo_emit("\n");
-}
-
-static void
-display(void)
-{
- struct passwd *pwd;
- struct file *xf;
- struct sock *s;
- int n;
- struct col_widths cw;
- const size_t bufsize = 512;
- void *buf;
- if ((buf = (char *)malloc(bufsize)) == NULL) {
- xo_err(1, "malloc()");
- return;
- }
-
- if (xo_get_style(NULL) == XO_STYLE_TEXT) {
- cw = (struct col_widths) {
- .user = strlen("USER"),
- .command = 10,
- .pid = strlen("PID"),
- .fd = strlen("FD"),
- .proto = strlen("PROTO"),
- .local_addr = opt_w ? strlen("LOCAL ADDRESS") : 21,
- .foreign_addr = opt_w ? strlen("FOREIGN ADDRESS") : 21,
- .pcb_kva = 18,
- .fib = strlen("FIB"),
- .splice_address = strlen("SPLICE ADDRESS"),
- .inp_gencnt = strlen("ID"),
- .encaps = strlen("ENCAPS"),
- .path_state = strlen("PATH STATE"),
- .conn_state = strlen("CONN STATE"),
- .stack = strlen("STACK"),
- .cc = strlen("CC"),
- };
- calculate_column_widths(&cw);
- } else
- memset(&cw, 0, sizeof(cw));
-
- xo_set_version(SOCKSTAT_XO_VERSION);
- xo_open_container("sockstat");
- xo_open_list("socket");
- if (!opt_q) {
- xo_emit("{T:/%-*s} {T:/%-*s} {T:/%*s} {T:/%*s} {T:/%-*s} "
- "{T:/%-*s} {T:/%-*s}", cw.user, "USER", cw.command,
- "COMMAND", cw.pid, "PID", cw.fd, "FD", cw.proto,
- "PROTO", cw.local_addr, "LOCAL ADDRESS",
- cw.foreign_addr, "FOREIGN ADDRESS");
- if (opt_A)
- xo_emit(" {T:/%-*s}", cw.pcb_kva, "PCB KVA");
- if (opt_f)
- /* RT_MAXFIBS is 65535. */
- xo_emit(" {T:/%*s}", cw.fib, "FIB");
- if (opt_I)
- xo_emit(" {T:/%-*s}", cw.splice_address,
- "SPLICE ADDRESS");
- if (opt_i)
- xo_emit(" {T:/%*s}", cw.inp_gencnt, "ID");
- if (opt_U)
- xo_emit(" {T:/%*s}", cw.encaps, "ENCAPS");
- if (opt_s) {
- xo_emit(" {T:/%-*s}", cw.path_state, "PATH STATE");
- xo_emit(" {T:/%-*s}", cw.conn_state, "CONN STATE");
- }
- if (opt_S)
- xo_emit(" {T:/%-*s}", cw.stack, "STACK");
- if (opt_C)
- xo_emit(" {T:/%-*s}", cw.cc, "CC");
- xo_emit("\n");
- }
- cap_setpassent(cappwd, 1);
- for (xf = files, n = 0; n < nfiles; ++n, ++xf) {
- if (xf->xf_data == 0)
- continue;
- if (opt_j >= 0 && opt_j != getprocjid(xf->xf_pid))
- continue;
- s = RB_FIND(socks_t, &socks,
- &(struct sock){ .socket = xf->xf_data});
- if (s != NULL && check_ports(s)) {
- xo_open_instance("socket");
- s->shown = 1;
- if (opt_n ||
- (pwd = cap_getpwuid(cappwd, xf->xf_uid)) == NULL)
- xo_emit("{:user/%-*lu}", cw.user,
- (u_long)xf->xf_uid);
- else
- xo_emit("{:user/%-*s}", cw.user, pwd->pw_name);
- if (xo_get_style(NULL) == XO_STYLE_TEXT)
- xo_emit(" {:/%-*.10s}", cw.command,
- getprocname(xf->xf_pid));
- else
- xo_emit(" {:command/%-*s}", cw.command,
- getprocname(xf->xf_pid));
- xo_emit(" {:pid/%*lu}", cw.pid, (u_long)xf->xf_pid);
- xo_emit(" {:fd/%*d}", cw.fd, xf->xf_fd);
- display_sock(s, &cw, buf, bufsize);
- xo_close_instance("socket");
- }
- }
- if (opt_j >= 0)
- return;
- SLIST_FOREACH(s, &nosocks, socket_list) {
- if (!check_ports(s))
- continue;
- xo_open_instance("socket");
- if (xo_get_style(NULL) == XO_STYLE_TEXT)
- xo_emit("{:/%-*s} {:/%-*s} {:/%*s} {:/%*s}",
- cw.user, "??", cw.command, "??",
- cw.pid, "??", cw.fd, "??");
- display_sock(s, &cw, buf, bufsize);
- xo_close_instance("socket");
- }
- RB_FOREACH(s, socks_t, &socks) {
- if (s->shown)
- continue;
- if (!check_ports(s))
- continue;
- xo_open_instance("socket");
- if (xo_get_style(NULL) == XO_STYLE_TEXT)
- xo_emit("{:/%-*s} {:/%-*s} {:/%*s} {:/%*s}",
- cw.user, "??", cw.command, "??",
- cw.pid, "??", cw.fd, "??");
- display_sock(s, &cw, buf, bufsize);
- xo_close_instance("socket");
- }
- xo_close_list("socket");
- xo_close_container("sockstat");
- if (xo_finish() < 0)
- xo_err(1, "stdout");
- free(buf);
- cap_endpwent(cappwd);
-}
-
-static int
-set_default_protos(void)
-{
- struct protoent *prot;
- const char *pname;
- size_t pindex;
-
- init_protos(default_numprotos);
-
- for (pindex = 0; pindex < default_numprotos; pindex++) {
- pname = default_protos[pindex];
- prot = cap_getprotobyname(capnetdb, pname);
- if (prot == NULL)
- xo_err(1, "cap_getprotobyname: %s", pname);
- protos[pindex] = prot->p_proto;
- }
- numprotos = pindex;
- return (pindex);
-}
-
-/*
- * Return the vnet property of the jail, or -1 on error.
- */
-static int
-jail_getvnet(int jid)
-{
- struct iovec jiov[6];
- int vnet;
- size_t len = sizeof(vnet);
-
- if (sysctlbyname("kern.features.vimage", &vnet, &len, NULL, 0) != 0)
- return (0);
-
- vnet = -1;
- jiov[0].iov_base = __DECONST(char *, "jid");
- jiov[0].iov_len = sizeof("jid");
- jiov[1].iov_base = &jid;
- jiov[1].iov_len = sizeof(jid);
- jiov[2].iov_base = __DECONST(char *, "vnet");
- jiov[2].iov_len = sizeof("vnet");
- jiov[3].iov_base = &vnet;
- jiov[3].iov_len = sizeof(vnet);
- jiov[4].iov_base = __DECONST(char *, "errmsg");
- jiov[4].iov_len = sizeof("errmsg");
- jiov[5].iov_base = jail_errmsg;
- jiov[5].iov_len = JAIL_ERRMSGLEN;
- jail_errmsg[0] = '\0';
- if (jail_get(jiov, nitems(jiov), 0) < 0) {
- if (!jail_errmsg[0])
- snprintf(jail_errmsg, JAIL_ERRMSGLEN,
- "jail_get: %s", strerror(errno));
- return (-1);
- }
- return (vnet);
-}
-
-static void
-usage(void)
-{
- xo_error(
-"usage: sockstat [--libxo] [-46ACcfIiLlnqSsUuvw] [-j jid] [-p ports]\n"
-" [-P protocols]\n");
- exit(1);
-}
-
-int
-main(int argc, char *argv[])
-{
- cap_channel_t *capcas;
- cap_net_limit_t *limit;
- const char *pwdcmds[] = { "setpassent", "getpwuid" };
- const char *pwdfields[] = { "pw_name" };
- int protos_defined = -1;
- int o, i;
-
- argc = xo_parse_args(argc, argv);
- if (argc < 0)
- exit(1);
- opt_j = -1;
- while ((o = getopt(argc, argv, "46ACcfIij:Llnp:P:qSsUuvw")) != -1)
- switch (o) {
- case '4':
- opt_4 = true;
- break;
- case '6':
- opt_6 = true;
- break;
- case 'A':
- opt_A = true;
- break;
- case 'C':
- opt_C = true;
- break;
- case 'c':
- opt_c = true;
- break;
- case 'f':
- opt_f = true;
- break;
- case 'I':
- opt_I = true;
- break;
- case 'i':
- opt_i = true;
- break;
- case 'j':
- opt_j = jail_getid(optarg);
- if (opt_j < 0)
- xo_errx(1, "jail_getid: %s", jail_errmsg);
- break;
- case 'L':
- opt_L = true;
- break;
- case 'l':
- opt_l = true;
- break;
- case 'n':
- opt_n = true;
- break;
- case 'p':
- parse_ports(optarg);
- break;
- case 'P':
- protos_defined = parse_protos(optarg);
- break;
- case 'q':
- opt_q = true;
- break;
- case 'S':
- opt_S = true;
- break;
- case 's':
- opt_s = true;
- break;
- case 'U':
- opt_U = true;
- break;
- case 'u':
- opt_u = true;
- break;
- case 'v':
- ++opt_v;
- break;
- case 'w':
- opt_w = true;
- break;
- default:
- usage();
- }
-
- argc -= optind;
- argv += optind;
-
- if (argc > 0)
- usage();
-
- if (opt_j > 0) {
- switch (jail_getvnet(opt_j)) {
- case -1:
- xo_errx(2, "jail_getvnet: %s", jail_errmsg);
- case JAIL_SYS_NEW:
- if (jail_attach(opt_j) < 0)
- xo_err(3, "jail_attach()");
- /* Set back to -1 for normal output in vnet jail. */
- opt_j = -1;
- break;
- default:
- break;
- }
- }
-
- capcas = cap_init();
- if (capcas == NULL)
- xo_err(1, "Unable to contact Casper");
- if (caph_enter_casper() < 0)
- xo_err(1, "Unable to enter capability mode");
- capnet = cap_service_open(capcas, "system.net");
- if (capnet == NULL)
- xo_err(1, "Unable to open system.net service");
- capnetdb = cap_service_open(capcas, "system.netdb");
- if (capnetdb == NULL)
- xo_err(1, "Unable to open system.netdb service");
- capsysctl = cap_service_open(capcas, "system.sysctl");
- if (capsysctl == NULL)
- xo_err(1, "Unable to open system.sysctl service");
- cappwd = cap_service_open(capcas, "system.pwd");
- if (cappwd == NULL)
- xo_err(1, "Unable to open system.pwd service");
- cap_close(capcas);
- limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
- if (limit == NULL)
- xo_err(1, "Unable to init cap_net limits");
- if (cap_net_limit(limit) < 0)
- xo_err(1, "Unable to apply limits");
- if (cap_pwd_limit_cmds(cappwd, pwdcmds, nitems(pwdcmds)) < 0)
- xo_err(1, "Unable to apply pwd commands limits");
- if (cap_pwd_limit_fields(cappwd, pwdfields, nitems(pwdfields)) < 0)
- xo_err(1, "Unable to apply pwd commands limits");
-
- if ((!opt_4 && !opt_6) && protos_defined != -1)
- opt_4 = opt_6 = true;
- if (!opt_4 && !opt_6 && !opt_u)
- opt_4 = opt_6 = opt_u = true;
- if ((opt_4 || opt_6) && protos_defined == -1)
- protos_defined = set_default_protos();
- if (!opt_c && !opt_l)
- opt_c = opt_l = true;
-
- if (opt_4 || opt_6) {
- for (i = 0; i < protos_defined; i++)
- if (protos[i] == IPPROTO_SCTP)
- gather_sctp();
- else
- gather_inet(protos[i]);
- }
-
- if (opt_u || (protos_defined == -1 && !opt_4 && !opt_6)) {
- gather_unix(SOCK_STREAM);
- gather_unix(SOCK_DGRAM);
- gather_unix(SOCK_SEQPACKET);
- }
- getfiles();
- display();
- exit(0);
-}
diff --git a/usr.bin/sockstat/sockstat.h b/usr.bin/sockstat/sockstat.h
new file mode 100644
index 000000000000..80d91ebbaddc
--- /dev/null
+++ b/usr.bin/sockstat/sockstat.h
@@ -0,0 +1,35 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 ConnectWise
+ * 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.
+ *
+ * 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.
+ */
+
+#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)))
+
+extern int *ports;
+
+int parse_ports(const char *portspec);
diff --git a/usr.bin/sockstat/tests/Makefile b/usr.bin/sockstat/tests/Makefile
new file mode 100644
index 000000000000..9971bca2d474
--- /dev/null
+++ b/usr.bin/sockstat/tests/Makefile
@@ -0,0 +1,8 @@
+ATF_TESTS_C+= sockstat_test
+SRCS.sockstat_test= sockstat_test.c ../sockstat.c
+
+LIBADD= xo
+
+PACKAGE= tests
+
+.include <bsd.test.mk>
diff --git a/usr.bin/sockstat/tests/sockstat_test.c b/usr.bin/sockstat/tests/sockstat_test.c
new file mode 100644
index 000000000000..2d2a23c7a166
--- /dev/null
+++ b/usr.bin/sockstat/tests/sockstat_test.c
@@ -0,0 +1,190 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 ConnectWise
+ * 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.
+ *
+ * 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/param.h>
+#include <sys/errno.h>
+
+#include <atf-c.h>
+
+#include "../sockstat.h"
+
+ATF_TC_WITHOUT_HEAD(backwards_range);
+ATF_TC_BODY(backwards_range, tc)
+{
+ const char portspec[] = "22-21";
+
+ ATF_CHECK_EQ(ERANGE, parse_ports(portspec));
+}
+
+ATF_TC_WITHOUT_HEAD(erange_low);
+ATF_TC_BODY(erange_low, tc)
+{
+ const char portspec[] = "-1";
+
+ ATF_CHECK_EQ(ERANGE, parse_ports(portspec));
+}
+
+ATF_TC_WITHOUT_HEAD(erange_high);
+ATF_TC_BODY(erange_high, tc)
+{
+ const char portspec[] = "65536";
+
+ ATF_CHECK_EQ(ERANGE, parse_ports(portspec));
+}
+
+ATF_TC_WITHOUT_HEAD(erange_on_range_end);
+ATF_TC_BODY(erange_on_range_end, tc)
+{
+ const char portspec[] = "22-65536";
+
+ ATF_CHECK_EQ(ERANGE, parse_ports(portspec));
+}
+
+ATF_TC_WITHOUT_HEAD(multiple);
+ATF_TC_BODY(multiple, tc)
+{
+ const char portspec[] = "80,443";
+
+ ATF_REQUIRE_EQ(0, parse_ports(portspec));
+
+ ATF_CHECK(!CHK_PORT(0));
+
+ ATF_CHECK(!CHK_PORT(79));
+ ATF_CHECK(CHK_PORT(80));
+ ATF_CHECK(!CHK_PORT(81));
+
+ ATF_CHECK(!CHK_PORT(442));
+ ATF_CHECK(CHK_PORT(443));
+ ATF_CHECK(!CHK_PORT(444));
+}
+
+ATF_TC_WITHOUT_HEAD(multiple_plus_ranges);
+ATF_TC_BODY(multiple_plus_ranges, tc)
+{
+ const char portspec[] = "80,443,500-501,510,520,40000-40002";
+
+ ATF_REQUIRE_EQ(0, parse_ports(portspec));
+
+ ATF_CHECK(!CHK_PORT(0));
+
+ ATF_CHECK(!CHK_PORT(79));
+ ATF_CHECK(CHK_PORT(80));
+ ATF_CHECK(!CHK_PORT(81));
+
+ ATF_CHECK(!CHK_PORT(442));
+ ATF_CHECK(CHK_PORT(443));
+ ATF_CHECK(!CHK_PORT(444));
+
+ ATF_CHECK(!CHK_PORT(499));
+ ATF_CHECK(CHK_PORT(500));
+ ATF_CHECK(CHK_PORT(501));
+ ATF_CHECK(!CHK_PORT(502));
+
+ ATF_CHECK(!CHK_PORT(519));
+ ATF_CHECK(CHK_PORT(520));
+ ATF_CHECK(!CHK_PORT(521));
+
+ ATF_CHECK(!CHK_PORT(39999));
+ ATF_CHECK(CHK_PORT(40000));
+ ATF_CHECK(CHK_PORT(40001));
+ ATF_CHECK(CHK_PORT(40002));
+ ATF_CHECK(!CHK_PORT(40003));
+}
+
+ATF_TC_WITHOUT_HEAD(nonnumeric);
+ATF_TC_BODY(nonnumeric, tc)
+{
+ const char portspec[] = "foo";
+
+ ATF_CHECK_EQ(EINVAL, parse_ports(portspec));
+}
+
+ATF_TC_WITHOUT_HEAD(null_range);
+ATF_TC_BODY(null_range, tc)
+{
+ const char portspec[] = "22-22";
+
+ ATF_REQUIRE_EQ(0, parse_ports(portspec));
+
+ ATF_CHECK(!CHK_PORT(0));
+ ATF_CHECK(CHK_PORT(22));
+ ATF_CHECK(!CHK_PORT(23));
+}
+
+ATF_TC_WITHOUT_HEAD(range);
+ATF_TC_BODY(range, tc)
+{
+ const char portspec[] = "22-25";
+
+ ATF_REQUIRE_EQ(0, parse_ports(portspec));
+
+ ATF_CHECK(!CHK_PORT(0));
+ ATF_CHECK(CHK_PORT(22));
+ ATF_CHECK(CHK_PORT(23));
+ ATF_CHECK(CHK_PORT(24));
+ ATF_CHECK(CHK_PORT(25));
+ ATF_CHECK(!CHK_PORT(26));
+}
+
+ATF_TC_WITHOUT_HEAD(single);
+ATF_TC_BODY(single, tc)
+{
+ const char portspec[] = "22";
+
+ ATF_REQUIRE_EQ(0, parse_ports(portspec));
+
+ ATF_CHECK(!CHK_PORT(0));
+ ATF_CHECK(CHK_PORT(22));
+}
+
+ATF_TC_WITHOUT_HEAD(zero);
+ATF_TC_BODY(zero, tc)
+{
+ const char portspec[] = "0";
+
+ ATF_REQUIRE_EQ(0, parse_ports(portspec));
+
+ ATF_CHECK(CHK_PORT(0));
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, backwards_range);
+ ATF_TP_ADD_TC(tp, erange_low);
+ ATF_TP_ADD_TC(tp, erange_high);
+ ATF_TP_ADD_TC(tp, erange_on_range_end);
+ ATF_TP_ADD_TC(tp, multiple);
+ ATF_TP_ADD_TC(tp, multiple_plus_ranges);
+ ATF_TP_ADD_TC(tp, nonnumeric);
+ ATF_TP_ADD_TC(tp, null_range);
+ ATF_TP_ADD_TC(tp, range);
+ ATF_TP_ADD_TC(tp, single);
+ ATF_TP_ADD_TC(tp, zero);
+
+ return (atf_no_error());
+}