aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/sockstat/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/sockstat/main.c')
-rw-r--r--usr.bin/sockstat/main.c1883
1 files changed, 1883 insertions, 0 deletions
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);
+}