aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/netstat
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/netstat')
-rw-r--r--usr.bin/netstat/Makefile72
-rw-r--r--usr.bin/netstat/Makefile.depend21
-rw-r--r--usr.bin/netstat/Makefile.depend.options7
-rw-r--r--usr.bin/netstat/bpf.c167
-rw-r--r--usr.bin/netstat/common.c136
-rw-r--r--usr.bin/netstat/common.h90
-rw-r--r--usr.bin/netstat/if.c745
-rw-r--r--usr.bin/netstat/inet.c1549
-rw-r--r--usr.bin/netstat/inet6.c1359
-rw-r--r--usr.bin/netstat/ipsec.c433
-rw-r--r--usr.bin/netstat/main.c935
-rw-r--r--usr.bin/netstat/mbuf.c354
-rw-r--r--usr.bin/netstat/mroute.c462
-rw-r--r--usr.bin/netstat/mroute6.c274
-rw-r--r--usr.bin/netstat/netgraph.c141
-rw-r--r--usr.bin/netstat/netisr.c506
-rw-r--r--usr.bin/netstat/netstat.1978
-rw-r--r--usr.bin/netstat/netstat.h168
-rw-r--r--usr.bin/netstat/nhgrp.c352
-rw-r--r--usr.bin/netstat/nhops.c477
-rw-r--r--usr.bin/netstat/nlist_symbols50
-rw-r--r--usr.bin/netstat/pfkey.c200
-rw-r--r--usr.bin/netstat/route.c724
-rw-r--r--usr.bin/netstat/route_netlink.c340
-rw-r--r--usr.bin/netstat/sctp.c841
-rw-r--r--usr.bin/netstat/unix.c320
26 files changed, 11701 insertions, 0 deletions
diff --git a/usr.bin/netstat/Makefile b/usr.bin/netstat/Makefile
new file mode 100644
index 000000000000..121911b8a18b
--- /dev/null
+++ b/usr.bin/netstat/Makefile
@@ -0,0 +1,72 @@
+.include <src.opts.mk>
+
+PROG= netstat
+SRCS= if.c inet.c main.c mbuf.c mroute.c netisr.c nl_symbols.c route.c \
+ unix.c mroute6.c ipsec.c bpf.c pfkey.c sctp.c common.c nhops.c nhgrp.c \
+ nl_defs.h
+
+nl_symbols.c: nlist_symbols
+ awk '\
+ BEGIN { \
+ print "#include <sys/param.h>"; \
+ print "#include <nlist.h>"; \
+ print "struct nlist nl[] = {"; \
+ } \
+ !/^\#/ { printf("\t{ .n_name = \"%s\" },\n", $$2); } \
+ END { print "\t{ .n_name = NULL },\n};" } \
+ ' < ${.ALLSRC} > ${.TARGET} || rm -f ${.TARGET}
+nl_defs.h: nlist_symbols
+ awk '\
+ BEGIN { \
+ print "#include <nlist.h>"; \
+ print "extern struct nlist nl[];"; \
+ i = 0; \
+ } \
+ !/^\#/ { printf("\#define\tN%s\t%s\n", toupper($$2), i++); }' \
+ < ${.ALLSRC} > ${.TARGET} || rm -f ${.TARGET}
+CLEANFILES+= nl_symbols.c nl_defs.h
+CFLAGS+= -I${.OBJDIR}
+
+WARNS?= 3
+CFLAGS+=-fno-strict-aliasing
+
+CFLAGS+=-DIPSEC
+CFLAGS+=-DSCTP
+
+.if ${MK_INET_SUPPORT} != "no"
+CFLAGS+=-DINET
+.endif
+
+.if ${MK_INET6_SUPPORT} != "no"
+SRCS+= inet6.c
+CFLAGS+=-DINET6
+.endif
+
+.if ${MK_OFED} != "no"
+CFLAGS+=-DSDP
+.endif
+
+.if ${MK_PF} != "no"
+CFLAGS+=-DPF
+.endif
+
+LIBADD= kvm memstat xo util
+
+.if ${MK_NETGRAPH_SUPPORT} != "no"
+SRCS+= netgraph.c
+LIBADD+= netgraph
+CFLAGS+=-DNETGRAPH
+.endif
+
+.if ${MK_NETLINK_SUPPORT} != "no"
+SRCS+= route_netlink.c
+.else
+CFLAGS+=-DWITHOUT_NETLINK
+.endif
+
+.if ${MK_JAIL} != "no" && !defined(RESCUE)
+CFLAGS+= -DJAIL
+LIBADD+= jail
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/netstat/Makefile.depend b/usr.bin/netstat/Makefile.depend
new file mode 100644
index 000000000000..9fe03c55bea9
--- /dev/null
+++ b/usr.bin/netstat/Makefile.depend
@@ -0,0 +1,21 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libkvm \
+ lib/libmemstat \
+ lib/libutil \
+ lib/libxo/libxo \
+ usr.bin/awk.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.bin/netstat/Makefile.depend.options b/usr.bin/netstat/Makefile.depend.options
new file mode 100644
index 000000000000..b741c9f5954b
--- /dev/null
+++ b/usr.bin/netstat/Makefile.depend.options
@@ -0,0 +1,7 @@
+# This file is not autogenerated - take care!
+
+DIRDEPS_OPTIONS= NETGRAPH_SUPPORT
+
+DIRDEPS.NETGRAPH_SUPPORT.yes= lib/libnetgraph
+
+.include <dirdeps-options.mk>
diff --git a/usr.bin/netstat/bpf.c b/usr.bin/netstat/bpf.c
new file mode 100644
index 000000000000..5d61da7b4d2b
--- /dev/null
+++ b/usr.bin/netstat/bpf.c
@@ -0,0 +1,167 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2005 Christian S.J. Peron
+ * 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.
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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/types.h>
+#include <sys/protosw.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+#include <sys/param.h>
+#include <sys/user.h>
+
+#include <net/if.h>
+#include <net/bpf.h>
+#include <net/bpfdesc.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+
+#include "netstat.h"
+
+/* print bpf stats */
+
+static char *
+bpf_pidname(pid_t pid)
+{
+ struct kinfo_proc newkp;
+ int error, mib[4];
+ size_t size;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = pid;
+ size = sizeof(newkp);
+ error = sysctl(mib, 4, &newkp, &size, NULL, 0);
+ if (error < 0) {
+ xo_warn("kern.proc.pid failed");
+ return (strdup("??????"));
+ }
+ return (strdup(newkp.ki_comm));
+}
+
+static void
+bpf_flags(struct xbpf_d *bd, char *flagbuf)
+{
+
+ *flagbuf++ = bd->bd_promisc ? 'p' : '-';
+ *flagbuf++ = bd->bd_immediate ? 'i' : '-';
+ *flagbuf++ = bd->bd_hdrcmplt ? '-' : 'f';
+ *flagbuf++ = (bd->bd_direction == BPF_D_IN) ? '-' :
+ ((bd->bd_direction == BPF_D_OUT) ? 'o' : 's');
+ *flagbuf++ = bd->bd_feedback ? 'b' : '-';
+ *flagbuf++ = bd->bd_async ? 'a' : '-';
+ *flagbuf++ = bd->bd_locked ? 'l' : '-';
+ *flagbuf++ = '\0';
+
+ if (bd->bd_promisc)
+ xo_emit("{e:promiscuous/}");
+ if (bd->bd_immediate)
+ xo_emit("{e:immediate/}");
+ if (bd->bd_hdrcmplt)
+ xo_emit("{e:header-complete/}");
+ xo_emit("{e:direction}", (bd->bd_direction == BPF_D_IN) ? "input" :
+ (bd->bd_direction == BPF_D_OUT) ? "output" : "bidirectional");
+ if (bd->bd_feedback)
+ xo_emit("{e:feedback/}");
+ if (bd->bd_async)
+ xo_emit("{e:async/}");
+ if (bd->bd_locked)
+ xo_emit("{e:locked/}");
+}
+
+void
+bpf_stats(char *ifname)
+{
+ struct xbpf_d *d, *bd, zerostat;
+ char *pname, flagbuf[12];
+ size_t size;
+
+ if (zflag) {
+ bzero(&zerostat, sizeof(zerostat));
+ if (sysctlbyname("net.bpf.stats", NULL, NULL,
+ &zerostat, sizeof(zerostat)) < 0)
+ xo_warn("failed to zero bpf counters");
+ return;
+ }
+ if (sysctlbyname("net.bpf.stats", NULL, &size,
+ NULL, 0) < 0) {
+ xo_warn("net.bpf.stats");
+ return;
+ }
+ if (size == 0)
+ return;
+ bd = malloc(size);
+ if (bd == NULL) {
+ xo_warn("malloc failed");
+ return;
+ }
+ if (sysctlbyname("net.bpf.stats", bd, &size,
+ NULL, 0) < 0) {
+ xo_warn("net.bpf.stats");
+ free(bd);
+ return;
+ }
+ xo_emit("{T:/%5s} {T:/%6s} {T:/%7s} {T:/%9s} {T:/%9s} {T:/%9s} "
+ "{T:/%5s} {T:/%5s} {T:/%s}\n",
+ "Pid", "Netif", "Flags", "Recv", "Drop", "Match",
+ "Sblen", "Hblen", "Command");
+ xo_open_container("bpf-statistics");
+ xo_open_list("bpf-entry");
+ for (d = &bd[0]; d < &bd[size / sizeof(*d)]; d++) {
+ if (d->bd_structsize != sizeof(*d)) {
+ xo_warnx("bpf_stats_extended: version mismatch");
+ return;
+ }
+ if (ifname && strcmp(ifname, d->bd_ifname) != 0)
+ continue;
+ xo_open_instance("bpf-entry");
+ pname = bpf_pidname(d->bd_pid);
+ xo_emit("{k:pid/%5d} {k:interface-name/%6s} ",
+ d->bd_pid, d->bd_ifname);
+ bpf_flags(d, flagbuf);
+ xo_emit("{d:flags/%7s} {:received-packets/%9ju} "
+ "{:dropped-packets/%9ju} {:filter-packets/%9ju} "
+ "{:store-buffer-length/%5d} {:hold-buffer-length/%5d} "
+ "{:process/%s}\n",
+ flagbuf, (uintmax_t)d->bd_rcount, (uintmax_t)d->bd_dcount,
+ (uintmax_t)d->bd_fcount, d->bd_slen, d->bd_hlen, pname);
+ free(pname);
+ xo_close_instance("bpf-entry");
+ }
+ xo_close_list("bpf-entry");
+ xo_close_container("bpf-statistics");
+ free(bd);
+}
diff --git a/usr.bin/netstat/common.c b/usr.bin/netstat/common.c
new file mode 100644
index 000000000000..00a3f405ed1e
--- /dev/null
+++ b/usr.bin/netstat/common.c
@@ -0,0 +1,136 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1988, 1993
+ * The Regents of the University of California. 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.
+ * 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. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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/protosw.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <libutil.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+#include "common.h"
+
+const char *
+fmt_flags(const struct bits *p, int f)
+{
+ static char name[33];
+ char *flags;
+
+ for (flags = name; p->b_mask; p++)
+ if (p->b_mask & f)
+ *flags++ = p->b_val;
+ *flags = '\0';
+ return (name);
+}
+
+void
+print_flags_generic(int flags, const struct bits *pbits, const char *format,
+ const char *tag_name)
+{
+ const struct bits *p;
+ char tag_fmt[64];
+
+ xo_emit(format, fmt_flags(pbits, flags));
+
+ snprintf(tag_fmt, sizeof(tag_fmt), "{le:%s/%%s}", tag_name);
+ xo_open_list(tag_name);
+ for (p = pbits; p->b_mask; p++)
+ if (p->b_mask & flags)
+ xo_emit(tag_fmt, p->b_name);
+ xo_close_list(tag_name);
+}
+
+struct ifmap_entry *
+prepare_ifmap(size_t *pifmap_size)
+{
+ int ifindex = 0, size;
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_dl *sdl;
+
+ struct ifmap_entry *ifmap = NULL;
+ int ifmap_size = 0;
+
+ /*
+ * Retrieve interface list at first
+ * since we need #ifindex -> if_xname match
+ */
+ if (getifaddrs(&ifap) != 0)
+ xo_err(EX_OSERR, "getifaddrs");
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+
+ sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+ ifindex = sdl->sdl_index;
+
+ if (ifindex >= ifmap_size) {
+ size = roundup2(ifindex + 1, 32) *
+ sizeof(struct ifmap_entry);
+ if ((ifmap = realloc(ifmap, size)) == NULL)
+ xo_errx(EX_OSERR, "realloc(%d) failed", size);
+ memset(&ifmap[ifmap_size], 0,
+ size - ifmap_size *
+ sizeof(struct ifmap_entry));
+
+ ifmap_size = roundup2(ifindex + 1, 32);
+ }
+
+ if (*ifmap[ifindex].ifname != '\0')
+ continue;
+
+ strlcpy(ifmap[ifindex].ifname, ifa->ifa_name, IFNAMSIZ);
+ }
+
+ freeifaddrs(ifap);
+
+ *pifmap_size = ifmap_size;
+
+ return (ifmap);
+}
+
diff --git a/usr.bin/netstat/common.h b/usr.bin/netstat/common.h
new file mode 100644
index 000000000000..d5d39902037b
--- /dev/null
+++ b/usr.bin/netstat/common.h
@@ -0,0 +1,90 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1992, 1993
+ * Regents of the University of California. 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.
+ * 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. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
+ */
+
+#ifndef _NETSTAT_COMMON_H_
+#define _NETSTAT_COMMON_H_
+
+struct bits {
+ u_long b_mask;
+ char b_val;
+ const char *b_name;
+};
+extern struct bits rt_bits[];
+
+const char *fmt_flags(const struct bits *p, int f);
+void print_flags_generic(int flags, const struct bits *pbits,
+ const char *format, const char *tag_name);
+int p_sockaddr(const char *name, struct sockaddr *sa, struct sockaddr *mask,
+ int flags, int width);
+
+struct _wid {
+ int dst;
+ int gw;
+ int flags;
+ int pksent;
+ int mtu;
+ int iface;
+ int expire;
+};
+void set_wid(int fam);
+void pr_rthdr(int af1 __unused);
+extern struct _wid wid;
+void p_flags(int f, const char *format);
+
+bool p_rtable_netlink(int fibnum, int af);
+
+struct ifmap_entry {
+ char ifname[IFNAMSIZ];
+ uint32_t mtu;
+};
+
+struct ifmap_entry *prepare_ifmap(size_t *ifmap_size);
+extern const uint32_t rt_default_weight;
+
+struct rt_msghdr;
+struct nhops_map {
+ uint32_t idx;
+ struct rt_msghdr *rtm;
+};
+
+struct nhops_dump {
+ void *nh_buf;
+ struct nhops_map *nh_map;
+ size_t nh_count;
+};
+
+void dump_nhops_sysctl(int fibnum, int af, struct nhops_dump *nd);
+struct nhop_map;
+void nhop_map_update(struct nhop_map *map, uint32_t idx, char *gw, char *ifname);
+
+
+#endif
+
diff --git a/usr.bin/netstat/if.c b/usr.bin/netstat/if.c
new file mode 100644
index 000000000000..7ee03eb3689b
--- /dev/null
+++ b/usr.bin/netstat/if.c
@@ -0,0 +1,745 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2013 Gleb Smirnoff <glebius@FreeBSD.org>
+ * Copyright (c) 1983, 1988, 1993
+ * The Regents of the University of California. 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.
+ * 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. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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/protosw.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <arpa/inet.h>
+#ifdef PF
+#include <net/pfvar.h>
+#include <net/pflow.h>
+#include <net/if_pfsync.h>
+#endif
+
+#include <errno.h>
+#include <ifaddrs.h>
+#include <libutil.h>
+#ifdef INET6
+#include <netdb.h>
+#endif
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+
+#include "netstat.h"
+
+static void sidewaysintpr(void);
+
+#ifdef PF
+static const char* pfsyncacts[] = {
+ /* PFSYNC_ACT_CLR */ "clear all request",
+ /* PFSYNC_ACT_INS_1301 */ "13.1 state insert",
+ /* PFSYNC_ACT_INS_ACK */ "state inserted ack",
+ /* PFSYNC_ACT_UPD_1301 */ "13.1 state update",
+ /* PFSYNC_ACT_UPD_C */ "compressed state update",
+ /* PFSYNC_ACT_UPD_REQ */ "uncompressed state request",
+ /* PFSYNC_ACT_DEL */ "state delete",
+ /* PFSYNC_ACT_DEL_C */ "compressed state delete",
+ /* PFSYNC_ACT_INS_F */ "fragment insert",
+ /* PFSYNC_ACT_DEL_F */ "fragment delete",
+ /* PFSYNC_ACT_BUS */ "bulk update mark",
+ /* PFSYNC_ACT_TDB */ "TDB replay counter update",
+ /* PFSYNC_ACT_EOF */ "end of frame mark",
+ /* PFSYNC_ACT_INS_1400 */ "14.0 state insert",
+ /* PFSYNC_ACT_UPD_1400 */ "14.0 state update",
+ /* PFSYNC_ACT_INS_1500 */ "state insert",
+ /* PFSYNC_ACT_UPD_1500 */ "state update",
+};
+
+static const char* pfsyncacts_name[] = {
+ /* PFSYNC_ACT_CLR */ "clear-all-request",
+ /* PFSYNC_ACT_INS_1301 */ "state-insert-1301",
+ /* PFSYNC_ACT_INS_ACK */ "state-inserted-ack",
+ /* PFSYNC_ACT_UPD_1301 */ "state-update-1301",
+ /* PFSYNC_ACT_UPD_C */ "compressed-state-update",
+ /* PFSYNC_ACT_UPD_REQ */ "uncompressed-state-request",
+ /* PFSYNC_ACT_DEL */ "state-delete",
+ /* PFSYNC_ACT_DEL_C */ "compressed-state-delete",
+ /* PFSYNC_ACT_INS_F */ "fragment-insert",
+ /* PFSYNC_ACT_DEL_F */ "fragment-delete",
+ /* PFSYNC_ACT_BUS */ "bulk-update-mark",
+ /* PFSYNC_ACT_TDB */ "TDB-replay-counter-update",
+ /* PFSYNC_ACT_EOF */ "end-of-frame-mark",
+ /* PFSYNC_ACT_INS_1400 */ "state-insert-1400",
+ /* PFSYNC_ACT_UPD_1400 */ "state-update-1400",
+ /* PFSYNC_ACT_INS_1500 */ "state-insert",
+ /* PFSYNC_ACT_UPD_1500 */ "state-update",
+};
+
+static void
+pfsync_acts_stats(const char *list, const char *desc, uint64_t *a)
+{
+ int i;
+
+ xo_open_list(list);
+ for (i = 0; i < PFSYNC_ACT_MAX; i++, a++) {
+ if (*a || sflag <= 1) {
+ xo_open_instance(list);
+ xo_emit("\t\t{e:name}{:count/%ju} {N:/%s%s %s}\n",
+ pfsyncacts_name[i], (uintmax_t)(*a),
+ pfsyncacts[i], plural(*a), desc);
+ xo_close_instance(list);
+ }
+ }
+ xo_close_list(list);
+}
+
+/*
+ * Dump pfsync statistics structure.
+ */
+void
+pfsync_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct pfsyncstats pfsyncstat;
+
+ if (fetch_stats("net.pfsync.stats", off, &pfsyncstat,
+ sizeof(pfsyncstat), kread) != 0)
+ return;
+
+ xo_emit("{T:/%s}:\n", name);
+ xo_open_container(name);
+
+#define p(f, m) if (pfsyncstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)pfsyncstat.f, plural(pfsyncstat.f))
+
+ p(pfsyncs_ipackets, "\t{:received-inet-packets/%ju} "
+ "{N:/packet%s received (IPv4)}\n");
+ p(pfsyncs_ipackets6, "\t{:received-inet6-packets/%ju} "
+ "{N:/packet%s received (IPv6)}\n");
+ pfsync_acts_stats("input-histogram", "received",
+ &pfsyncstat.pfsyncs_iacts[0]);
+ p(pfsyncs_badif, "\t\t{:dropped-bad-interface/%ju} "
+ "{N:/packet%s discarded for bad interface}\n");
+ p(pfsyncs_badttl, "\t\t{:dropped-bad-ttl/%ju} "
+ "{N:/packet%s discarded for bad ttl}\n");
+ p(pfsyncs_hdrops, "\t\t{:dropped-short-header/%ju} "
+ "{N:/packet%s shorter than header}\n");
+ p(pfsyncs_badver, "\t\t{:dropped-bad-version/%ju} "
+ "{N:/packet%s discarded for bad version}\n");
+ p(pfsyncs_badauth, "\t\t{:dropped-bad-auth/%ju} "
+ "{N:/packet%s discarded for bad HMAC}\n");
+ p(pfsyncs_badact,"\t\t{:dropped-bad-action/%ju} "
+ "{N:/packet%s discarded for bad action}\n");
+ p(pfsyncs_badlen, "\t\t{:dropped-short/%ju} "
+ "{N:/packet%s discarded for short packet}\n");
+ p(pfsyncs_badval, "\t\t{:dropped-bad-values/%ju} "
+ "{N:/state%s discarded for bad values}\n");
+ p(pfsyncs_stale, "\t\t{:dropped-stale-state/%ju} "
+ "{N:/stale state%s}\n");
+ p(pfsyncs_badstate, "\t\t{:dropped-failed-lookup/%ju} "
+ "{N:/failed state lookup\\/insert%s}\n");
+ p(pfsyncs_opackets, "\t{:sent-inet-packets/%ju} "
+ "{N:/packet%s sent (IPv4})\n");
+ p(pfsyncs_opackets6, "\t{:send-inet6-packets/%ju} "
+ "{N:/packet%s sent (IPv6})\n");
+ pfsync_acts_stats("output-histogram", "sent",
+ &pfsyncstat.pfsyncs_oacts[0]);
+ p(pfsyncs_onomem, "\t\t{:discarded-no-memory/%ju} "
+ "{N:/failure%s due to mbuf memory error}\n");
+ p(pfsyncs_oerrors, "\t\t{:send-errors/%ju} "
+ "{N:/send error%s}\n");
+#undef p
+ xo_close_container(name);
+}
+
+void
+pflow_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct pflowstats pflowstat;
+
+ if (fetch_stats("net.pflow.stats", off, &pflowstat,
+ sizeof(pflowstat), kread) != 0)
+ return;
+
+ xo_emit("{T:/%s}:\n", name);
+ xo_open_container(name);
+
+#define p(f, m) if (pflowstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)pflowstat.f, plural(pflowstat.f))
+
+ p(pflow_flows, "\t{:flows/%ju} {N:/flow%s sent}\n");
+ p(pflow_packets, "\t{:packets/%ju} {N:/packet%s sent}\n");
+ p(pflow_onomem, "\t{:nomem/%ju} "
+ "{N:/send failed due to mbuf memory error}\n");
+ p(pflow_oerrors, "\t{:send-error/%ju} {N:/send error}\n");
+#undef p
+
+ xo_close_container(name);
+}
+#endif /* PF */
+
+/*
+ * Display a formatted value, or a '-' in the same space.
+ */
+static void
+show_stat(const char *fmt, int width, const char *name,
+ u_long value, short showvalue, int div1000)
+{
+ const char *lsep, *rsep;
+ char newfmt[64];
+
+ lsep = "";
+ if (strncmp(fmt, "LS", 2) == 0) {
+ lsep = " ";
+ fmt += 2;
+ }
+ rsep = " ";
+ if (strncmp(fmt, "NRS", 3) == 0) {
+ rsep = "";
+ fmt += 3;
+ }
+ if (showvalue == 0) {
+ /* Print just dash. */
+ xo_emit("{P:/%s}{D:/%*s}{P:/%s}", lsep, width, "-", rsep);
+ return;
+ }
+
+ /*
+ * XXX: workaround {P:} modifier can't be empty and doesn't seem to
+ * take args... so we need to conditionally include it in the format.
+ */
+#define maybe_pad(pad) do { \
+ if (strlen(pad)) { \
+ snprintf(newfmt, sizeof(newfmt), "{P:%s}", pad); \
+ xo_emit(newfmt); \
+ } \
+} while (0)
+
+ if (hflag) {
+ char buf[5];
+
+ /* Format in human readable form. */
+ humanize_number(buf, sizeof(buf), (int64_t)value, "",
+ HN_AUTOSCALE, HN_NOSPACE | HN_DECIMAL | \
+ ((div1000) ? HN_DIVISOR_1000 : 0));
+ maybe_pad(lsep);
+ snprintf(newfmt, sizeof(newfmt), "{:%s/%%%ds}", name, width);
+ xo_emit(newfmt, buf);
+ maybe_pad(rsep);
+ } else {
+ /* Construct the format string. */
+ maybe_pad(lsep);
+ snprintf(newfmt, sizeof(newfmt), "{:%s/%%%d%s}",
+ name, width, fmt);
+ xo_emit(newfmt, value);
+ maybe_pad(rsep);
+ }
+}
+
+/*
+ * Find next multiaddr for a given interface name.
+ */
+static struct ifmaddrs *
+next_ifma(struct ifmaddrs *ifma, const char *name, const sa_family_t family)
+{
+
+ for(; ifma != NULL; ifma = ifma->ifma_next) {
+ struct sockaddr_dl *sdl;
+
+ sdl = (struct sockaddr_dl *)ifma->ifma_name;
+ if (ifma->ifma_addr->sa_family == family &&
+ sdl->sdl_nlen == strlen(name) &&
+ strncmp(sdl->sdl_data, name, sdl->sdl_nlen) == 0)
+ break;
+ }
+
+ return (ifma);
+}
+
+enum process_op { MEASURE, EMIT };
+
+static void
+process_ifa_addr(enum process_op op, struct ifaddrs *ifa, int *max_net_len,
+ int *max_addr_len, bool *network, bool *link)
+{
+ int net_len, addr_len;
+ const char *nn, *rn;
+
+ if (op == EMIT) {
+ net_len = *max_net_len;
+ addr_len = *max_addr_len;
+ }
+
+ switch (ifa->ifa_addr->sa_family) {
+ case AF_UNSPEC:
+ if (op == MEASURE) {
+ net_len = strlen("none");
+ addr_len = strlen("none");
+ } else {
+ xo_emit("{:network/%-*.*s} ", net_len, net_len,
+ "none");
+ xo_emit("{:address/%-*.*s} ", addr_len, addr_len,
+ "none");
+ }
+ break;
+ case AF_INET:
+#ifdef INET6
+ case AF_INET6:
+#endif /* INET6 */
+ nn = netname(ifa->ifa_addr, ifa->ifa_netmask);
+ rn = routename(ifa->ifa_addr, numeric_addr);
+ if (op == MEASURE) {
+ net_len = strlen(nn);
+ addr_len = strlen(rn);
+ } else {
+ xo_emit("{t:network/%-*s} ", net_len, nn);
+ xo_emit("{t:address/%-*s} ", addr_len, rn);
+ }
+
+ if (network != NULL)
+ *network = true;
+ break;
+ case AF_LINK:
+ {
+ struct sockaddr_dl *sdl;
+ char linknum[sizeof("<Link#32767>")];
+
+ sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+ snprintf(linknum, sizeof(linknum), "<Link#%d>", sdl->sdl_index);
+ if (op == MEASURE) {
+ net_len = strlen(linknum);
+ if (sdl->sdl_nlen == 0 &&
+ sdl->sdl_alen == 0 &&
+ sdl->sdl_slen == 0)
+ addr_len = 1;
+ else
+ addr_len = strlen(routename(ifa->ifa_addr, 1));
+ } else {
+ xo_emit("{t:network/%-*.*s} ", net_len, net_len,
+ linknum);
+ if (sdl->sdl_nlen == 0 &&
+ sdl->sdl_alen == 0 &&
+ sdl->sdl_slen == 0)
+ xo_emit("{P:/%*s} ", addr_len, "");
+ else
+ xo_emit("{t:address/%-*.*s} ", addr_len,
+ addr_len, routename(ifa->ifa_addr, 1));
+ }
+ if (link != NULL)
+ *link = true;
+ break;
+ }
+ }
+
+ if (op == MEASURE) {
+ if (net_len > *max_net_len)
+ *max_net_len = net_len;
+ if (addr_len > *max_addr_len)
+ *max_addr_len = addr_len;
+ }
+}
+
+static int
+max_num_len(int max_len, u_long num)
+{
+ int len = 2; /* include space */
+
+ for (; num > 10; len++)
+ num /= 10;
+ return (MAX(max_len, len));
+}
+
+/*
+ * Print a description of the network interfaces.
+ */
+void
+intpr(void (*pfunc)(char *), int af)
+{
+ struct ifaddrs *ifap, *ifa;
+ struct ifmaddrs *ifmap, *ifma;
+ u_int ifn_len_max = 5, ifn_len;
+ u_int net_len = strlen("Network "), addr_len = strlen("Address ");
+ u_int npkt_len = 8, nbyte_len = 10, nerr_len = 5;
+
+ if (interval)
+ return sidewaysintpr();
+
+ if (getifaddrs(&ifap) != 0)
+ xo_err(EX_OSERR, "getifaddrs");
+ if (aflag && getifmaddrs(&ifmap) != 0)
+ xo_err(EX_OSERR, "getifmaddrs");
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (interface != NULL &&
+ strcmp(ifa->ifa_name, interface) != 0)
+ continue;
+ if (af != AF_UNSPEC && ifa->ifa_addr->sa_family != af)
+ continue;
+ ifn_len = strlen(ifa->ifa_name);
+ if ((ifa->ifa_flags & IFF_UP) == 0)
+ ++ifn_len;
+ ifn_len_max = MAX(ifn_len_max, ifn_len);
+ process_ifa_addr(MEASURE, ifa, &net_len, &addr_len,
+ NULL, NULL);
+
+#define IFA_STAT(s) (((struct if_data *)ifa->ifa_data)->ifi_ ## s)
+ if (!hflag) {
+ npkt_len = max_num_len(npkt_len, IFA_STAT(ipackets));
+ npkt_len = max_num_len(npkt_len, IFA_STAT(opackets));
+ nerr_len = max_num_len(nerr_len, IFA_STAT(ierrors));
+ nerr_len = max_num_len(nerr_len, IFA_STAT(iqdrops));
+ nerr_len = max_num_len(nerr_len, IFA_STAT(collisions));
+ if (dflag)
+ nerr_len = max_num_len(nerr_len,
+ IFA_STAT(oqdrops));
+ if (bflag) {
+ nbyte_len = max_num_len(nbyte_len,
+ IFA_STAT(ibytes));
+ nbyte_len = max_num_len(nbyte_len,
+ IFA_STAT(obytes));
+ }
+ }
+ }
+
+ xo_open_list("interface");
+ if (!pfunc) {
+ xo_emit("{T:/%-*.*s}", ifn_len_max, ifn_len_max, "Name");
+ xo_emit(" {T:/%5.5s} {T:/%-*.*s} {T:/%-*.*s} {T:/%*.*s} "
+ "{T:/%*.*s} {T:/%*.*s}",
+ "Mtu", net_len, net_len, "Network", addr_len, addr_len,
+ "Address", npkt_len, npkt_len, "Ipkts",
+ nerr_len, nerr_len, "Ierrs", nerr_len, nerr_len, "Idrop");
+ if (bflag)
+ xo_emit(" {T:/%*.*s}", nbyte_len, nbyte_len, "Ibytes");
+ xo_emit(" {T:/%*.*s} {T:/%*.*s}", npkt_len, npkt_len, "Opkts",
+ nerr_len, nerr_len, "Oerrs");
+ if (bflag)
+ xo_emit(" {T:/%*.*s}", nbyte_len, nbyte_len, "Obytes");
+ xo_emit(" {T:/%*s}", nerr_len, "Coll");
+ if (dflag)
+ xo_emit(" {T:/%*.*s}", nerr_len, nerr_len, "Drop");
+ xo_emit("\n");
+ }
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ bool network = false, link = false;
+ char *name, *xname, buf[IFNAMSIZ+1];
+
+ if (interface != NULL && strcmp(ifa->ifa_name, interface) != 0)
+ continue;
+
+ name = ifa->ifa_name;
+
+ if (pfunc) {
+
+ (*pfunc)(name);
+
+ /*
+ * Skip all ifaddrs belonging to same interface.
+ */
+ while(ifa->ifa_next != NULL &&
+ (strcmp(ifa->ifa_next->ifa_name, name) == 0)) {
+ ifa = ifa->ifa_next;
+ }
+ continue;
+ }
+
+ if (af != AF_UNSPEC && ifa->ifa_addr->sa_family != af)
+ continue;
+
+ xo_open_instance("interface");
+
+ if ((ifa->ifa_flags & IFF_UP) == 0) {
+ xname = stpcpy(buf, name);
+ *xname++ = '*';
+ *xname = '\0';
+ xname = buf;
+ } else
+ xname = name;
+
+ xo_emit("{d:/%-*.*s}{etk:name}{eq:flags/0x%x}",
+ ifn_len_max, ifn_len_max, xname, name, ifa->ifa_flags);
+
+#define IFA_MTU(ifa) (((struct if_data *)(ifa)->ifa_data)->ifi_mtu)
+ show_stat("lu", 6, "mtu", IFA_MTU(ifa), IFA_MTU(ifa), 0);
+#undef IFA_MTU
+
+ process_ifa_addr(EMIT, ifa, &net_len, &addr_len,
+ &network, &link);
+
+ show_stat("lu", npkt_len, "received-packets",
+ IFA_STAT(ipackets), link|network, 1);
+ show_stat("lu", nerr_len, "received-errors", IFA_STAT(ierrors),
+ link, 1);
+ show_stat("lu", nerr_len, "dropped-packets", IFA_STAT(iqdrops),
+ link, 1);
+ if (bflag)
+ show_stat("lu", nbyte_len, "received-bytes",
+ IFA_STAT(ibytes), link|network, 0);
+ show_stat("lu", npkt_len, "sent-packets", IFA_STAT(opackets),
+ link|network, 1);
+ show_stat("lu", nerr_len, "send-errors", IFA_STAT(oerrors),
+ link, 1);
+ if (bflag)
+ show_stat("lu", nbyte_len, "sent-bytes",
+ IFA_STAT(obytes), link|network, 0);
+ show_stat("NRSlu", nerr_len, "collisions", IFA_STAT(collisions),
+ link, 1);
+ if (dflag)
+ show_stat("LSlu", nerr_len, "dropped-packets",
+ IFA_STAT(oqdrops), link, 1);
+ xo_emit("\n");
+
+ if (!aflag) {
+ xo_close_instance("interface");
+ continue;
+ }
+
+ /*
+ * Print family's multicast addresses.
+ */
+ xo_open_list("multicast-address");
+ for (ifma = next_ifma(ifmap, ifa->ifa_name,
+ ifa->ifa_addr->sa_family);
+ ifma != NULL;
+ ifma = next_ifma(ifma, ifa->ifa_name,
+ ifa->ifa_addr->sa_family)) {
+ const char *fmt = NULL;
+
+ xo_open_instance("multicast-address");
+ switch (ifma->ifma_addr->sa_family) {
+ case AF_LINK:
+ {
+ struct sockaddr_dl *sdl;
+
+ sdl = (struct sockaddr_dl *)ifma->ifma_addr;
+ if (sdl->sdl_type != IFT_ETHER &&
+ sdl->sdl_type != IFT_FDDI)
+ break;
+ }
+ /* FALLTHROUGH */
+ case AF_INET:
+#ifdef INET6
+ case AF_INET6:
+#endif /* INET6 */
+ fmt = routename(ifma->ifma_addr, numeric_addr);
+ break;
+ }
+ if (fmt) {
+ if (Wflag)
+ xo_emit("{P:/%27s }"
+ "{t:address/%-17s/}", "", fmt);
+ else
+ xo_emit("{P:/%25s }"
+ "{t:address/%-17.17s/}", "", fmt);
+ if (ifma->ifma_addr->sa_family == AF_LINK) {
+ xo_emit(" {:received-packets/%8lu}",
+ IFA_STAT(imcasts));
+ xo_emit("{P:/%*s}", bflag? 17 : 6, "");
+ xo_emit(" {:sent-packets/%8lu}",
+ IFA_STAT(omcasts));
+ }
+ xo_emit("\n");
+ }
+ xo_close_instance("multicast-address");
+ ifma = ifma->ifma_next;
+ }
+ xo_close_list("multicast-address");
+ xo_close_instance("interface");
+ }
+ xo_close_list("interface");
+
+ freeifaddrs(ifap);
+ if (aflag)
+ freeifmaddrs(ifmap);
+}
+
+struct iftot {
+ u_long ift_ip; /* input packets */
+ u_long ift_ie; /* input errors */
+ u_long ift_id; /* input drops */
+ u_long ift_op; /* output packets */
+ u_long ift_oe; /* output errors */
+ u_long ift_od; /* output drops */
+ u_long ift_co; /* collisions */
+ u_long ift_ib; /* input bytes */
+ u_long ift_ob; /* output bytes */
+};
+
+/*
+ * Obtain stats for interface(s).
+ */
+static void
+fill_iftot(struct iftot *st)
+{
+ struct ifaddrs *ifap, *ifa;
+ bool found = false;
+
+ if (getifaddrs(&ifap) != 0)
+ xo_err(EX_OSERR, "getifaddrs");
+
+ bzero(st, sizeof(*st));
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+ if (interface) {
+ if (strcmp(ifa->ifa_name, interface) == 0)
+ found = true;
+ else
+ continue;
+ }
+
+ st->ift_ip += IFA_STAT(ipackets);
+ st->ift_ie += IFA_STAT(ierrors);
+ st->ift_id += IFA_STAT(iqdrops);
+ st->ift_ib += IFA_STAT(ibytes);
+ st->ift_op += IFA_STAT(opackets);
+ st->ift_oe += IFA_STAT(oerrors);
+ st->ift_od += IFA_STAT(oqdrops);
+ st->ift_ob += IFA_STAT(obytes);
+ st->ift_co += IFA_STAT(collisions);
+ }
+
+ if (interface && found == false)
+ xo_err(EX_DATAERR, "interface %s not found", interface);
+
+ freeifaddrs(ifap);
+}
+
+/*
+ * Set a flag to indicate that a signal from the periodic itimer has been
+ * caught.
+ */
+static sig_atomic_t signalled;
+static void
+catchalarm(int signo __unused)
+{
+ signalled = true;
+}
+
+/*
+ * Print a running summary of interface statistics.
+ * Repeat display every interval seconds, showing statistics
+ * collected over that interval. Assumes that interval is non-zero.
+ * First line printed at top of screen is always cumulative.
+ */
+static void
+sidewaysintpr(void)
+{
+ struct iftot ift[2], *new, *old;
+ struct itimerval interval_it;
+ int oldmask, line;
+
+ new = &ift[0];
+ old = &ift[1];
+ fill_iftot(old);
+
+ (void)signal(SIGALRM, catchalarm);
+ signalled = false;
+ interval_it.it_interval.tv_sec = interval;
+ interval_it.it_interval.tv_usec = 0;
+ interval_it.it_value = interval_it.it_interval;
+ setitimer(ITIMER_REAL, &interval_it, NULL);
+ xo_open_list("interface-statistics");
+
+banner:
+ xo_emit("{T:/%17s} {T:/%14s} {T:/%16s}\n", "input",
+ interface != NULL ? interface : "(Total)", "output");
+ xo_emit("{T:/%10s} {T:/%5s} {T:/%5s} {T:/%10s} {T:/%10s} {T:/%5s} "
+ "{T:/%10s} {T:/%5s}",
+ "packets", "errs", "idrops", "bytes", "packets", "errs", "bytes",
+ "colls");
+ if (dflag)
+ xo_emit(" {T:/%5.5s}", "drops");
+ xo_emit("\n");
+ xo_flush();
+ line = 0;
+
+loop:
+ if ((noutputs != 0) && (--noutputs == 0)) {
+ xo_close_list("interface-statistics");
+ return;
+ }
+ oldmask = sigblock(sigmask(SIGALRM));
+ while (!signalled)
+ sigpause(0);
+ signalled = false;
+ sigsetmask(oldmask);
+ line++;
+
+ fill_iftot(new);
+
+ xo_open_instance("stats");
+ show_stat("lu", 10, "received-packets",
+ new->ift_ip - old->ift_ip, 1, 1);
+ show_stat("lu", 5, "received-errors",
+ new->ift_ie - old->ift_ie, 1, 1);
+ show_stat("lu", 5, "dropped-packets",
+ new->ift_id - old->ift_id, 1, 1);
+ show_stat("lu", 10, "received-bytes",
+ new->ift_ib - old->ift_ib, 1, 0);
+ show_stat("lu", 10, "sent-packets",
+ new->ift_op - old->ift_op, 1, 1);
+ show_stat("lu", 5, "send-errors",
+ new->ift_oe - old->ift_oe, 1, 1);
+ show_stat("lu", 10, "sent-bytes",
+ new->ift_ob - old->ift_ob, 1, 0);
+ show_stat("NRSlu", 5, "collisions",
+ new->ift_co - old->ift_co, 1, 1);
+ if (dflag)
+ show_stat("LSlu", 5, "dropped-packets",
+ new->ift_od - old->ift_od, 1, 1);
+ xo_close_instance("stats");
+ xo_emit("\n");
+ xo_flush();
+
+ if (new == &ift[0]) {
+ new = &ift[1];
+ old = &ift[0];
+ } else {
+ new = &ift[0];
+ old = &ift[1];
+ }
+
+ if (line == 21)
+ goto banner;
+ else
+ goto loop;
+
+ /* NOTREACHED */
+}
diff --git a/usr.bin/netstat/inet.c b/usr.bin/netstat/inet.c
new file mode 100644
index 000000000000..5f36b1599cad
--- /dev/null
+++ b/usr.bin/netstat/inet.c
@@ -0,0 +1,1549 @@
+/*-
+ * Copyright (c) 1983, 1988, 1993, 1995
+ * The Regents of the University of California. 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.
+ * 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. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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/queue.h>
+#include <sys/domain.h>
+#define _WANT_PROTOSW
+#include <sys/protosw.h>
+#include <sys/socket.h>
+#define _WANT_SOCKET
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+
+#include <net/route.h>
+#include <net/if_arp.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_carp.h>
+#ifdef INET6
+#include <netinet/ip6.h>
+#endif /* INET6 */
+#include <netinet/in_pcb.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp_var.h>
+#include <netinet/igmp_var.h>
+#include <netinet/ip_divert.h>
+#include <netinet/ip_var.h>
+#include <netinet/pim_var.h>
+#include <netinet/tcp.h>
+#include <netinet/tcpip.h>
+#include <netinet/tcp_seq.h>
+#define TCPSTATES
+#include <netinet/tcp_fsm.h>
+#include <netinet/tcp_var.h>
+#include <netinet/udp.h>
+#include <netinet/udp_var.h>
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <libutil.h>
+#include <netdb.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+#include "nl_defs.h"
+
+#define max(a, b) (((a) > (b)) ? (a) : (b))
+
+#ifdef INET
+static void inetprint(const char *, struct in_addr *, int, const char *, int,
+ const int);
+#endif
+#ifdef INET6
+static int udp_done, tcp_done, sdp_done;
+#endif /* INET6 */
+
+static int
+pcblist_sysctl(int proto, const char *name, char **bufp)
+{
+ const char *mibvar;
+ char *buf;
+ size_t len;
+
+ switch (proto) {
+ case IPPROTO_TCP:
+ mibvar = "net.inet.tcp.pcblist";
+ break;
+ case IPPROTO_UDP:
+ mibvar = "net.inet.udp.pcblist";
+ break;
+ default:
+ mibvar = "net.inet.raw.pcblist";
+ break;
+ }
+ if (strncmp(name, "sdp", 3) == 0)
+ mibvar = "net.inet.sdp.pcblist";
+ else if (strncmp(name, "divert", 6) == 0)
+ mibvar = "net.inet.divert.pcblist";
+ len = 0;
+ if (sysctlbyname(mibvar, 0, &len, 0, 0) < 0) {
+ if (errno != ENOENT)
+ xo_warn("sysctl: %s", mibvar);
+ return (0);
+ }
+ if ((buf = malloc(len)) == NULL) {
+ xo_warnx("malloc %lu bytes", (u_long)len);
+ return (0);
+ }
+ if (sysctlbyname(mibvar, buf, &len, 0, 0) < 0) {
+ xo_warn("sysctl: %s", mibvar);
+ free(buf);
+ return (0);
+ }
+ *bufp = buf;
+ return (1);
+}
+
+/*
+ * Copied directly from uipc_socket2.c. We leave out some fields that are in
+ * nested structures that aren't used to avoid extra work.
+ */
+static void
+sbtoxsockbuf(struct sockbuf *sb, struct xsockbuf *xsb)
+{
+ xsb->sb_cc = sb->sb_ccc;
+ xsb->sb_hiwat = sb->sb_hiwat;
+ xsb->sb_mbcnt = sb->sb_mbcnt;
+ xsb->sb_mbmax = sb->sb_mbmax;
+ xsb->sb_lowat = sb->sb_lowat;
+ xsb->sb_flags = sb->sb_flags;
+ xsb->sb_timeo = sb->sb_timeo;
+}
+
+int
+sotoxsocket(struct socket *so, struct xsocket *xso)
+{
+ struct protosw proto;
+ struct domain domain;
+
+ bzero(xso, sizeof *xso);
+ xso->xso_len = sizeof *xso;
+ xso->xso_so = (uintptr_t)so;
+ xso->so_type = so->so_type;
+ xso->so_options = so->so_options;
+ xso->so_linger = so->so_linger;
+ xso->so_state = so->so_state;
+ xso->so_pcb = (uintptr_t)so->so_pcb;
+ if (kread((uintptr_t)so->so_proto, &proto, sizeof(proto)) != 0)
+ return (-1);
+ xso->xso_protocol = proto.pr_protocol;
+ if (kread((uintptr_t)proto.pr_domain, &domain, sizeof(domain)) != 0)
+ return (-1);
+ xso->xso_family = domain.dom_family;
+ xso->so_timeo = so->so_timeo;
+ xso->so_error = so->so_error;
+ if ((so->so_options & SO_ACCEPTCONN) != 0) {
+ xso->so_qlen = so->sol_qlen;
+ xso->so_incqlen = so->sol_incqlen;
+ xso->so_qlimit = so->sol_qlimit;
+ } else {
+ sbtoxsockbuf(&so->so_snd, &xso->so_snd);
+ sbtoxsockbuf(&so->so_rcv, &xso->so_rcv);
+ xso->so_oobmark = so->so_oobmark;
+ }
+ return (0);
+}
+
+/*
+ * Print a summary of connections related to an Internet
+ * protocol. For TCP, also give state of connection.
+ * Listening processes (aflag) are suppressed unless the
+ * -a (all) flag is specified.
+ */
+void
+protopr(u_long off, const char *name, int af1, int proto)
+{
+ static int first = 1;
+ int istcp;
+ char *buf;
+ const char *vchar;
+ struct xtcpcb *tp;
+ struct xinpcb *inp;
+ struct xinpgen *xig, *oxig;
+ struct xsocket *so;
+ int fnamelen, cnamelen;
+
+ istcp = 0;
+ switch (proto) {
+ case IPPROTO_TCP:
+#ifdef INET6
+ if (strncmp(name, "sdp", 3) != 0) {
+ if (tcp_done != 0)
+ return;
+ else
+ tcp_done = 1;
+ } else {
+ if (sdp_done != 0)
+ return;
+ else
+ sdp_done = 1;
+ }
+#endif
+ istcp = 1;
+ break;
+ case IPPROTO_UDP:
+#ifdef INET6
+ if (udp_done != 0)
+ return;
+ else
+ udp_done = 1;
+#endif
+ break;
+ }
+
+ if (!pcblist_sysctl(proto, name, &buf))
+ return;
+
+ if (istcp && (cflag || Cflag)) {
+ fnamelen = strlen("Stack");
+ cnamelen = strlen("CC");
+ oxig = xig = (struct xinpgen *)buf;
+ for (xig = (struct xinpgen*)((char *)xig + xig->xig_len);
+ xig->xig_len > sizeof(struct xinpgen);
+ xig = (struct xinpgen *)((char *)xig + xig->xig_len)) {
+ tp = (struct xtcpcb *)xig;
+ inp = &tp->xt_inp;
+ if (inp->inp_gencnt > oxig->xig_gen)
+ continue;
+ so = &inp->xi_socket;
+ if (so->xso_protocol != proto)
+ continue;
+ fnamelen = max(fnamelen, (int)strlen(tp->xt_stack));
+ cnamelen = max(cnamelen, (int)strlen(tp->xt_cc));
+ }
+ }
+
+ oxig = xig = (struct xinpgen *)buf;
+ for (xig = (struct xinpgen *)((char *)xig + xig->xig_len);
+ xig->xig_len > sizeof(struct xinpgen);
+ xig = (struct xinpgen *)((char *)xig + xig->xig_len)) {
+ if (istcp) {
+ tp = (struct xtcpcb *)xig;
+ inp = &tp->xt_inp;
+ } else {
+ inp = (struct xinpcb *)xig;
+ }
+ so = &inp->xi_socket;
+
+ /* Ignore sockets for protocols other than the desired one. */
+ if (proto != 0 && so->xso_protocol != proto)
+ continue;
+
+ /* Ignore PCBs which were freed during copyout. */
+ if (inp->inp_gencnt > oxig->xig_gen)
+ continue;
+
+ if ((af1 == AF_INET && (inp->inp_vflag & INP_IPV4) == 0)
+#ifdef INET6
+ || (af1 == AF_INET6 && (inp->inp_vflag & INP_IPV6) == 0)
+#endif /* INET6 */
+ || (af1 == AF_UNSPEC && ((inp->inp_vflag & INP_IPV4) == 0
+#ifdef INET6
+ && (inp->inp_vflag & INP_IPV6) == 0
+#endif /* INET6 */
+ ))
+ )
+ continue;
+ if (!aflag &&
+ (
+ (istcp && tp->t_state == TCPS_LISTEN)
+ || (af1 == AF_INET &&
+ inp->inp_laddr.s_addr == INADDR_ANY)
+#ifdef INET6
+ || (af1 == AF_INET6 &&
+ IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr))
+#endif /* INET6 */
+ || (af1 == AF_UNSPEC &&
+ (((inp->inp_vflag & INP_IPV4) != 0 &&
+ inp->inp_laddr.s_addr == INADDR_ANY)
+#ifdef INET6
+ || ((inp->inp_vflag & INP_IPV6) != 0 &&
+ IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr))
+#endif
+ ))
+ ))
+ continue;
+
+ if (first) {
+ if (!Lflag) {
+ xo_emit("Active Internet connections");
+ if (aflag)
+ xo_emit(" (including servers)");
+ } else
+ xo_emit(
+ "Current listen queue sizes (qlen/incqlen/maxqlen)");
+ xo_emit("\n");
+ if (Aflag)
+ xo_emit("{T:/%-*s} ", 2 * (int)sizeof(void *),
+ "Tcpcb");
+ if (Lflag)
+ xo_emit((Aflag && !Wflag) ?
+ "{T:/%-5.5s} {T:/%-32.32s} {T:/%-18.18s}" :
+ ((!Wflag || af1 == AF_INET) ?
+ "{T:/%-5.5s} {T:/%-32.32s} {T:/%-22.22s}" :
+ "{T:/%-5.5s} {T:/%-32.32s} {T:/%-45.45s}"),
+ "Proto", "Listen", "Local Address");
+ else if (Tflag)
+ xo_emit((Aflag && !Wflag) ?
+ "{T:/%-5.5s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-18.18s} {T:/%s}" :
+ ((!Wflag || af1 == AF_INET) ?
+ "{T:/%-5.5s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-22.22s} {T:/%s}" :
+ "{T:/%-5.5s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-45.45s} {T:/%s}"),
+ "Proto", "Rexmit", "OOORcv", "0-win",
+ "Local Address", "Foreign Address");
+ else {
+ xo_emit((Aflag && !Wflag) ?
+ "{T:/%-5.5s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-18.18s} {T:/%-18.18s}" :
+ ((!Wflag || af1 == AF_INET) ?
+ "{T:/%-5.5s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-22.22s} {T:/%-22.22s}" :
+ "{T:/%-5.5s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-45.45s} {T:/%-45.45s}"),
+ "Proto", "Recv-Q", "Send-Q",
+ "Local Address", "Foreign Address");
+ if (!xflag && !Rflag)
+ xo_emit(" {T:/%-11.11s}", "(state)");
+ }
+ if (xflag) {
+ xo_emit(" {T:/%-6.6s} {T:/%-6.6s} "
+ "{T:/%-6.6s} {T:/%-6.6s} {T:/%-6.6s} "
+ "{T:/%-6.6s} {T:/%-6.6s} {T:/%-6.6s}",
+ "R-HIWA", "S-HIWA", "R-LOWA", "S-LOWA",
+ "R-BCNT", "S-BCNT", "R-BMAX", "S-BMAX");
+ xo_emit(" {T:/%7.7s} {T:/%7.7s} {T:/%7.7s} "
+ "{T:/%7.7s} {T:/%7.7s} {T:/%7.7s}",
+ "rexmt", "persist", "keep", "2msl",
+ "delack", "rcvtime");
+ } else if (Rflag) {
+ xo_emit(" {T:/%8.8s} {T:/%5.5s}",
+ "flowid", "ftype");
+ }
+ if (cflag) {
+ xo_emit(" {T:/%-*.*s}",
+ fnamelen, fnamelen, "Stack");
+ }
+ if (Cflag)
+ xo_emit(" {T:/%-*.*s} {T:/%10.10s}"
+ " {T:/%10.10s} {T:/%5.5s}"
+ " {T:/%3.3s}", cnamelen,
+ cnamelen, "CC",
+ "cwin",
+ "ssthresh",
+ "MSS",
+ "ECN");
+ if (Pflag)
+ xo_emit(" {T:/%s}", "Log ID");
+ xo_emit("\n");
+ first = 0;
+ }
+ if (Lflag && so->so_qlimit == 0)
+ continue;
+ xo_open_instance("socket");
+ if (Aflag)
+ xo_emit("{q:address/%*lx} ", 2 * (int)sizeof(void *),
+ (u_long)so->so_pcb);
+#ifdef INET6
+ if ((inp->inp_vflag & INP_IPV6) != 0)
+ vchar = ((inp->inp_vflag & INP_IPV4) != 0) ?
+ "46" : "6";
+ else
+#endif
+ vchar = ((inp->inp_vflag & INP_IPV4) != 0) ?
+ "4" : "";
+ if (istcp && (tp->t_flags & TF_TOE) != 0)
+ xo_emit("{:protocol/%-3.3s%-2.2s/%s%s} ", "toe", vchar);
+ else
+ xo_emit("{:protocol/%-3.3s%-2.2s/%s%s} ", name, vchar);
+ if (Lflag) {
+ char buf1[33];
+
+ snprintf(buf1, sizeof buf1, "%u/%u/%u", so->so_qlen,
+ so->so_incqlen, so->so_qlimit);
+ xo_emit("{:listen-queue-sizes/%-32.32s} ", buf1);
+ } else if (Tflag) {
+ if (istcp)
+ xo_emit("{:sent-retransmit-packets/%6u} "
+ "{:received-out-of-order-packets/%6u} "
+ "{:sent-zero-window/%6u} ",
+ tp->t_sndrexmitpack, tp->t_rcvoopack,
+ tp->t_sndzerowin);
+ else
+ xo_emit("{P:/%21s}", "");
+ } else {
+ xo_emit("{:receive-bytes-waiting/%6u} "
+ "{:send-bytes-waiting/%6u} ",
+ so->so_rcv.sb_cc, so->so_snd.sb_cc);
+ }
+ if (numeric_port) {
+#ifdef INET
+ if (inp->inp_vflag & INP_IPV4) {
+ inetprint("local", &inp->inp_laddr,
+ (int)inp->inp_lport, name, 1, af1);
+ if (!Lflag)
+ inetprint("remote", &inp->inp_faddr,
+ (int)inp->inp_fport, name, 1, af1);
+ }
+#endif
+#if defined(INET) && defined(INET6)
+ else
+#endif
+#ifdef INET6
+ if (inp->inp_vflag & INP_IPV6) {
+ inet6print("local", &inp->in6p_laddr,
+ (int)inp->inp_lport, name, 1);
+ if (!Lflag)
+ inet6print("remote", &inp->in6p_faddr,
+ (int)inp->inp_fport, name, 1);
+ } /* else nothing printed now */
+#endif /* INET6 */
+ } else if (inp->inp_flags & INP_ANONPORT) {
+#ifdef INET
+ if (inp->inp_vflag & INP_IPV4) {
+ inetprint("local", &inp->inp_laddr,
+ (int)inp->inp_lport, name, 1, af1);
+ if (!Lflag)
+ inetprint("remote", &inp->inp_faddr,
+ (int)inp->inp_fport, name, 0, af1);
+ }
+#endif
+#if defined(INET) && defined(INET6)
+ else
+#endif
+#ifdef INET6
+ if (inp->inp_vflag & INP_IPV6) {
+ inet6print("local", &inp->in6p_laddr,
+ (int)inp->inp_lport, name, 1);
+ if (!Lflag)
+ inet6print("remote", &inp->in6p_faddr,
+ (int)inp->inp_fport, name, 0);
+ } /* else nothing printed now */
+#endif /* INET6 */
+ } else {
+#ifdef INET
+ if (inp->inp_vflag & INP_IPV4) {
+ inetprint("local", &inp->inp_laddr,
+ (int)inp->inp_lport, name, 0, af1);
+ if (!Lflag)
+ inetprint("remote", &inp->inp_faddr,
+ (int)inp->inp_fport, name,
+ inp->inp_lport != inp->inp_fport,
+ af1);
+ }
+#endif
+#if defined(INET) && defined(INET6)
+ else
+#endif
+#ifdef INET6
+ if (inp->inp_vflag & INP_IPV6) {
+ inet6print("local", &inp->in6p_laddr,
+ (int)inp->inp_lport, name, 0);
+ if (!Lflag)
+ inet6print("remote", &inp->in6p_faddr,
+ (int)inp->inp_fport, name,
+ inp->inp_lport != inp->inp_fport);
+ } /* else nothing printed now */
+#endif /* INET6 */
+ }
+ if (xflag) {
+ xo_emit("{:receive-high-water/%6u} "
+ "{:send-high-water/%6u} "
+ "{:receive-low-water/%6u} {:send-low-water/%6u} "
+ "{:receive-mbuf-bytes/%6u} {:send-mbuf-bytes/%6u} "
+ "{:receive-mbuf-bytes-max/%6u} "
+ "{:send-mbuf-bytes-max/%6u}",
+ so->so_rcv.sb_hiwat, so->so_snd.sb_hiwat,
+ so->so_rcv.sb_lowat, so->so_snd.sb_lowat,
+ so->so_rcv.sb_mbcnt, so->so_snd.sb_mbcnt,
+ so->so_rcv.sb_mbmax, so->so_snd.sb_mbmax);
+ if (istcp)
+ xo_emit(" {:retransmit-timer/%4d.%02d} "
+ "{:persist-timer/%4d.%02d} "
+ "{:keepalive-timer/%4d.%02d} "
+ "{:msl2-timer/%4d.%02d} "
+ "{:delay-ack-timer/%4d.%02d} "
+ "{:inactivity-timer/%4d.%02d}",
+ tp->tt_rexmt / 1000,
+ (tp->tt_rexmt % 1000) / 10,
+ tp->tt_persist / 1000,
+ (tp->tt_persist % 1000) / 10,
+ tp->tt_keep / 1000,
+ (tp->tt_keep % 1000) / 10,
+ tp->tt_2msl / 1000,
+ (tp->tt_2msl % 1000) / 10,
+ tp->tt_delack / 1000,
+ (tp->tt_delack % 1000) / 10,
+ tp->t_rcvtime / 1000,
+ (tp->t_rcvtime % 1000) / 10);
+ }
+ if (istcp && !Lflag && !xflag && !Tflag && !Rflag) {
+ if (tp->t_state < 0 || tp->t_state >= TCP_NSTATES)
+ xo_emit("{:tcp-state/%-11d}", tp->t_state);
+ else {
+ xo_emit("{:tcp-state/%-11s}",
+ tcpstates[tp->t_state]);
+#if defined(TF_NEEDSYN) && defined(TF_NEEDFIN)
+ /* Show T/TCP `hidden state' */
+ if (tp->t_flags & (TF_NEEDSYN|TF_NEEDFIN))
+ xo_emit("{:need-syn-or-fin/*}");
+#endif /* defined(TF_NEEDSYN) && defined(TF_NEEDFIN) */
+ }
+ }
+ if (Rflag) {
+ /* XXX: is this right Alfred */
+ xo_emit(" {:flow-id/%08x} {:flow-type/%5d}",
+ inp->inp_flowid,
+ inp->inp_flowtype);
+ }
+ if (istcp) {
+ if (cflag)
+ xo_emit(" {:stack/%-*.*s}",
+
+ fnamelen, fnamelen, tp->xt_stack);
+ if (Cflag)
+ xo_emit(" {:cc/%-*.*s}"
+ " {:snd-cwnd/%10lu}"
+ " {:snd-ssthresh/%10lu}"
+ " {:t-maxseg/%5u} {:ecn/%3s}",
+ cnamelen, cnamelen, tp->xt_cc,
+ tp->t_snd_cwnd, tp->t_snd_ssthresh,
+ tp->t_maxseg,
+ (tp->t_state >= TCPS_ESTABLISHED ?
+ (tp->xt_ecn > 0 ?
+ (tp->xt_ecn == 1 ?
+ "ecn" : "ace")
+ : "off")
+ : "n/a"));
+ if (Pflag)
+ xo_emit(" {:log-id/%s}",
+ tp->xt_logid[0] == '\0' ?
+ "-" : tp->xt_logid);
+ }
+ xo_emit("\n");
+ xo_close_instance("socket");
+ }
+ if (xig != oxig && xig->xig_gen != oxig->xig_gen) {
+ if (oxig->xig_count > xig->xig_count) {
+ xo_emit("Some {d:lost/%s} sockets may have been "
+ "deleted.\n", name);
+ } else if (oxig->xig_count < xig->xig_count) {
+ xo_emit("Some {d:created/%s} sockets may have been "
+ "created.\n", name);
+ } else {
+ xo_emit("Some {d:changed/%s} sockets may have been "
+ "created or deleted.\n", name);
+ }
+ }
+ free(buf);
+}
+
+/*
+ * Dump TCP statistics structure.
+ */
+void
+tcp_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct tcpstat tcpstat;
+ uint64_t tcps_states[TCP_NSTATES];
+
+#ifdef INET6
+ if (tcp_done != 0)
+ return;
+ else
+ tcp_done = 1;
+#endif
+
+ if (fetch_stats("net.inet.tcp.stats", off, &tcpstat,
+ sizeof(tcpstat), kread_counters) != 0)
+ return;
+
+ if (fetch_stats_ro("net.inet.tcp.states", nl[N_TCPS_STATES].n_value,
+ &tcps_states, sizeof(tcps_states), kread_counters) != 0)
+ return;
+
+ xo_open_container("tcp");
+ xo_emit("{T:/%s}:\n", name);
+
+#define p(f, m) if (tcpstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t )tcpstat.f, plural(tcpstat.f))
+#define p1a(f, m) if (tcpstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t )tcpstat.f)
+#define p2(f1, f2, m) if (tcpstat.f1 || tcpstat.f2 || sflag <= 1) \
+ xo_emit(m, (uintmax_t )tcpstat.f1, plural(tcpstat.f1), \
+ (uintmax_t )tcpstat.f2, plural(tcpstat.f2))
+#define p2a(f1, f2, m) if (tcpstat.f1 || tcpstat.f2 || sflag <= 1) \
+ xo_emit(m, (uintmax_t )tcpstat.f1, plural(tcpstat.f1), \
+ (uintmax_t )tcpstat.f2)
+#define p3(f, m) if (tcpstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t )tcpstat.f, pluralies(tcpstat.f))
+
+ p(tcps_sndtotal, "\t{:sent-packets/%ju} {N:/packet%s sent}\n");
+ p2(tcps_sndpack,tcps_sndbyte, "\t\t{:sent-data-packets/%ju} "
+ "{N:/data packet%s} ({:sent-data-bytes/%ju} {N:/byte%s})\n");
+ p2(tcps_sndrexmitpack, tcps_sndrexmitbyte, "\t\t"
+ "{:sent-retransmitted-packets/%ju} {N:/data packet%s} "
+ "({:sent-retransmitted-bytes/%ju} {N:/byte%s}) "
+ "{N:retransmitted}\n");
+ p(tcps_sndrexmitbad, "\t\t"
+ "{:sent-unnecessary-retransmitted-packets/%ju} "
+ "{N:/data packet%s unnecessarily retransmitted}\n");
+ p(tcps_mturesent, "\t\t{:sent-resends-by-mtu-discovery/%ju} "
+ "{N:/resend%s initiated by MTU discovery}\n");
+ p2a(tcps_sndacks, tcps_delack, "\t\t{:sent-ack-only-packets/%ju} "
+ "{N:/ack-only packet%s/} ({:sent-packets-delayed/%ju} "
+ "{N:delayed})\n");
+ p(tcps_sndurg, "\t\t{:sent-urg-only-packets/%ju} "
+ "{N:/URG only packet%s}\n");
+ p(tcps_sndprobe, "\t\t{:sent-window-probe-packets/%ju} "
+ "{N:/window probe packet%s}\n");
+ p(tcps_sndwinup, "\t\t{:sent-window-update-packets/%ju} "
+ "{N:/window update packet%s}\n");
+ p(tcps_sndctrl, "\t\t{:sent-control-packets/%ju} "
+ "{N:/control packet%s}\n");
+ p(tcps_rcvtotal, "\t{:received-packets/%ju} "
+ "{N:/packet%s received}\n");
+ p2(tcps_rcvackpack, tcps_rcvackbyte, "\t\t"
+ "{:received-ack-packets/%ju} {N:/ack%s} "
+ "{N:(for} {:received-ack-bytes/%ju} {N:/byte%s})\n");
+ p(tcps_rcvdupack, "\t\t{:received-duplicate-acks/%ju} "
+ "{N:/duplicate ack%s}\n");
+ p(tcps_tunneled_pkts, "\t\t{:received-udp-tunneled-pkts/%ju} "
+ "{N:/UDP tunneled pkt%s}\n");
+ p(tcps_tunneled_errs, "\t\t{:received-bad-udp-tunneled-pkts/%ju} "
+ "{N:/UDP tunneled pkt cnt with error%s}\n");
+ p(tcps_rcvacktoomuch, "\t\t{:received-acks-for-data-not-yet-sent/%ju} "
+ "{N:/ack%s for data not yet sent}\n");
+ p(tcps_rcvghostack, "\t\t{:received-acks-for-data-never-been-sent/%ju} "
+ "{N:/ack%s for data never been sent (ghost acks)}\n");
+ p(tcps_rcvacktooold, "\t\t{:received-acks-for-data-being-too-old/%ju} "
+ "{N:/ack%s for data being too old}\n");
+ p2(tcps_rcvpack, tcps_rcvbyte, "\t\t"
+ "{:received-in-sequence-packets/%ju} {N:/packet%s} "
+ "({:received-in-sequence-bytes/%ju} {N:/byte%s}) "
+ "{N:received in-sequence}\n");
+ p2(tcps_rcvduppack, tcps_rcvdupbyte, "\t\t"
+ "{:received-completely-duplicate-packets/%ju} "
+ "{N:/completely duplicate packet%s} "
+ "({:received-completely-duplicate-bytes/%ju} {N:/byte%s})\n");
+ p(tcps_pawsdrop, "\t\t{:received-old-duplicate-packets/%ju} "
+ "{N:/old duplicate packet%s}\n");
+ p2(tcps_rcvpartduppack, tcps_rcvpartdupbyte, "\t\t"
+ "{:received-some-duplicate-packets/%ju} "
+ "{N:/packet%s with some dup. data} "
+ "({:received-some-duplicate-bytes/%ju} {N:/byte%s duped/})\n");
+ p2(tcps_rcvoopack, tcps_rcvoobyte, "\t\t{:received-out-of-order/%ju} "
+ "{N:/out-of-order packet%s} "
+ "({:received-out-of-order-bytes/%ju} {N:/byte%s})\n");
+ p2(tcps_rcvpackafterwin, tcps_rcvbyteafterwin, "\t\t"
+ "{:received-after-window-packets/%ju} {N:/packet%s} "
+ "({:received-after-window-bytes/%ju} {N:/byte%s}) "
+ "{N:of data after window}\n");
+ p(tcps_rcvwinprobe, "\t\t{:received-window-probes/%ju} "
+ "{N:/window probe%s}\n");
+ p(tcps_rcvwinupd, "\t\t{:receive-window-update-packets/%ju} "
+ "{N:/window update packet%s}\n");
+ p(tcps_dsack_count, "\t\t{:received-with-dsack-packets/%ju} "
+ "{N:/packet%s received with dsack}\n");
+ p(tcps_dsack_bytes, "\t\t{:received-with-dsack-bytes/%ju} "
+ "{N:/dsack byte%s received (no TLP involved)}\n");
+ p(tcps_dsack_tlp_bytes, "\t\t{:received-with-dsack-bytes-tlp/%ju} "
+ "{N:/dsack byte%s received (TLP responsible)}\n");
+ p(tcps_rcvafterclose, "\t\t{:received-after-close-packets/%ju} "
+ "{N:/packet%s received after close}\n");
+ p(tcps_rcvbadsum, "\t\t{:discard-bad-checksum/%ju} "
+ "{N:/discarded for bad checksum%s}\n");
+ p(tcps_rcvbadoff, "\t\t{:discard-bad-header-offset/%ju} "
+ "{N:/discarded for bad header offset field%s}\n");
+ p1a(tcps_rcvshort, "\t\t{:discard-too-short/%ju} "
+ "{N:discarded because packet too short}\n");
+ p1a(tcps_rcvreassfull, "\t\t{:discard-reassembly-queue-full/%ju} "
+ "{N:discarded due to full reassembly queue}\n");
+ p(tcps_connattempt, "\t{:connection-requests/%ju} "
+ "{N:/connection request%s}\n");
+ p(tcps_accepts, "\t{:connections-accepts/%ju} "
+ "{N:/connection accept%s}\n");
+ p(tcps_badsyn, "\t{:bad-connection-attempts/%ju} "
+ "{N:/bad connection attempt%s}\n");
+ p(tcps_listendrop, "\t{:listen-queue-overflows/%ju} "
+ "{N:/listen queue overflow%s}\n");
+ p(tcps_badrst, "\t{:ignored-in-window-resets/%ju} "
+ "{N:/ignored RSTs in the window%s}\n");
+ p(tcps_connects, "\t{:connections-established/%ju} "
+ "{N:/connection%s established (including accepts)}\n");
+ p(tcps_usedrtt, "\t\t{:connections-hostcache-rtt/%ju} "
+ "{N:/time%s used RTT from hostcache}\n");
+ p(tcps_usedrttvar, "\t\t{:connections-hostcache-rttvar/%ju} "
+ "{N:/time%s used RTT variance from hostcache}\n");
+ p(tcps_usedssthresh, "\t\t{:connections-hostcache-ssthresh/%ju} "
+ "{N:/time%s used slow-start threshold from hostcache}\n");
+ p2(tcps_closed, tcps_drops, "\t{:connections-closed/%ju} "
+ "{N:/connection%s closed (including} "
+ "{:connection-drops/%ju} {N:/drop%s})\n");
+ p(tcps_cachedrtt, "\t\t{:connections-updated-rtt-on-close/%ju} "
+ "{N:/connection%s updated cached RTT on close}\n");
+ p(tcps_cachedrttvar, "\t\t"
+ "{:connections-updated-variance-on-close/%ju} "
+ "{N:/connection%s updated cached RTT variance on close}\n");
+ p(tcps_cachedssthresh, "\t\t"
+ "{:connections-updated-ssthresh-on-close/%ju} "
+ "{N:/connection%s updated cached ssthresh on close}\n");
+ p(tcps_conndrops, "\t{:embryonic-connections-dropped/%ju} "
+ "{N:/embryonic connection%s dropped}\n");
+ p2(tcps_rttupdated, tcps_segstimed, "\t{:segments-updated-rtt/%ju} "
+ "{N:/segment%s updated rtt (of} "
+ "{:segment-update-attempts/%ju} {N:/attempt%s})\n");
+ p(tcps_rexmttimeo, "\t{:retransmit-timeouts/%ju} "
+ "{N:/retransmit timeout%s}\n");
+ p(tcps_timeoutdrop, "\t\t"
+ "{:connections-dropped-by-retransmit-timeout/%ju} "
+ "{N:/connection%s dropped by rexmit timeout}\n");
+ p(tcps_persisttimeo, "\t{:persist-timeout/%ju} "
+ "{N:/persist timeout%s}\n");
+ p(tcps_persistdrop, "\t\t"
+ "{:connections-dropped-by-persist-timeout/%ju} "
+ "{N:/connection%s dropped by persist timeout}\n");
+ p(tcps_finwait2_drops, "\t"
+ "{:connections-dropped-by-finwait2-timeout/%ju} "
+ "{N:/Connection%s (fin_wait_2) dropped because of timeout}\n");
+ p(tcps_keeptimeo, "\t{:keepalive-timeout/%ju} "
+ "{N:/keepalive timeout%s}\n");
+ p(tcps_keepprobe, "\t\t{:keepalive-probes/%ju} "
+ "{N:/keepalive probe%s sent}\n");
+ p(tcps_keepdrops, "\t\t{:connections-dropped-by-keepalives/%ju} "
+ "{N:/connection%s dropped by keepalive}\n");
+ p(tcps_progdrops, "\t{:connections-dropped-due-to-progress-time/%ju} "
+ "{N:/connection%s dropped due to exceeding progress time}\n");
+ p(tcps_predack, "\t{:ack-header-predictions/%ju} "
+ "{N:/correct ACK header prediction%s}\n");
+ p(tcps_preddat, "\t{:data-packet-header-predictions/%ju} "
+ "{N:/correct data packet header prediction%s}\n");
+
+ xo_open_container("syncache");
+
+ p3(tcps_sc_added, "\t{:entries-added/%ju} "
+ "{N:/syncache entr%s added}\n");
+ p1a(tcps_sc_retransmitted, "\t\t{:retransmitted/%ju} "
+ "{N:/retransmitted}\n");
+ p1a(tcps_sc_dupsyn, "\t\t{:duplicates/%ju} {N:/dupsyn}\n");
+ p1a(tcps_sc_dropped, "\t\t{:dropped/%ju} {N:/dropped}\n");
+ p1a(tcps_sc_completed, "\t\t{:completed/%ju} {N:/completed}\n");
+ p1a(tcps_sc_bucketoverflow, "\t\t{:bucket-overflow/%ju} "
+ "{N:/bucket overflow}\n");
+ p1a(tcps_sc_cacheoverflow, "\t\t{:cache-overflow/%ju} "
+ "{N:/cache overflow}\n");
+ p1a(tcps_sc_reset, "\t\t{:reset/%ju} {N:/reset}\n");
+ p1a(tcps_sc_stale, "\t\t{:stale/%ju} {N:/stale}\n");
+ p1a(tcps_sc_aborted, "\t\t{:aborted/%ju} {N:/aborted}\n");
+ p1a(tcps_sc_badack, "\t\t{:bad-ack/%ju} {N:/badack}\n");
+ p1a(tcps_sc_unreach, "\t\t{:unreachable/%ju} {N:/unreach}\n");
+ p(tcps_sc_zonefail, "\t\t{:zone-failures/%ju} {N:/zone failure%s}\n");
+
+ xo_close_container("syncache");
+
+ xo_open_container("syncookies");
+
+ p(tcps_sc_sendcookie, "\t{:sent-cookies/%ju} {N:/cookie%s sent}\n");
+ p(tcps_sc_recvcookie, "\t\t{:received-cookies/%ju} "
+ "{N:/cookie%s received}\n");
+ p(tcps_sc_spurcookie, "\t\t{:spurious-cookies/%ju} "
+ "{N:/spurious cookie%s rejected}\n");
+ p(tcps_sc_failcookie, "\t\t{:failed-cookies/%ju} "
+ "{N:/failed cookie%s rejected}\n");
+
+ xo_close_container("syncookies");
+
+ xo_open_container("hostcache");
+
+ p3(tcps_hc_added, "\t{:entries-added/%ju} "
+ "{N:/hostcache entr%s added}\n");
+ p1a(tcps_hc_bucketoverflow, "\t\t{:buffer-overflows/%ju} "
+ "{N:/bucket overflow}\n");
+
+ xo_close_container("hostcache");
+
+ xo_open_container("sack");
+
+ p(tcps_sack_recovery_episode, "\t{:recovery-episodes/%ju} "
+ "{N:/SACK recovery episode%s}\n");
+ p(tcps_sack_rexmits, "\t{:segment-retransmits/%ju} "
+ "{N:/segment rexmit%s in SACK recovery episodes}\n");
+ p(tcps_sack_rexmits_tso, "\t{:tso-chunk-retransmits/%ju} "
+ "{N:/tso chunk rexmit%s in SACK recovery episodes}\n");
+ p(tcps_sack_rexmit_bytes, "\t{:byte-retransmits/%ju} "
+ "{N:/byte rexmit%s in SACK recovery episodes}\n");
+ p(tcps_sack_rcv_blocks, "\t{:received-blocks/%ju} "
+ "{N:/SACK option%s (SACK blocks) received}\n");
+ p(tcps_sack_send_blocks, "\t{:sent-option-blocks/%ju} "
+ "{N:/SACK option%s (SACK blocks) sent}\n");
+ p(tcps_sack_lostrexmt, "\t{:lost-retransmissions/%ju} "
+ "{N:/SACK retransmission%s lost}\n");
+ p1a(tcps_sack_sboverflow, "\t{:scoreboard-overflows/%ju} "
+ "{N:/SACK scoreboard overflow}\n");
+
+ xo_close_container("sack");
+ xo_open_container("ecn");
+
+ p(tcps_ecn_rcvce, "\t{:received-ce-packets/%ju} "
+ "{N:/packet%s received with ECN CE bit set}\n");
+ p(tcps_ecn_rcvect0, "\t{:received-ect0-packets/%ju} "
+ "{N:/packet%s received with ECN ECT(0) bit set}\n");
+ p(tcps_ecn_rcvect1, "\t{:received-ect1-packets/%ju} "
+ "{N:/packet%s received with ECN ECT(1) bit set}\n");
+ p(tcps_ecn_sndect0, "\t{:sent-ect0-packets/%ju} "
+ "{N:/packet%s sent with ECN ECT(0) bit set}\n");
+ p(tcps_ecn_sndect1, "\t{:sent-ect1-packets/%ju} "
+ "{N:/packet%s sent with ECN ECT(1) bit set}\n");
+ p(tcps_ecn_shs, "\t{:handshakes/%ju} "
+ "{N:/successful ECN handshake%s}\n");
+ p(tcps_ecn_rcwnd, "\t{:congestion-reductions/%ju} "
+ "{N:/time%s ECN reduced the congestion window}\n");
+
+ p(tcps_ace_nect, "\t{:ace-nonect-syn/%ju} "
+ "{N:/ACE SYN packet%s with Non-ECT}\n");
+ p(tcps_ace_ect0, "\t{:ace-ect0-syn/%ju} "
+ "{N:/ACE SYN packet%s with ECT0}\n");
+ p(tcps_ace_ect1, "\t{:ace-ect1-syn/%ju} "
+ "{N:/ACE SYN packet%s with ECT1}\n");
+ p(tcps_ace_ce, "\t{:ace-ce-syn/%ju} "
+ "{N:/ACE SYN packet%s with CE}\n");
+
+ xo_close_container("ecn");
+ xo_open_container("tcp-signature");
+ p(tcps_sig_rcvgoodsig, "\t{:received-good-signature/%ju} "
+ "{N:/packet%s with matching signature received}\n");
+ p(tcps_sig_rcvbadsig, "\t{:received-bad-signature/%ju} "
+ "{N:/packet%s with bad signature received}\n");
+ p(tcps_sig_err_buildsig, "\t{:failed-make-signature/%ju} "
+ "{N:/time%s failed to make signature due to no SA}\n");
+ p(tcps_sig_err_sigopt, "\t{:no-signature-expected/%ju} "
+ "{N:/time%s unexpected signature received}\n");
+ p(tcps_sig_err_nosigopt, "\t{:no-signature-provided/%ju} "
+ "{N:/time%s no signature provided by segment}\n");
+
+ xo_close_container("tcp-signature");
+ xo_open_container("pmtud");
+
+ p(tcps_pmtud_blackhole_activated, "\t{:pmtud-activated/%ju} "
+ "{N:/Path MTU discovery black hole detection activation%s}\n");
+ p(tcps_pmtud_blackhole_activated_min_mss,
+ "\t{:pmtud-activated-min-mss/%ju} "
+ "{N:/Path MTU discovery black hole detection min MSS activation%s}\n");
+ p(tcps_pmtud_blackhole_failed, "\t{:pmtud-failed/%ju} "
+ "{N:/Path MTU discovery black hole detection failure%s}\n");
+
+ xo_close_container("pmtud");
+ xo_open_container("tw");
+
+ p(tcps_tw_responds, "\t{:tw_responds/%ju} "
+ "{N:/time%s connection in TIME-WAIT responded with ACK}\n");
+ p(tcps_tw_recycles, "\t{:tw_recycles/%ju} "
+ "{N:/time%s connection in TIME-WAIT was actively recycled}\n");
+ p(tcps_tw_resets, "\t{:tw_resets/%ju} "
+ "{N:/time%s connection in TIME-WAIT responded with RST}\n");
+
+ xo_close_container("tw");
+ #undef p
+ #undef p1a
+ #undef p2
+ #undef p2a
+ #undef p3
+
+ xo_open_container("TCP connection count by state");
+ xo_emit("{T:/TCP connection count by state}:\n");
+ for (int i = 0; i < TCP_NSTATES; i++) {
+ /*
+ * XXXGL: is there a way in libxo to use %s
+ * in the "content string" of a format
+ * string? I failed to do that, that's why
+ * a temporary buffer is used to construct
+ * format string for xo_emit().
+ */
+ char fmtbuf[80];
+
+ if (sflag > 1 && tcps_states[i] == 0)
+ continue;
+ snprintf(fmtbuf, sizeof(fmtbuf), "\t{:%s/%%ju} "
+ "{Np:/connection ,connections} in %s state\n",
+ tcpstates[i], tcpstates[i]);
+ xo_emit(fmtbuf, (uintmax_t )tcps_states[i]);
+ }
+ xo_close_container("TCP connection count by state");
+
+ xo_close_container("tcp");
+}
+
+/*
+ * Dump UDP statistics structure.
+ */
+void
+udp_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct udpstat udpstat;
+ uint64_t delivered, noportbmcast;
+
+#ifdef INET6
+ if (udp_done != 0)
+ return;
+ else
+ udp_done = 1;
+#endif
+
+ if (fetch_stats("net.inet.udp.stats", off, &udpstat,
+ sizeof(udpstat), kread_counters) != 0)
+ return;
+
+ xo_open_container("udp");
+ xo_emit("{T:/%s}:\n", name);
+
+#define p(f, m) if (udpstat.f || sflag <= 1) \
+ xo_emit("\t" m, (uintmax_t)udpstat.f, plural(udpstat.f))
+#define p1a(f, m) if (udpstat.f || sflag <= 1) \
+ xo_emit("\t" m, (uintmax_t)udpstat.f)
+
+ p(udps_ipackets, "{:received-datagrams/%ju} "
+ "{N:/datagram%s received}\n");
+ p1a(udps_hdrops, "{:dropped-incomplete-headers/%ju} "
+ "{N:/with incomplete header}\n");
+ p1a(udps_badlen, "{:dropped-bad-data-length/%ju} "
+ "{N:/with bad data length field}\n");
+ p1a(udps_badsum, "{:dropped-bad-checksum/%ju} "
+ "{N:/with bad checksum}\n");
+ p1a(udps_nosum, "{:dropped-no-checksum/%ju} "
+ "{N:/with no checksum}\n");
+ p1a(udps_noport, "{:dropped-no-socket/%ju} "
+ "{N:/dropped due to no socket}\n");
+ noportbmcast = udpstat.udps_noportmcast + udpstat.udps_noportbcast;
+ if (noportbmcast || sflag <= 1)
+ xo_emit("\t{:dropped-broadcast-multicast/%ju} "
+ "{N:/broadcast\\/multicast datagram%s undelivered}\n",
+ (uintmax_t)noportbmcast, plural(noportbmcast));
+ p1a(udps_fullsock, "{:dropped-full-socket-buffer/%ju} "
+ "{N:/dropped due to full socket buffers}\n");
+ p1a(udpps_pcbhashmiss, "{:not-for-hashed-pcb/%ju} "
+ "{N:/not for hashed pcb}\n");
+ delivered = udpstat.udps_ipackets -
+ udpstat.udps_hdrops -
+ udpstat.udps_badlen -
+ udpstat.udps_badsum -
+ udpstat.udps_noport -
+ udpstat.udps_fullsock;
+ if (delivered || sflag <= 1)
+ xo_emit("\t{:delivered-packets/%ju} {N:/delivered}\n",
+ (uintmax_t)delivered);
+ p(udps_opackets, "{:output-packets/%ju} {N:/datagram%s output}\n");
+ /* the next statistic is cumulative in udps_noportbcast */
+ p(udps_filtermcast, "{:multicast-source-filter-matches/%ju} "
+ "{N:/time%s multicast source filter matched}\n");
+#undef p
+#undef p1a
+ xo_close_container("udp");
+}
+
+/*
+ * Dump CARP statistics structure.
+ */
+void
+carp_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct carpstats carpstat;
+
+ if (fetch_stats("net.inet.carp.stats", off, &carpstat,
+ sizeof(carpstat), kread_counters) != 0)
+ return;
+
+ xo_open_container(name);
+ xo_emit("{T:/%s}:\n", name);
+
+#define p(f, m) if (carpstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)carpstat.f, plural(carpstat.f))
+#define p2(f, m) if (carpstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)carpstat.f)
+
+ p(carps_ipackets, "\t{:received-inet-packets/%ju} "
+ "{N:/packet%s received (IPv4)}\n");
+ p(carps_ipackets6, "\t{:received-inet6-packets/%ju} "
+ "{N:/packet%s received (IPv6)}\n");
+ p(carps_badttl, "\t\t{:dropped-wrong-ttl/%ju} "
+ "{N:/packet%s discarded for wrong TTL}\n");
+ p(carps_hdrops, "\t\t{:dropped-short-header/%ju} "
+ "{N:/packet%s shorter than header}\n");
+ p(carps_badsum, "\t\t{:dropped-bad-checksum/%ju} "
+ "{N:/discarded for bad checksum%s}\n");
+ p(carps_badver, "\t\t{:dropped-bad-version/%ju} "
+ "{N:/discarded packet%s with a bad version}\n");
+ p2(carps_badlen, "\t\t{:dropped-short-packet/%ju} "
+ "{N:/discarded because packet too short}\n");
+ p2(carps_badauth, "\t\t{:dropped-bad-authentication/%ju} "
+ "{N:/discarded for bad authentication}\n");
+ p2(carps_badvhid, "\t\t{:dropped-bad-vhid/%ju} "
+ "{N:/discarded for bad vhid}\n");
+ p2(carps_badaddrs, "\t\t{:dropped-bad-address-list/%ju} "
+ "{N:/discarded because of a bad address list}\n");
+ p(carps_opackets, "\t{:sent-inet-packets/%ju} "
+ "{N:/packet%s sent (IPv4)}\n");
+ p(carps_opackets6, "\t{:sent-inet6-packets/%ju} "
+ "{N:/packet%s sent (IPv6)}\n");
+ p2(carps_onomem, "\t\t{:send-failed-memory-error/%ju} "
+ "{N:/send failed due to mbuf memory error}\n");
+#if notyet
+ p(carps_ostates, "\t\t{:send-state-updates/%s} "
+ "{N:/state update%s sent}\n");
+#endif
+#undef p
+#undef p2
+ xo_close_container(name);
+}
+
+/*
+ * Dump IP statistics structure.
+ */
+void
+ip_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct ipstat ipstat;
+
+ if (fetch_stats("net.inet.ip.stats", off, &ipstat,
+ sizeof(ipstat), kread_counters) != 0)
+ return;
+
+ xo_open_container(name);
+ xo_emit("{T:/%s}:\n", name);
+
+#define p(f, m) if (ipstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t )ipstat.f, plural(ipstat.f))
+#define p1a(f, m) if (ipstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t )ipstat.f)
+
+ p(ips_total, "\t{:received-packets/%ju} "
+ "{N:/total packet%s received}\n");
+ p(ips_badsum, "\t{:dropped-bad-checksum/%ju} "
+ "{N:/bad header checksum%s}\n");
+ p1a(ips_toosmall, "\t{:dropped-below-minimum-size/%ju} "
+ "{N:/with size smaller than minimum}\n");
+ p1a(ips_tooshort, "\t{:dropped-short-packets/%ju} "
+ "{N:/with data size < data length}\n");
+ p1a(ips_toolong, "\t{:dropped-too-long/%ju} "
+ "{N:/with ip length > max ip packet size}\n");
+ p1a(ips_badhlen, "\t{:dropped-short-header-length/%ju} "
+ "{N:/with header length < data size}\n");
+ p1a(ips_badlen, "\t{:dropped-short-data/%ju} "
+ "{N:/with data length < header length}\n");
+ p1a(ips_badoptions, "\t{:dropped-bad-options/%ju} "
+ "{N:/with bad options}\n");
+ p1a(ips_badvers, "\t{:dropped-bad-version/%ju} "
+ "{N:/with incorrect version number}\n");
+ p(ips_fragments, "\t{:received-fragments/%ju} "
+ "{N:/fragment%s received}\n");
+ p(ips_fragdropped, "\t{:dropped-fragments/%ju} "
+ "{N:/fragment%s dropped (dup or out of space)}\n");
+ p(ips_fragtimeout, "\t{:dropped-fragments-after-timeout/%ju} "
+ "{N:/fragment%s dropped after timeout}\n");
+ p(ips_reassembled, "\t{:reassembled-packets/%ju} "
+ "{N:/packet%s reassembled ok}\n");
+ p(ips_delivered, "\t{:received-local-packets/%ju} "
+ "{N:/packet%s for this host}\n");
+ p(ips_noproto, "\t{:dropped-unknown-protocol/%ju} "
+ "{N:/packet%s for unknown\\/unsupported protocol}\n");
+ p(ips_forward, "\t{:forwarded-packets/%ju} "
+ "{N:/packet%s forwarded}");
+ p(ips_fastforward, " ({:fast-forwarded-packets/%ju} "
+ "{N:/packet%s fast forwarded})");
+ if (ipstat.ips_forward || sflag <= 1)
+ xo_emit("\n");
+ p(ips_cantforward, "\t{:packets-cannot-forward/%ju} "
+ "{N:/packet%s not forwardable}\n");
+ p(ips_notmember, "\t{:received-unknown-multicast-group/%ju} "
+ "{N:/packet%s received for unknown multicast group}\n");
+ p(ips_redirectsent, "\t{:redirects-sent/%ju} "
+ "{N:/redirect%s sent}\n");
+ p(ips_localout, "\t{:sent-packets/%ju} "
+ "{N:/packet%s sent from this host}\n");
+ p(ips_rawout, "\t{:send-packets-fabricated-header/%ju} "
+ "{N:/packet%s sent with fabricated ip header}\n");
+ p(ips_odropped, "\t{:discard-no-mbufs/%ju} "
+ "{N:/output packet%s dropped due to no bufs, etc.}\n");
+ p(ips_noroute, "\t{:discard-no-route/%ju} "
+ "{N:/output packet%s discarded due to no route}\n");
+ p(ips_fragmented, "\t{:sent-fragments/%ju} "
+ "{N:/output datagram%s fragmented}\n");
+ p(ips_ofragments, "\t{:fragments-created/%ju} "
+ "{N:/fragment%s created}\n");
+ p(ips_cantfrag, "\t{:discard-cannot-fragment/%ju} "
+ "{N:/datagram%s that can't be fragmented}\n");
+ p(ips_nogif, "\t{:discard-tunnel-no-gif/%ju} "
+ "{N:/tunneling packet%s that can't find gif}\n");
+ p(ips_badaddr, "\t{:discard-bad-address/%ju} "
+ "{N:/datagram%s with bad address in header}\n");
+#undef p
+#undef p1a
+ xo_close_container(name);
+}
+
+/*
+ * Dump ARP statistics structure.
+ */
+void
+arp_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct arpstat arpstat;
+
+ if (fetch_stats("net.link.ether.arp.stats", off, &arpstat,
+ sizeof(arpstat), kread_counters) != 0)
+ return;
+
+ xo_open_container(name);
+ xo_emit("{T:/%s}:\n", name);
+
+#define p(f, m) if (arpstat.f || sflag <= 1) \
+ xo_emit("\t" m, (uintmax_t)arpstat.f, plural(arpstat.f))
+#define p2(f, m) if (arpstat.f || sflag <= 1) \
+ xo_emit("\t" m, (uintmax_t)arpstat.f, pluralies(arpstat.f))
+
+ p(txrequests, "{:sent-requests/%ju} {N:/ARP request%s sent}\n");
+ p(txerrors, "{:sent-failures/%ju} {N:/ARP request%s failed to sent}\n");
+ p2(txreplies, "{:sent-replies/%ju} {N:/ARP repl%s sent}\n");
+ p(rxrequests, "{:received-requests/%ju} "
+ "{N:/ARP request%s received}\n");
+ p2(rxreplies, "{:received-replies/%ju} "
+ "{N:/ARP repl%s received}\n");
+ p(received, "{:received-packets/%ju} "
+ "{N:/ARP packet%s received}\n");
+ p(dropped, "{:dropped-no-entry/%ju} "
+ "{N:/total packet%s dropped due to no ARP entry}\n");
+ p(timeouts, "{:entries-timeout/%ju} "
+ "{N:/ARP entry%s timed out}\n");
+ p(dupips, "{:dropped-duplicate-address/%ju} "
+ "{N:/Duplicate IP%s seen}\n");
+#undef p
+#undef p2
+ xo_close_container(name);
+}
+
+
+
+static const char *icmpnames[ICMP_MAXTYPE + 1] = {
+ "echo reply", /* RFC 792 */
+ "#1",
+ "#2",
+ "destination unreachable", /* RFC 792 */
+ "source quench", /* RFC 792 */
+ "routing redirect", /* RFC 792 */
+ "#6",
+ "#7",
+ "echo", /* RFC 792 */
+ "router advertisement", /* RFC 1256 */
+ "router solicitation", /* RFC 1256 */
+ "time exceeded", /* RFC 792 */
+ "parameter problem", /* RFC 792 */
+ "time stamp", /* RFC 792 */
+ "time stamp reply", /* RFC 792 */
+ "information request", /* RFC 792 */
+ "information request reply", /* RFC 792 */
+ "address mask request", /* RFC 950 */
+ "address mask reply", /* RFC 950 */
+ "#19",
+ "#20",
+ "#21",
+ "#22",
+ "#23",
+ "#24",
+ "#25",
+ "#26",
+ "#27",
+ "#28",
+ "#29",
+ "icmp traceroute", /* RFC 1393 */
+ "datagram conversion error", /* RFC 1475 */
+ "mobile host redirect",
+ "IPv6 where-are-you",
+ "IPv6 i-am-here",
+ "mobile registration req",
+ "mobile registration reply",
+ "domain name request", /* RFC 1788 */
+ "domain name reply", /* RFC 1788 */
+ "icmp SKIP",
+ "icmp photuris", /* RFC 2521 */
+};
+
+/*
+ * Dump ICMP statistics.
+ */
+void
+icmp_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct icmpstat icmpstat;
+ size_t len;
+ int i, first;
+
+ if (fetch_stats("net.inet.icmp.stats", off, &icmpstat,
+ sizeof(icmpstat), kread_counters) != 0)
+ return;
+
+ xo_open_container(name);
+ xo_emit("{T:/%s}:\n", name);
+
+#define p(f, m) if (icmpstat.f || sflag <= 1) \
+ xo_emit(m, icmpstat.f, plural(icmpstat.f))
+#define p1a(f, m) if (icmpstat.f || sflag <= 1) \
+ xo_emit(m, icmpstat.f)
+#define p2(f, m) if (icmpstat.f || sflag <= 1) \
+ xo_emit(m, icmpstat.f, plurales(icmpstat.f))
+
+ p(icps_error, "\t{:icmp-calls/%lu} "
+ "{N:/call%s to icmp_error}\n");
+ p(icps_oldicmp, "\t{:errors-not-from-message/%lu} "
+ "{N:/error%s not generated in response to an icmp message}\n");
+
+ for (first = 1, i = 0; i < ICMP_MAXTYPE + 1; i++) {
+ if (icmpstat.icps_outhist[i] != 0) {
+ if (first) {
+ xo_open_list("output-histogram");
+ xo_emit("\tOutput histogram:\n");
+ first = 0;
+ }
+ xo_open_instance("output-histogram");
+ if (icmpnames[i] != NULL)
+ xo_emit("\t\t{k:name/%s}: {:count/%lu}\n",
+ icmpnames[i], icmpstat.icps_outhist[i]);
+ else
+ xo_emit("\t\tunknown ICMP #{k:name/%d}: "
+ "{:count/%lu}\n",
+ i, icmpstat.icps_outhist[i]);
+ xo_close_instance("output-histogram");
+ }
+ }
+ if (!first)
+ xo_close_list("output-histogram");
+
+ p(icps_badcode, "\t{:dropped-bad-code/%lu} "
+ "{N:/message%s with bad code fields}\n");
+ p(icps_tooshort, "\t{:dropped-too-short/%lu} "
+ "{N:/message%s less than the minimum length}\n");
+ p(icps_checksum, "\t{:dropped-bad-checksum/%lu} "
+ "{N:/message%s with bad checksum}\n");
+ p(icps_badlen, "\t{:dropped-bad-length/%lu} "
+ "{N:/message%s with bad length}\n");
+ p1a(icps_bmcastecho, "\t{:dropped-multicast-echo/%lu} "
+ "{N:/multicast echo requests ignored}\n");
+ p1a(icps_bmcasttstamp, "\t{:dropped-multicast-timestamp/%lu} "
+ "{N:/multicast timestamp requests ignored}\n");
+
+ for (first = 1, i = 0; i < ICMP_MAXTYPE + 1; i++) {
+ if (icmpstat.icps_inhist[i] != 0) {
+ if (first) {
+ xo_open_list("input-histogram");
+ xo_emit("\tInput histogram:\n");
+ first = 0;
+ }
+ xo_open_instance("input-histogram");
+ if (icmpnames[i] != NULL)
+ xo_emit("\t\t{k:name/%s}: {:count/%lu}\n",
+ icmpnames[i],
+ icmpstat.icps_inhist[i]);
+ else
+ xo_emit(
+ "\t\tunknown ICMP #{k:name/%d}: {:count/%lu}\n",
+ i, icmpstat.icps_inhist[i]);
+ xo_close_instance("input-histogram");
+ }
+ }
+ if (!first)
+ xo_close_list("input-histogram");
+
+ p(icps_reflect, "\t{:sent-packets/%lu} "
+ "{N:/message response%s generated}\n");
+ p2(icps_badaddr, "\t{:discard-invalid-return-address/%lu} "
+ "{N:/invalid return address%s}\n");
+ p(icps_noroute, "\t{:discard-no-route/%lu} "
+ "{N:/no return route%s}\n");
+#undef p
+#undef p1a
+#undef p2
+ if (live) {
+ len = sizeof i;
+ if (sysctlbyname("net.inet.icmp.maskrepl", &i, &len, NULL, 0) <
+ 0)
+ return;
+ xo_emit("\tICMP address mask responses are "
+ "{q:icmp-address-responses/%sabled}\n", i ? "en" : "dis");
+ }
+
+ xo_close_container(name);
+}
+
+/*
+ * Dump IGMP statistics structure.
+ */
+void
+igmp_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct igmpstat igmpstat;
+ int error, zflag0;
+
+ if (fetch_stats("net.inet.igmp.stats", 0, &igmpstat,
+ sizeof(igmpstat), kread) != 0)
+ return;
+ /*
+ * Reread net.inet.igmp.stats when zflag == 1.
+ * This is because this MIB contains version number and
+ * length of the structure which are not set when clearing
+ * the counters.
+ */
+ zflag0 = zflag;
+ if (zflag) {
+ zflag = 0;
+ error = fetch_stats("net.inet.igmp.stats", 0, &igmpstat,
+ sizeof(igmpstat), kread);
+ zflag = zflag0;
+ if (error)
+ return;
+ }
+
+ if (igmpstat.igps_version != IGPS_VERSION_3) {
+ xo_warnx("%s: version mismatch (%d != %d)", __func__,
+ igmpstat.igps_version, IGPS_VERSION_3);
+ return;
+ }
+ if (igmpstat.igps_len != IGPS_VERSION3_LEN) {
+ xo_warnx("%s: size mismatch (%d != %d)", __func__,
+ igmpstat.igps_len, IGPS_VERSION3_LEN);
+ return;
+ }
+
+ xo_open_container(name);
+ xo_emit("{T:/%s}:\n", name);
+
+#define p64(f, m) if (igmpstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t) igmpstat.f, plural(igmpstat.f))
+#define py64(f, m) if (igmpstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t) igmpstat.f, pluralies(igmpstat.f))
+
+ p64(igps_rcv_total, "\t{:received-messages/%ju} "
+ "{N:/message%s received}\n");
+ p64(igps_rcv_tooshort, "\t{:dropped-too-short/%ju} "
+ "{N:/message%s received with too few bytes}\n");
+ p64(igps_rcv_badttl, "\t{:dropped-wrong-ttl/%ju} "
+ "{N:/message%s received with wrong TTL}\n");
+ p64(igps_rcv_badsum, "\t{:dropped-bad-checksum/%ju} "
+ "{N:/message%s received with bad checksum}\n");
+ py64(igps_rcv_v1v2_queries, "\t{:received-membership-queries/%ju} "
+ "{N:/V1\\/V2 membership quer%s received}\n");
+ py64(igps_rcv_v3_queries, "\t{:received-v3-membership-queries/%ju} "
+ "{N:/V3 membership quer%s received}\n");
+ py64(igps_rcv_badqueries, "\t{:dropped-membership-queries/%ju} "
+ "{N:/membership quer%s received with invalid field(s)}\n");
+ py64(igps_rcv_gen_queries, "\t{:received-general-queries/%ju} "
+ "{N:/general quer%s received}\n");
+ py64(igps_rcv_group_queries, "\t{:received-group-queries/%ju} "
+ "{N:/group quer%s received}\n");
+ py64(igps_rcv_gsr_queries, "\t{:received-group-source-queries/%ju} "
+ "{N:/group-source quer%s received}\n");
+ py64(igps_drop_gsr_queries, "\t{:dropped-group-source-queries/%ju} "
+ "{N:/group-source quer%s dropped}\n");
+ p64(igps_rcv_reports, "\t{:received-membership-requests/%ju} "
+ "{N:/membership report%s received}\n");
+ p64(igps_rcv_badreports, "\t{:dropped-membership-reports/%ju} "
+ "{N:/membership report%s received with invalid field(s)}\n");
+ p64(igps_rcv_ourreports, "\t"
+ "{:received-membership-reports-matching/%ju} "
+ "{N:/membership report%s received for groups to which we belong}"
+ "\n");
+ p64(igps_rcv_nora, "\t{:received-v3-reports-no-router-alert/%ju} "
+ "{N:/V3 report%s received without Router Alert}\n");
+ p64(igps_snd_reports, "\t{:sent-membership-reports/%ju} "
+ "{N:/membership report%s sent}\n");
+#undef p64
+#undef py64
+ xo_close_container(name);
+}
+
+/*
+ * Dump PIM statistics structure.
+ */
+void
+pim_stats(u_long off __unused, const char *name, int af1 __unused,
+ int proto __unused)
+{
+ struct pimstat pimstat;
+
+ if (fetch_stats("net.inet.pim.stats", off, &pimstat,
+ sizeof(pimstat), kread_counters) != 0)
+ return;
+
+ xo_open_container(name);
+ xo_emit("{T:/%s}:\n", name);
+
+#define p(f, m) if (pimstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)pimstat.f, plural(pimstat.f))
+#define py(f, m) if (pimstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)pimstat.f, pimstat.f != 1 ? "ies" : "y")
+
+ p(pims_rcv_total_msgs, "\t{:received-messages/%ju} "
+ "{N:/message%s received}\n");
+ p(pims_rcv_total_bytes, "\t{:received-bytes/%ju} "
+ "{N:/byte%s received}\n");
+ p(pims_rcv_tooshort, "\t{:dropped-too-short/%ju} "
+ "{N:/message%s received with too few bytes}\n");
+ p(pims_rcv_badsum, "\t{:dropped-bad-checksum/%ju} "
+ "{N:/message%s received with bad checksum}\n");
+ p(pims_rcv_badversion, "\t{:dropped-bad-version/%ju} "
+ "{N:/message%s received with bad version}\n");
+ p(pims_rcv_registers_msgs, "\t{:received-data-register-messages/%ju} "
+ "{N:/data register message%s received}\n");
+ p(pims_rcv_registers_bytes, "\t{:received-data-register-bytes/%ju} "
+ "{N:/data register byte%s received}\n");
+ p(pims_rcv_registers_wrongiif, "\t"
+ "{:received-data-register-wrong-interface/%ju} "
+ "{N:/data register message%s received on wrong iif}\n");
+ p(pims_rcv_badregisters, "\t{:received-bad-registers/%ju} "
+ "{N:/bad register%s received}\n");
+ p(pims_snd_registers_msgs, "\t{:sent-data-register-messages/%ju} "
+ "{N:/data register message%s sent}\n");
+ p(pims_snd_registers_bytes, "\t{:sent-data-register-bytes/%ju} "
+ "{N:/data register byte%s sent}\n");
+#undef p
+#undef py
+ xo_close_container(name);
+}
+
+/*
+ * Dump divert(4) statistics structure.
+ */
+void
+divert_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct divstat divstat;
+
+ if (fetch_stats("net.inet.divert.stats", off, &divstat,
+ sizeof(divstat), kread_counters) != 0)
+ return;
+
+ xo_open_container(name);
+ xo_emit("{T:/%s}:\n", name);
+
+#define p(f, m) if (divstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)divstat.f, plural(divstat.f))
+
+ p(div_diverted, "\t{:diverted-packets/%ju} "
+ "{N:/packet%s successfully diverted to userland}\n");
+ p(div_noport, "\t{:noport-fails/%ju} "
+ "{N:/packet%s failed to divert due to no socket bound at port}\n");
+ p(div_outbound, "\t{:outbound-packets/%ju} "
+ "{N:/packet%s successfully re-injected as outbound}\n");
+ p(div_inbound, "\t{:inbound-packets/%ju} "
+ "{N:/packet%s successfully re-injected as inbound}\n");
+#undef p
+ xo_close_container(name);
+}
+
+#ifdef INET
+/*
+ * Pretty print an Internet address (net address + port).
+ */
+static void
+inetprint(const char *container, struct in_addr *in, int port,
+ const char *proto, int num_port, const int af1)
+{
+ struct servent *sp = 0;
+ char line[80], *cp;
+ int width;
+ size_t alen, plen;
+
+ if (container)
+ xo_open_container(container);
+
+ if (Wflag)
+ snprintf(line, sizeof(line), "%s.", inetname(in));
+ else
+ snprintf(line, sizeof(line), "%.*s.",
+ (Aflag && !num_port) ? 12 : 16, inetname(in));
+ alen = strlen(line);
+ cp = line + alen;
+ if (!num_port && port)
+ sp = getservbyport((int)port, proto);
+ if (sp || port == 0)
+ snprintf(cp, sizeof(line) - alen,
+ "%.15s ", sp ? sp->s_name : "*");
+ else
+ snprintf(cp, sizeof(line) - alen,
+ "%d ", ntohs((u_short)port));
+ width = (Aflag && !Wflag) ? 18 :
+ ((!Wflag || af1 == AF_INET) ? 22 : 45);
+ if (Wflag)
+ xo_emit("{d:target/%-*s} ", width, line);
+ else
+ xo_emit("{d:target/%-*.*s} ", width, width, line);
+
+ plen = strlen(cp) - 1;
+ alen--;
+ xo_emit("{e:address/%*.*s}{e:port/%*.*s}", alen, alen, line, plen,
+ plen, cp);
+
+ if (container)
+ xo_close_container(container);
+}
+
+/*
+ * Construct an Internet address representation.
+ * If numeric_addr has been supplied, give
+ * numeric value, otherwise try for symbolic name.
+ */
+char *
+inetname(struct in_addr *inp)
+{
+ char *cp;
+ static char line[MAXHOSTNAMELEN];
+ struct hostent *hp;
+
+ cp = 0;
+ if (!numeric_addr && inp->s_addr != INADDR_ANY) {
+ hp = gethostbyaddr((char *)inp, sizeof (*inp), AF_INET);
+ if (hp) {
+ cp = hp->h_name;
+ trimdomain(cp, strlen(cp));
+ }
+ }
+ if (inp->s_addr == INADDR_ANY)
+ strcpy(line, "*");
+ else if (cp) {
+ strlcpy(line, cp, sizeof(line));
+ } else {
+ inp->s_addr = ntohl(inp->s_addr);
+#define C(x) ((u_int)((x) & 0xff))
+ snprintf(line, sizeof(line), "%u.%u.%u.%u",
+ C(inp->s_addr >> 24), C(inp->s_addr >> 16),
+ C(inp->s_addr >> 8), C(inp->s_addr));
+ }
+ return (line);
+}
+#endif
diff --git a/usr.bin/netstat/inet6.c b/usr.bin/netstat/inet6.c
new file mode 100644
index 000000000000..5995be299425
--- /dev/null
+++ b/usr.bin/netstat/inet6.c
@@ -0,0 +1,1359 @@
+/* BSDI inet.c,v 2.3 1995/10/24 02:19:29 prb Exp */
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1988, 1993
+ * The Regents of the University of California. 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.
+ * 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. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifdef INET6
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/ioctl.h>
+#include <sys/mbuf.h>
+#include <sys/protosw.h>
+
+#include <net/route.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <netinet/in_systm.h>
+#include <netinet6/in6_pcb.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/ip6_var.h>
+#include <netinet6/pim6_var.h>
+#include <netinet6/raw_ip6.h>
+
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+
+static char ntop_buf[INET6_ADDRSTRLEN];
+
+static const char *ip6nh[] = {
+ "hop by hop",
+ "ICMP",
+ "IGMP",
+ "#3",
+ "IP",
+ "#5",
+ "TCP",
+ "#7",
+ "#8",
+ "#9",
+ "#10",
+ "#11",
+ "#12",
+ "#13",
+ "#14",
+ "#15",
+ "#16",
+ "UDP",
+ "#18",
+ "#19",
+ "#20",
+ "#21",
+ "IDP",
+ "#23",
+ "#24",
+ "#25",
+ "#26",
+ "#27",
+ "#28",
+ "TP",
+ "#30",
+ "#31",
+ "#32",
+ "#33",
+ "#34",
+ "#35",
+ "#36",
+ "#37",
+ "#38",
+ "#39",
+ "#40",
+ "IP6",
+ "#42",
+ "routing",
+ "fragment",
+ "#45",
+ "#46",
+ "#47",
+ "#48",
+ "#49",
+ "ESP",
+ "AH",
+ "#52",
+ "#53",
+ "#54",
+ "#55",
+ "#56",
+ "#57",
+ "ICMP6",
+ "no next header",
+ "destination option",
+ "#61",
+ "mobility",
+ "#63",
+ "#64",
+ "#65",
+ "#66",
+ "#67",
+ "#68",
+ "#69",
+ "#70",
+ "#71",
+ "#72",
+ "#73",
+ "#74",
+ "#75",
+ "#76",
+ "#77",
+ "#78",
+ "#79",
+ "ISOIP",
+ "#81",
+ "#82",
+ "#83",
+ "#84",
+ "#85",
+ "#86",
+ "#87",
+ "#88",
+ "OSPF",
+ "#80",
+ "#91",
+ "#92",
+ "#93",
+ "#94",
+ "#95",
+ "#96",
+ "Ethernet",
+ "#98",
+ "#99",
+ "#100",
+ "#101",
+ "#102",
+ "PIM",
+ "#104",
+ "#105",
+ "#106",
+ "#107",
+ "#108",
+ "#109",
+ "#110",
+ "#111",
+ "#112",
+ "#113",
+ "#114",
+ "#115",
+ "#116",
+ "#117",
+ "#118",
+ "#119",
+ "#120",
+ "#121",
+ "#122",
+ "#123",
+ "#124",
+ "#125",
+ "#126",
+ "#127",
+ "#128",
+ "#129",
+ "#130",
+ "#131",
+ "SCTP",
+ "#133",
+ "#134",
+ "#135",
+ "UDPLite",
+ "#137",
+ "#138",
+ "#139",
+ "#140",
+ "#141",
+ "#142",
+ "#143",
+ "#144",
+ "#145",
+ "#146",
+ "#147",
+ "#148",
+ "#149",
+ "#150",
+ "#151",
+ "#152",
+ "#153",
+ "#154",
+ "#155",
+ "#156",
+ "#157",
+ "#158",
+ "#159",
+ "#160",
+ "#161",
+ "#162",
+ "#163",
+ "#164",
+ "#165",
+ "#166",
+ "#167",
+ "#168",
+ "#169",
+ "#170",
+ "#171",
+ "#172",
+ "#173",
+ "#174",
+ "#175",
+ "#176",
+ "#177",
+ "#178",
+ "#179",
+ "#180",
+ "#181",
+ "#182",
+ "#183",
+ "#184",
+ "#185",
+ "#186",
+ "#187",
+ "#188",
+ "#189",
+ "#180",
+ "#191",
+ "#192",
+ "#193",
+ "#194",
+ "#195",
+ "#196",
+ "#197",
+ "#198",
+ "#199",
+ "#200",
+ "#201",
+ "#202",
+ "#203",
+ "#204",
+ "#205",
+ "#206",
+ "#207",
+ "#208",
+ "#209",
+ "#210",
+ "#211",
+ "#212",
+ "#213",
+ "#214",
+ "#215",
+ "#216",
+ "#217",
+ "#218",
+ "#219",
+ "#220",
+ "#221",
+ "#222",
+ "#223",
+ "#224",
+ "#225",
+ "#226",
+ "#227",
+ "#228",
+ "#229",
+ "#230",
+ "#231",
+ "#232",
+ "#233",
+ "#234",
+ "#235",
+ "#236",
+ "#237",
+ "#238",
+ "#239",
+ "#240",
+ "#241",
+ "#242",
+ "#243",
+ "#244",
+ "#245",
+ "#246",
+ "#247",
+ "#248",
+ "#249",
+ "#250",
+ "#251",
+ "#252",
+ "#253",
+ "#254",
+ "#255",
+};
+
+static const char *srcrule_str[] = {
+ "first candidate",
+ "same address",
+ "appropriate scope",
+ "deprecated address",
+ "home address",
+ "outgoing interface",
+ "matching label",
+ "public/temporary address",
+ "alive interface",
+ "better virtual status",
+ "preferred source",
+ "rule #11",
+ "rule #12",
+ "rule #13",
+ "longest match",
+ "rule #15",
+};
+
+/*
+ * Dump IP6 statistics structure.
+ */
+void
+ip6_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct ip6stat ip6stat;
+ int first, i;
+
+ if (fetch_stats("net.inet6.ip6.stats", off, &ip6stat,
+ sizeof(ip6stat), kread_counters) != 0)
+ return;
+
+ xo_open_container(name);
+ xo_emit("{T:/%s}:\n", name);
+
+#define p(f, m) if (ip6stat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)ip6stat.f, plural(ip6stat.f))
+#define p1a(f, m) if (ip6stat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)ip6stat.f)
+
+ p(ip6s_total, "\t{:received-packets/%ju} "
+ "{N:/total packet%s received}\n");
+ p1a(ip6s_toosmall, "\t{:dropped-below-minimum-size/%ju} "
+ "{N:/with size smaller than minimum}\n");
+ p1a(ip6s_tooshort, "\t{:dropped-short-packets/%ju} "
+ "{N:/with data size < data length}\n");
+ p1a(ip6s_badoptions, "\t{:dropped-bad-options/%ju} "
+ "{N:/with bad options}\n");
+ p1a(ip6s_badvers, "\t{:dropped-bad-version/%ju} "
+ "{N:/with incorrect version number}\n");
+ p(ip6s_fragments, "\t{:received-fragments/%ju} "
+ "{N:/fragment%s received}\n");
+ p(ip6s_fragdropped, "\t{:dropped-fragment/%ju} "
+ "{N:/fragment%s dropped (dup or out of space)}\n");
+ p(ip6s_fragtimeout, "\t{:dropped-fragment-after-timeout/%ju} "
+ "{N:/fragment%s dropped after timeout}\n");
+ p(ip6s_fragoverflow, "\t{:dropped-fragments-overflow/%ju} "
+ "{N:/fragment%s that exceeded limit}\n");
+ p(ip6s_atomicfrags, "\t{:atomic-fragments/%ju} "
+ "{N:/atomic fragment%s}\n");
+ p(ip6s_reassembled, "\t{:reassembled-packets/%ju} "
+ "{N:/packet%s reassembled ok}\n");
+ p(ip6s_delivered, "\t{:received-local-packets/%ju} "
+ "{N:/packet%s for this host}\n");
+ p(ip6s_forward, "\t{:forwarded-packets/%ju} "
+ "{N:/packet%s forwarded}\n");
+ p(ip6s_cantforward, "\t{:packets-not-forwardable/%ju} "
+ "{N:/packet%s not forwardable}\n");
+ p(ip6s_redirectsent, "\t{:sent-redirects/%ju} "
+ "{N:/redirect%s sent}\n");
+ p(ip6s_localout, "\t{:sent-packets/%ju} "
+ "{N:/packet%s sent from this host}\n");
+ p(ip6s_rawout, "\t{:send-packets-fabricated-header/%ju} "
+ "{N:/packet%s sent with fabricated ip header}\n");
+ p(ip6s_odropped, "\t{:discard-no-mbufs/%ju} "
+ "{N:/output packet%s dropped due to no bufs, etc.}\n");
+ p(ip6s_noroute, "\t{:discard-no-route/%ju} "
+ "{N:/output packet%s discarded due to no route}\n");
+ p(ip6s_fragmented, "\t{:sent-fragments/%ju} "
+ "{N:/output datagram%s fragmented}\n");
+ p(ip6s_ofragments, "\t{:fragments-created/%ju} "
+ "{N:/fragment%s created}\n");
+ p(ip6s_cantfrag, "\t{:discard-cannot-fragment/%ju} "
+ "{N:/datagram%s that can't be fragmented}\n");
+ p(ip6s_badscope, "\t{:discard-scope-violations/%ju} "
+ "{N:/packet%s that violated scope rules}\n");
+ p(ip6s_notmember, "\t{:multicast-no-join-packets/%ju} "
+ "{N:/multicast packet%s which we don't join}\n");
+ for (first = 1, i = 0; i < IP6S_HDRCNT; i++)
+ if (ip6stat.ip6s_nxthist[i] != 0) {
+ if (first) {
+ xo_emit("\t{T:Input histogram}:\n");
+ xo_open_list("input-histogram");
+ first = 0;
+ }
+ xo_open_instance("input-histogram");
+ xo_emit("\t\t{k:name/%s}: {:count/%ju}\n", ip6nh[i],
+ (uintmax_t)ip6stat.ip6s_nxthist[i]);
+ xo_close_instance("input-histogram");
+ }
+ if (!first)
+ xo_close_list("input-histogram");
+
+ xo_open_container("mbuf-statistics");
+ xo_emit("\t{T:Mbuf statistics}:\n");
+ xo_emit("\t\t{:one-mbuf/%ju} {N:/one mbuf}\n",
+ (uintmax_t)ip6stat.ip6s_m1);
+ for (first = 1, i = 0; i < IP6S_M2MMAX; i++) {
+ char ifbuf[IFNAMSIZ];
+ if (ip6stat.ip6s_m2m[i] != 0) {
+ if (first) {
+ xo_emit("\t\t{N:two or more mbuf}:\n");
+ xo_open_list("mbuf-data");
+ first = 0;
+ }
+ xo_open_instance("mbuf-data");
+ xo_emit("\t\t\t{k:name/%s}= {:count/%ju}\n",
+ if_indextoname(i, ifbuf),
+ (uintmax_t)ip6stat.ip6s_m2m[i]);
+ xo_close_instance("mbuf-data");
+ }
+ }
+ if (!first)
+ xo_close_list("mbuf-data");
+ xo_emit("\t\t{:one-extra-mbuf/%ju} {N:one ext mbuf}\n",
+ (uintmax_t)ip6stat.ip6s_mext1);
+ xo_emit("\t\t{:two-or-more-extra-mbufs/%ju} "
+ "{N:/two or more ext mbuf}\n", (uintmax_t)ip6stat.ip6s_mext2m);
+ xo_close_container("mbuf-statistics");
+
+ p(ip6s_exthdrtoolong, "\t{:dropped-header-too-long/%ju} "
+ "{N:/packet%s whose headers are not contiguous}\n");
+ p(ip6s_nogif, "\t{:discard-tunnel-no-gif/%ju} "
+ "{N:/tunneling packet%s that can't find gif}\n");
+ p(ip6s_toomanyhdr, "\t{:dropped-too-many-headers/%ju} "
+ "{N:/packet%s discarded because of too many headers}\n");
+
+ /* for debugging source address selection */
+#define PRINT_SCOPESTAT(s,i) do {\
+ switch(i) { /* XXX hardcoding in each case */\
+ case 1:\
+ p(s, "\t\t{ke:name/interface-locals}{:count/%ju} " \
+ "{N:/interface-local%s}\n"); \
+ break;\
+ case 2:\
+ p(s,"\t\t{ke:name/link-locals}{:count/%ju} " \
+ "{N:/link-local%s}\n"); \
+ break;\
+ case 5:\
+ p(s,"\t\t{ke:name/site-locals}{:count/%ju} " \
+ "{N:/site-local%s}\n");\
+ break;\
+ case 14:\
+ p(s,"\t\t{ke:name/globals}{:count/%ju} " \
+ "{N:/global%s}\n");\
+ break;\
+ default:\
+ xo_emit("\t\t{qke:name/%#x}{:count/%ju} " \
+ "{N:/addresses scope=%#x}\n",\
+ i, (uintmax_t)ip6stat.s, i); \
+ }\
+ } while (0);
+
+ xo_open_container("source-address-selection");
+ p(ip6s_sources_none, "\t{:address-selection-failures/%ju} "
+ "{N:/failure%s of source address selection}\n");
+
+ for (first = 1, i = 0; i < IP6S_SCOPECNT; i++) {
+ if (ip6stat.ip6s_sources_sameif[i]) {
+ if (first) {
+ xo_open_list("outgoing-interface");
+ xo_emit("\tsource addresses on an outgoing "
+ "I/F\n");
+ first = 0;
+ }
+ xo_open_instance("outgoing-interface");
+ PRINT_SCOPESTAT(ip6s_sources_sameif[i], i);
+ xo_close_instance("outgoing-interface");
+ }
+ }
+ if (!first)
+ xo_close_list("outgoing-interface");
+
+ for (first = 1, i = 0; i < IP6S_SCOPECNT; i++) {
+ if (ip6stat.ip6s_sources_otherif[i]) {
+ if (first) {
+ xo_open_list("non-outgoing-interface");
+ xo_emit("\tsource addresses on a non-outgoing "
+ "I/F\n");
+ first = 0;
+ }
+ xo_open_instance("non-outgoing-interface");
+ PRINT_SCOPESTAT(ip6s_sources_otherif[i], i);
+ xo_close_instance("non-outgoing-interface");
+ }
+ }
+ if (!first)
+ xo_close_list("non-outgoing-interface");
+
+ for (first = 1, i = 0; i < IP6S_SCOPECNT; i++) {
+ if (ip6stat.ip6s_sources_samescope[i]) {
+ if (first) {
+ xo_open_list("same-source");
+ xo_emit("\tsource addresses of same scope\n");
+ first = 0;
+ }
+ xo_open_instance("same-source");
+ PRINT_SCOPESTAT(ip6s_sources_samescope[i], i);
+ xo_close_instance("same-source");
+ }
+ }
+ if (!first)
+ xo_close_list("same-source");
+
+ for (first = 1, i = 0; i < IP6S_SCOPECNT; i++) {
+ if (ip6stat.ip6s_sources_otherscope[i]) {
+ if (first) {
+ xo_open_list("different-scope");
+ xo_emit("\tsource addresses of a different "
+ "scope\n");
+ first = 0;
+ }
+ xo_open_instance("different-scope");
+ PRINT_SCOPESTAT(ip6s_sources_otherscope[i], i);
+ xo_close_instance("different-scope");
+ }
+ }
+ if (!first)
+ xo_close_list("different-scope");
+
+ for (first = 1, i = 0; i < IP6S_SCOPECNT; i++) {
+ if (ip6stat.ip6s_sources_deprecated[i]) {
+ if (first) {
+ xo_open_list("deprecated-source");
+ xo_emit("\tdeprecated source addresses\n");
+ first = 0;
+ }
+ xo_open_instance("deprecated-source");
+ PRINT_SCOPESTAT(ip6s_sources_deprecated[i], i);
+ xo_close_instance("deprecated-source");
+ }
+ }
+ if (!first)
+ xo_close_list("deprecated-source");
+
+ for (first = 1, i = 0; i < IP6S_RULESMAX; i++) {
+ if (ip6stat.ip6s_sources_rule[i]) {
+ if (first) {
+ xo_open_list("rules-applied");
+ xo_emit("\t{T:Source addresses selection "
+ "rule applied}:\n");
+ first = 0;
+ }
+ xo_open_instance("rules-applied");
+ xo_emit("\t\t{ke:name/%s}{:count/%ju} {d:name/%s}\n",
+ srcrule_str[i],
+ (uintmax_t)ip6stat.ip6s_sources_rule[i],
+ srcrule_str[i]);
+ xo_close_instance("rules-applied");
+ }
+ }
+ if (!first)
+ xo_close_list("rules-applied");
+
+ xo_close_container("source-address-selection");
+
+#undef p
+#undef p1a
+ xo_close_container(name);
+}
+
+/*
+ * Dump IPv6 per-interface statistics based on RFC 2465.
+ */
+void
+ip6_ifstats(char *ifname)
+{
+ struct in6_ifreq ifr;
+ int s;
+
+#define p(f, m) if (ifr.ifr_ifru.ifru_stat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)ifr.ifr_ifru.ifru_stat.f, \
+ plural(ifr.ifr_ifru.ifru_stat.f))
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ xo_warn("Warning: socket(AF_INET6)");
+ return;
+ }
+
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ if (ioctl(s, SIOCGIFSTAT_IN6, (char *)&ifr) < 0) {
+ if (errno != EPFNOSUPPORT)
+ xo_warn("Warning: ioctl(SIOCGIFSTAT_IN6)");
+ goto end;
+ }
+
+ xo_emit("{T:/ip6 on %s}:\n", ifr.ifr_name);
+
+ xo_open_instance("ip6-interface-statistics");
+ xo_emit("{ke:name/%s}", ifr.ifr_name);
+
+ p(ifs6_in_receive, "\t{:received-packets/%ju} "
+ "{N:/total input datagram%s}\n");
+ p(ifs6_in_hdrerr, "\t{:dropped-invalid-header/%ju} "
+ "{N:/datagram%s with invalid header received}\n");
+ p(ifs6_in_toobig, "\t{:dropped-mtu-exceeded/%ju} "
+ "{N:/datagram%s exceeded MTU received}\n");
+ p(ifs6_in_noroute, "\t{:dropped-no-route/%ju} "
+ "{N:/datagram%s with no route received}\n");
+ p(ifs6_in_addrerr, "\t{:dropped-invalid-destination/%ju} "
+ "{N:/datagram%s with invalid dst received}\n");
+ p(ifs6_in_protounknown, "\t{:dropped-unknown-protocol/%ju} "
+ "{N:/datagram%s with unknown proto received}\n");
+ p(ifs6_in_truncated, "\t{:dropped-truncated/%ju} "
+ "{N:/truncated datagram%s received}\n");
+ p(ifs6_in_discard, "\t{:dropped-discarded/%ju} "
+ "{N:/input datagram%s discarded}\n");
+ p(ifs6_in_deliver, "\t{:received-valid-packets/%ju} "
+ "{N:/datagram%s delivered to an upper layer protocol}\n");
+ p(ifs6_out_forward, "\t{:sent-forwarded/%ju} "
+ "{N:/datagram%s forwarded to this interface}\n");
+ p(ifs6_out_request, "\t{:sent-packets/%ju} "
+ "{N:/datagram%s sent from an upper layer protocol}\n");
+ p(ifs6_out_discard, "\t{:discard-packets/%ju} "
+ "{N:/total discarded output datagram%s}\n");
+ p(ifs6_out_fragok, "\t{:discard-fragments/%ju} "
+ "{N:/output datagram%s fragmented}\n");
+ p(ifs6_out_fragfail, "\t{:fragments-failed/%ju} "
+ "{N:/output datagram%s failed on fragment}\n");
+ p(ifs6_out_fragcreat, "\t{:fragments-created/%ju} "
+ "{N:/output datagram%s succeeded on fragment}\n");
+ p(ifs6_reass_reqd, "\t{:reassembly-required/%ju} "
+ "{N:/incoming datagram%s fragmented}\n");
+ p(ifs6_reass_ok, "\t{:reassembled-packets/%ju} "
+ "{N:/datagram%s reassembled}\n");
+ p(ifs6_reass_fail, "\t{:reassembly-failed/%ju} "
+ "{N:/datagram%s failed on reassembly}\n");
+ p(ifs6_in_mcast, "\t{:received-multicast/%ju} "
+ "{N:/multicast datagram%s received}\n");
+ p(ifs6_out_mcast, "\t{:sent-multicast/%ju} "
+ "{N:/multicast datagram%s sent}\n");
+
+ end:
+ xo_close_instance("ip6-interface-statistics");
+ close(s);
+
+#undef p
+}
+
+static const char *icmp6names[] = {
+ "#0",
+ "unreach",
+ "packet too big",
+ "time exceed",
+ "parameter problem",
+ "#5",
+ "#6",
+ "#7",
+ "#8",
+ "#9",
+ "#10",
+ "#11",
+ "#12",
+ "#13",
+ "#14",
+ "#15",
+ "#16",
+ "#17",
+ "#18",
+ "#19",
+ "#20",
+ "#21",
+ "#22",
+ "#23",
+ "#24",
+ "#25",
+ "#26",
+ "#27",
+ "#28",
+ "#29",
+ "#30",
+ "#31",
+ "#32",
+ "#33",
+ "#34",
+ "#35",
+ "#36",
+ "#37",
+ "#38",
+ "#39",
+ "#40",
+ "#41",
+ "#42",
+ "#43",
+ "#44",
+ "#45",
+ "#46",
+ "#47",
+ "#48",
+ "#49",
+ "#50",
+ "#51",
+ "#52",
+ "#53",
+ "#54",
+ "#55",
+ "#56",
+ "#57",
+ "#58",
+ "#59",
+ "#60",
+ "#61",
+ "#62",
+ "#63",
+ "#64",
+ "#65",
+ "#66",
+ "#67",
+ "#68",
+ "#69",
+ "#70",
+ "#71",
+ "#72",
+ "#73",
+ "#74",
+ "#75",
+ "#76",
+ "#77",
+ "#78",
+ "#79",
+ "#80",
+ "#81",
+ "#82",
+ "#83",
+ "#84",
+ "#85",
+ "#86",
+ "#87",
+ "#88",
+ "#89",
+ "#80",
+ "#91",
+ "#92",
+ "#93",
+ "#94",
+ "#95",
+ "#96",
+ "#97",
+ "#98",
+ "#99",
+ "#100",
+ "#101",
+ "#102",
+ "#103",
+ "#104",
+ "#105",
+ "#106",
+ "#107",
+ "#108",
+ "#109",
+ "#110",
+ "#111",
+ "#112",
+ "#113",
+ "#114",
+ "#115",
+ "#116",
+ "#117",
+ "#118",
+ "#119",
+ "#120",
+ "#121",
+ "#122",
+ "#123",
+ "#124",
+ "#125",
+ "#126",
+ "#127",
+ "echo",
+ "echo reply",
+ "multicast listener query",
+ "MLDv1 listener report",
+ "MLDv1 listener done",
+ "router solicitation",
+ "router advertisement",
+ "neighbor solicitation",
+ "neighbor advertisement",
+ "redirect",
+ "router renumbering",
+ "node information request",
+ "node information reply",
+ "inverse neighbor solicitation",
+ "inverse neighbor advertisement",
+ "MLDv2 listener report",
+ "#144",
+ "#145",
+ "#146",
+ "#147",
+ "#148",
+ "#149",
+ "#150",
+ "#151",
+ "#152",
+ "#153",
+ "#154",
+ "#155",
+ "#156",
+ "#157",
+ "#158",
+ "#159",
+ "#160",
+ "#161",
+ "#162",
+ "#163",
+ "#164",
+ "#165",
+ "#166",
+ "#167",
+ "#168",
+ "#169",
+ "#170",
+ "#171",
+ "#172",
+ "#173",
+ "#174",
+ "#175",
+ "#176",
+ "#177",
+ "#178",
+ "#179",
+ "#180",
+ "#181",
+ "#182",
+ "#183",
+ "#184",
+ "#185",
+ "#186",
+ "#187",
+ "#188",
+ "#189",
+ "#180",
+ "#191",
+ "#192",
+ "#193",
+ "#194",
+ "#195",
+ "#196",
+ "#197",
+ "#198",
+ "#199",
+ "#200",
+ "#201",
+ "#202",
+ "#203",
+ "#204",
+ "#205",
+ "#206",
+ "#207",
+ "#208",
+ "#209",
+ "#210",
+ "#211",
+ "#212",
+ "#213",
+ "#214",
+ "#215",
+ "#216",
+ "#217",
+ "#218",
+ "#219",
+ "#220",
+ "#221",
+ "#222",
+ "#223",
+ "#224",
+ "#225",
+ "#226",
+ "#227",
+ "#228",
+ "#229",
+ "#230",
+ "#231",
+ "#232",
+ "#233",
+ "#234",
+ "#235",
+ "#236",
+ "#237",
+ "#238",
+ "#239",
+ "#240",
+ "#241",
+ "#242",
+ "#243",
+ "#244",
+ "#245",
+ "#246",
+ "#247",
+ "#248",
+ "#249",
+ "#250",
+ "#251",
+ "#252",
+ "#253",
+ "#254",
+ "#255",
+};
+
+/*
+ * Dump ICMP6 statistics.
+ */
+void
+icmp6_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct icmp6stat icmp6stat;
+ int i, first;
+
+ if (fetch_stats("net.inet6.icmp6.stats", off, &icmp6stat,
+ sizeof(icmp6stat), kread_counters) != 0)
+ return;
+
+ xo_emit("{T:/%s}:\n", name);
+ xo_open_container(name);
+
+#define p(f, m) if (icmp6stat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)icmp6stat.f, plural(icmp6stat.f))
+#define p_5(f, m) if (icmp6stat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)icmp6stat.f)
+
+ p(icp6s_error, "\t{:icmp6-calls/%ju} "
+ "{N:/call%s to icmp6_error}\n");
+ p(icp6s_canterror, "\t{:errors-not-generated-from-message/%ju} "
+ "{N:/error%s not generated in response to an icmp6 message}\n");
+ p(icp6s_toofreq, "\t{:errors-discarded-by-rate-limitation/%ju} "
+ "{N:/error%s not generated because of rate limitation}\n");
+#define NELEM (int)(sizeof(icmp6stat.icp6s_outhist)/sizeof(icmp6stat.icp6s_outhist[0]))
+ for (first = 1, i = 0; i < NELEM; i++)
+ if (icmp6stat.icp6s_outhist[i] != 0) {
+ if (first) {
+ xo_open_list("output-histogram");
+ xo_emit("\t{T:Output histogram}:\n");
+ first = 0;
+ }
+ xo_open_instance("output-histogram");
+ xo_emit("\t\t{k:name/%s}: {:count/%ju}\n",
+ icmp6names[i],
+ (uintmax_t)icmp6stat.icp6s_outhist[i]);
+ xo_close_instance("output-histogram");
+ }
+ if (!first)
+ xo_close_list("output-histogram");
+#undef NELEM
+
+ p(icp6s_badcode, "\t{:dropped-bad-code/%ju} "
+ "{N:/message%s with bad code fields}\n");
+ p(icp6s_tooshort, "\t{:dropped-too-short/%ju} "
+ "{N:/message%s < minimum length}\n");
+ p(icp6s_checksum, "\t{:dropped-bad-checksum/%ju} "
+ "{N:/bad checksum%s}\n");
+ p(icp6s_badlen, "\t{:dropped-bad-length/%ju} "
+ "{N:/message%s with bad length}\n");
+ p(icp6s_dropped, "\t{:dropped-no-entry/%ju} "
+ "{N:/total packet%s dropped due to failed NDP resolution}\n");
+#define NELEM (int)(sizeof(icmp6stat.icp6s_inhist)/sizeof(icmp6stat.icp6s_inhist[0]))
+ for (first = 1, i = 0; i < NELEM; i++)
+ if (icmp6stat.icp6s_inhist[i] != 0) {
+ if (first) {
+ xo_open_list("input-histogram");
+ xo_emit("\t{T:Input histogram}:\n");
+ first = 0;
+ }
+ xo_open_instance("input-histogram");
+ xo_emit("\t\t{k:name/%s}: {:count/%ju}\n",
+ icmp6names[i],
+ (uintmax_t)icmp6stat.icp6s_inhist[i]);
+ xo_close_instance("input-histogram");
+ }
+ if (!first)
+ xo_close_list("input-histogram");
+#undef NELEM
+ xo_emit("\t{T:Histogram of error messages to be generated}:\n");
+ xo_open_container("errors");
+ p_5(icp6s_odst_unreach_noroute, "\t\t{:no-route/%ju} "
+ "{N:/no route}\n");
+ p_5(icp6s_odst_unreach_admin, "\t\t{:admin-prohibited/%ju} "
+ "{N:/administratively prohibited}\n");
+ p_5(icp6s_odst_unreach_beyondscope, "\t\t{:beyond-scope/%ju} "
+ "{N:/beyond scope}\n");
+ p_5(icp6s_odst_unreach_addr, "\t\t{:address-unreachable/%ju} "
+ "{N:/address unreachable}\n");
+ p_5(icp6s_odst_unreach_noport, "\t\t{:port-unreachable/%ju} "
+ "{N:/port unreachable}\n");
+ p_5(icp6s_opacket_too_big, "\t\t{:packet-too-big/%ju} "
+ "{N:/packet too big}\n");
+ p_5(icp6s_otime_exceed_transit, "\t\t{:time-exceed-transmit/%ju} "
+ "{N:/time exceed transit}\n");
+ p_5(icp6s_otime_exceed_reassembly, "\t\t{:time-exceed-reassembly/%ju} "
+ "{N:/time exceed reassembly}\n");
+ p_5(icp6s_oparamprob_header, "\t\t{:bad-header/%ju} "
+ "{N:/erroneous header field}\n");
+ p_5(icp6s_oparamprob_nextheader, "\t\t{:bad-next-header/%ju} "
+ "{N:/unrecognized next header}\n");
+ p_5(icp6s_oparamprob_option, "\t\t{:bad-option/%ju} "
+ "{N:/unrecognized option}\n");
+ p_5(icp6s_oredirect, "\t\t{:redirects/%ju} "
+ "{N:/redirect}\n");
+ p_5(icp6s_ounknown, "\t\t{:unknown/%ju} {N:unknown}\n");
+
+ p(icp6s_reflect, "\t{:reflect/%ju} "
+ "{N:/message response%s generated}\n");
+ p(icp6s_nd_toomanyopt, "\t{:too-many-nd-options/%ju} "
+ "{N:/message%s with too many ND options}\n");
+ p(icp6s_nd_badopt, "\t{:bad-nd-options/%ju} "
+ "{N:/message%s with bad ND options}\n");
+ p(icp6s_badns, "\t{:bad-neighbor-solicitation/%ju} "
+ "{N:/bad neighbor solicitation message%s}\n");
+ p(icp6s_badna, "\t{:bad-neighbor-advertisement/%ju} "
+ "{N:/bad neighbor advertisement message%s}\n");
+ p(icp6s_badrs, "\t{:bad-router-solicitation/%ju} "
+ "{N:/bad router solicitation message%s}\n");
+ p(icp6s_badra, "\t{:bad-router-advertisement/%ju} "
+ "{N:/bad router advertisement message%s}\n");
+ p(icp6s_badredirect, "\t{:bad-redirect/%ju} "
+ "{N:/bad redirect message%s}\n");
+ p(icp6s_overflowdefrtr, "\t{:default-routers-overflows/%ju} "
+ "{N:/default routers overflow%s}\n");
+ p(icp6s_overflowprfx, "\t{:prefixes-overflows/%ju} "
+ "{N:/prefix overflow%s}\n");
+ p(icp6s_overflownndp, "\t{:neighbour-entries-overflows/%ju} "
+ "{N:/neighbour entries overflow%s}\n");
+ p(icp6s_overflowredirect, "\t{:redirect-overflows/%ju} "
+ "{N:/redirect overflow%s}\n");
+ p(icp6s_invlhlim, "\t{:dropped-invalid-hop-limit/%ju} "
+ "{N:/message%s with invalid hop limit}\n");
+ xo_close_container("errors");
+ p(icp6s_pmtuchg, "\t{:path-mtu-changes/%ju} {N:/path MTU change%s}\n");
+#undef p
+#undef p_5
+ xo_close_container(name);
+}
+
+/*
+ * Dump ICMPv6 per-interface statistics based on RFC 2466.
+ */
+void
+icmp6_ifstats(char *ifname)
+{
+ struct in6_ifreq ifr;
+ int s;
+
+#define p(f, m) if (ifr.ifr_ifru.ifru_icmp6stat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)ifr.ifr_ifru.ifru_icmp6stat.f, \
+ plural(ifr.ifr_ifru.ifru_icmp6stat.f))
+#define p2(f, m) if (ifr.ifr_ifru.ifru_icmp6stat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)ifr.ifr_ifru.ifru_icmp6stat.f, \
+ pluralies(ifr.ifr_ifru.ifru_icmp6stat.f))
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ xo_warn("Warning: socket(AF_INET6)");
+ return;
+ }
+
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ if (ioctl(s, SIOCGIFSTAT_ICMP6, (char *)&ifr) < 0) {
+ if (errno != EPFNOSUPPORT)
+ xo_warn("Warning: ioctl(SIOCGIFSTAT_ICMP6)");
+ goto end;
+ }
+
+ xo_emit("{T:/icmp6 on %s}:\n", ifr.ifr_name);
+
+ xo_open_instance("icmp6-interface-statistics");
+ xo_emit("{ke:name/%s}", ifr.ifr_name);
+ p(ifs6_in_msg, "\t{:received-packets/%ju} "
+ "{N:/total input message%s}\n");
+ p(ifs6_in_error, "\t{:received-errors/%ju} "
+ "{N:/total input error message%s}\n");
+ p(ifs6_in_dstunreach, "\t{:received-destination-unreachable/%ju} "
+ "{N:/input destination unreachable error%s}\n");
+ p(ifs6_in_adminprohib, "\t{:received-admin-prohibited/%ju} "
+ "{N:/input administratively prohibited error%s}\n");
+ p(ifs6_in_timeexceed, "\t{:received-time-exceeded/%ju} "
+ "{N:/input time exceeded error%s}\n");
+ p(ifs6_in_paramprob, "\t{:received-bad-parameter/%ju} "
+ "{N:/input parameter problem error%s}\n");
+ p(ifs6_in_pkttoobig, "\t{:received-packet-too-big/%ju} "
+ "{N:/input packet too big error%s}\n");
+ p(ifs6_in_echo, "\t{:received-echo-requests/%ju} "
+ "{N:/input echo request%s}\n");
+ p2(ifs6_in_echoreply, "\t{:received-echo-replies/%ju} "
+ "{N:/input echo repl%s}\n");
+ p(ifs6_in_routersolicit, "\t{:received-router-solicitation/%ju} "
+ "{N:/input router solicitation%s}\n");
+ p(ifs6_in_routeradvert, "\t{:received-router-advertisement/%ju} "
+ "{N:/input router advertisement%s}\n");
+ p(ifs6_in_neighborsolicit, "\t{:received-neighbor-solicitation/%ju} "
+ "{N:/input neighbor solicitation%s}\n");
+ p(ifs6_in_neighboradvert, "\t{:received-neighbor-advertisement/%ju} "
+ "{N:/input neighbor advertisement%s}\n");
+ p(ifs6_in_redirect, "\t{received-redirects/%ju} "
+ "{N:/input redirect%s}\n");
+ p2(ifs6_in_mldquery, "\t{:received-mld-queries/%ju} "
+ "{N:/input MLD quer%s}\n");
+ p(ifs6_in_mldreport, "\t{:received-mld-reports/%ju} "
+ "{N:/input MLD report%s}\n");
+ p(ifs6_in_mlddone, "\t{:received-mld-done/%ju} "
+ "{N:/input MLD done%s}\n");
+
+ p(ifs6_out_msg, "\t{:sent-packets/%ju} "
+ "{N:/total output message%s}\n");
+ p(ifs6_out_error, "\t{:sent-errors/%ju} "
+ "{N:/total output error message%s}\n");
+ p(ifs6_out_dstunreach, "\t{:sent-destination-unreachable/%ju} "
+ "{N:/output destination unreachable error%s}\n");
+ p(ifs6_out_adminprohib, "\t{:sent-admin-prohibited/%ju} "
+ "{N:/output administratively prohibited error%s}\n");
+ p(ifs6_out_timeexceed, "\t{:sent-time-exceeded/%ju} "
+ "{N:/output time exceeded error%s}\n");
+ p(ifs6_out_paramprob, "\t{:sent-bad-parameter/%ju} "
+ "{N:/output parameter problem error%s}\n");
+ p(ifs6_out_pkttoobig, "\t{:sent-packet-too-big/%ju} "
+ "{N:/output packet too big error%s}\n");
+ p(ifs6_out_echo, "\t{:sent-echo-requests/%ju} "
+ "{N:/output echo request%s}\n");
+ p2(ifs6_out_echoreply, "\t{:sent-echo-replies/%ju} "
+ "{N:/output echo repl%s}\n");
+ p(ifs6_out_routersolicit, "\t{:sent-router-solicitation/%ju} "
+ "{N:/output router solicitation%s}\n");
+ p(ifs6_out_routeradvert, "\t{:sent-router-advertisement/%ju} "
+ "{N:/output router advertisement%s}\n");
+ p(ifs6_out_neighborsolicit, "\t{:sent-neighbor-solicitation/%ju} "
+ "{N:/output neighbor solicitation%s}\n");
+ p(ifs6_out_neighboradvert, "\t{:sent-neighbor-advertisement/%ju} "
+ "{N:/output neighbor advertisement%s}\n");
+ p(ifs6_out_redirect, "\t{:sent-redirects/%ju} "
+ "{N:/output redirect%s}\n");
+ p2(ifs6_out_mldquery, "\t{:sent-mld-queries/%ju} "
+ "{N:/output MLD quer%s}\n");
+ p(ifs6_out_mldreport, "\t{:sent-mld-reports/%ju} "
+ "{N:/output MLD report%s}\n");
+ p(ifs6_out_mlddone, "\t{:sent-mld-dones/%ju} "
+ "{N:/output MLD done%s}\n");
+
+end:
+ xo_close_instance("icmp6-interface-statistics");
+ close(s);
+#undef p
+}
+
+/*
+ * Dump PIM statistics structure.
+ */
+void
+pim6_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct pim6stat pim6stat;
+
+ if (fetch_stats("net.inet6.pim.stats", off, &pim6stat,
+ sizeof(pim6stat), kread) != 0)
+ return;
+
+ xo_emit("{T:/%s}:\n", name);
+ xo_open_container(name);
+
+#define p(f, m) if (pim6stat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)pim6stat.f, plural(pim6stat.f))
+
+ p(pim6s_rcv_total, "\t{:received-packets/%ju} "
+ "{N:/message%s received}\n");
+ p(pim6s_rcv_tooshort, "\t{:dropped-too-short/%ju} "
+ "{N:/message%s received with too few bytes}\n");
+ p(pim6s_rcv_badsum, "\t{:dropped-bad-checksum/%ju} "
+ "{N:/message%s received with bad checksum}\n");
+ p(pim6s_rcv_badversion, "\t{:dropped-bad-version/%ju} "
+ "{N:/message%s received with bad version}\n");
+ p(pim6s_rcv_registers, "\t{:received-registers/%ju} "
+ "{N:/register%s received}\n");
+ p(pim6s_rcv_badregisters, "\t{:received-bad-registers/%ju} "
+ "{N:/bad register%s received}\n");
+ p(pim6s_snd_registers, "\t{:sent-registers/%ju} "
+ "{N:/register%s sent}\n");
+#undef p
+ xo_close_container(name);
+}
+
+/*
+ * Dump raw ip6 statistics structure.
+ */
+void
+rip6_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct rip6stat rip6stat;
+ u_quad_t delivered;
+
+ if (fetch_stats("net.inet6.ip6.rip6stats", off, &rip6stat,
+ sizeof(rip6stat), kread_counters) != 0)
+ return;
+
+ xo_emit("{T:/%s}:\n", name);
+ xo_open_container(name);
+
+#define p(f, m) if (rip6stat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)rip6stat.f, plural(rip6stat.f))
+
+ p(rip6s_ipackets, "\t{:received-packets/%ju} "
+ "{N:/message%s received}\n");
+ p(rip6s_isum, "\t{:input-checksum-computation/%ju} "
+ "{N:/checksum calculation%s on inbound}\n");
+ p(rip6s_badsum, "\t{:received-bad-checksum/%ju} "
+ "{N:/message%s with bad checksum}\n");
+ p(rip6s_nosock, "\t{:dropped-no-socket/%ju} "
+ "{N:/message%s dropped due to no socket}\n");
+ p(rip6s_nosockmcast, "\t{:dropped-multicast-no-socket/%ju} "
+ "{N:/multicast message%s dropped due to no socket}\n");
+ p(rip6s_fullsock, "\t{:dropped-full-socket-buffer/%ju} "
+ "{N:/message%s dropped due to full socket buffers}\n");
+ delivered = rip6stat.rip6s_ipackets -
+ rip6stat.rip6s_badsum -
+ rip6stat.rip6s_nosock -
+ rip6stat.rip6s_nosockmcast -
+ rip6stat.rip6s_fullsock;
+ if (delivered || sflag <= 1)
+ xo_emit("\t{:delivered-packets/%ju} {N:/delivered}\n",
+ (uintmax_t)delivered);
+ p(rip6s_opackets, "\t{:sent-packets/%ju} "
+ "{N:/datagram%s output}\n");
+#undef p
+ xo_close_container(name);
+}
+
+/*
+ * Pretty print an Internet address (net address + port).
+ * Take numeric_addr and numeric_port into consideration.
+ */
+#define GETSERVBYPORT6(port, proto, ret)\
+{\
+ if (strcmp((proto), "tcp6") == 0)\
+ (ret) = getservbyport((int)(port), "tcp");\
+ else if (strcmp((proto), "udp6") == 0)\
+ (ret) = getservbyport((int)(port), "udp");\
+ else\
+ (ret) = getservbyport((int)(port), (proto));\
+};
+
+void
+inet6print(const char *container, struct in6_addr *in6, int port,
+ const char *proto, int numeric)
+{
+ struct servent *sp = 0;
+ char line[80], *cp;
+ int width;
+ size_t alen, plen;
+
+ if (container)
+ xo_open_container(container);
+
+ snprintf(line, sizeof(line), "%.*s.",
+ Wflag ? 39 : (Aflag && !numeric) ? 12 : 16,
+ inet6name(in6));
+ alen = strlen(line);
+ cp = line + alen;
+ if (!numeric && port)
+ GETSERVBYPORT6(port, proto, sp);
+ if (sp || port == 0)
+ snprintf(cp, sizeof(line) - alen,
+ "%.15s", sp ? sp->s_name : "*");
+ else
+ snprintf(cp, sizeof(line) - alen,
+ "%d", ntohs((u_short)port));
+ width = Wflag ? 45 : Aflag ? 18 : 22;
+
+ xo_emit("{d:target/%-*.*s} ", width, width, line);
+
+ plen = strlen(cp);
+ alen--;
+ xo_emit("{e:address/%*.*s}{e:port/%*.*s}", alen, alen, line, plen,
+ plen, cp);
+
+ if (container)
+ xo_close_container(container);
+}
+
+/*
+ * Construct an Internet address representation.
+ * If the numeric_addr has been supplied, give
+ * numeric value, otherwise try for symbolic name.
+ */
+
+char *
+inet6name(struct in6_addr *ia6)
+{
+ struct sockaddr_in6 sin6;
+ char hbuf[NI_MAXHOST], *cp;
+ static char line[NI_MAXHOST];
+ static char domain[MAXHOSTNAMELEN];
+ static int first = 1;
+ int flags, error;
+
+ if (IN6_IS_ADDR_UNSPECIFIED(ia6)) {
+ strcpy(line, "*");
+ return (line);
+ }
+ if (first && !numeric_addr) {
+ first = 0;
+ if (gethostname(domain, sizeof(domain)) == 0 &&
+ (cp = strchr(domain, '.')))
+ strlcpy(domain, cp + 1, sizeof(domain));
+ else
+ domain[0] = 0;
+ }
+ memset(&sin6, 0, sizeof(sin6));
+ memcpy(&sin6.sin6_addr, ia6, sizeof(*ia6));
+ sin6.sin6_family = AF_INET6;
+ /* XXX: ia6.s6_addr[2] can contain scopeid. */
+ in6_fillscopeid(&sin6);
+ flags = (numeric_addr) ? NI_NUMERICHOST : 0;
+ error = getnameinfo((struct sockaddr *)&sin6, sizeof(sin6), hbuf,
+ sizeof(hbuf), NULL, 0, flags);
+ if (error == 0) {
+ if ((flags & NI_NUMERICHOST) == 0 &&
+ (cp = strchr(hbuf, '.')) &&
+ !strcmp(cp + 1, domain))
+ *cp = 0;
+ strlcpy(line, hbuf, sizeof(line));
+ } else {
+ /* XXX: this should not happen. */
+ snprintf(line, sizeof(line), "%s",
+ inet_ntop(AF_INET6, (void *)&sin6.sin6_addr, ntop_buf,
+ sizeof(ntop_buf)));
+ }
+ return (line);
+}
+#endif /*INET6*/
diff --git a/usr.bin/netstat/ipsec.c b/usr.bin/netstat/ipsec.c
new file mode 100644
index 000000000000..cac42b81325f
--- /dev/null
+++ b/usr.bin/netstat/ipsec.c
@@ -0,0 +1,433 @@
+/* $KAME: ipsec.c,v 1.33 2003/07/25 09:54:32 itojun Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2005 NTT Multimedia Communications Laboratories, Inc.
+ * 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.
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ */
+/*-
+ * Copyright (C) 1995, 1996, 1997, 1998, and 1999 WIDE Project.
+ * 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.
+ * 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. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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.
+ */
+/*-
+ * Copyright (c) 1983, 1988, 1993
+ * The Regents of the University of California. 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.
+ * 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. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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/queue.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+
+#include <netinet/in.h>
+
+#ifdef IPSEC
+#include <netipsec/ipsec.h>
+#include <netipsec/ah_var.h>
+#include <netipsec/esp_var.h>
+#include <netipsec/ipcomp_var.h>
+#endif
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+
+#ifdef IPSEC
+struct val2str {
+ int val;
+ const char *str;
+};
+
+static struct val2str ipsec_ahnames[] = {
+ { SADB_AALG_NONE, "none", },
+ { SADB_AALG_SHA1HMAC, "hmac-sha1", },
+ { SADB_X_AALG_NULL, "null", },
+ { SADB_X_AALG_SHA2_256, "hmac-sha2-256", },
+ { SADB_X_AALG_SHA2_384, "hmac-sha2-384", },
+ { SADB_X_AALG_SHA2_512, "hmac-sha2-512", },
+ { SADB_X_AALG_AES_XCBC_MAC, "aes-xcbc-mac", },
+ { SADB_X_AALG_TCP_MD5, "tcp-md5", },
+ { SADB_X_AALG_AES128GMAC, "aes-gmac-128", },
+ { SADB_X_AALG_AES192GMAC, "aes-gmac-192", },
+ { SADB_X_AALG_AES256GMAC, "aes-gmac-256", },
+ { -1, NULL },
+};
+
+static struct val2str ipsec_espnames[] = {
+ { SADB_EALG_NONE, "none", },
+ { SADB_EALG_NULL, "null", },
+ { SADB_X_EALG_AESCBC, "aes-cbc", },
+ { SADB_X_EALG_AESCTR, "aes-ctr", },
+ { SADB_X_EALG_AESGCM16, "aes-gcm-16", },
+ { SADB_X_EALG_AESGMAC, "aes-gmac", },
+ { -1, NULL },
+};
+
+static struct val2str ipsec_compnames[] = {
+ { SADB_X_CALG_NONE, "none", },
+ { SADB_X_CALG_OUI, "oui", },
+ { SADB_X_CALG_DEFLATE, "deflate", },
+ { SADB_X_CALG_LZS, "lzs", },
+ { -1, NULL },
+};
+
+static void
+print_ipsecstats(const char *tag, const struct ipsecstat *ipsecstat)
+{
+ xo_open_container(tag);
+
+#define p(f, m) if (ipsecstat->f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)ipsecstat->f, plural(ipsecstat->f))
+#define p2(f, m) if (ipsecstat->f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)ipsecstat->f, plurales(ipsecstat->f))
+
+ p(ips_in_polvio, "\t{:dropped-policy-violation/%ju} "
+ "{N:/inbound packet%s violated process security policy}\n");
+ p(ips_in_nomem, "\t{:dropped-no-memory/%ju} "
+ "{N:/inbound packet%s failed due to insufficient memory}\n");
+ p(ips_in_inval, "\t{:dropped-invalid/%ju} "
+ "{N:/invalid inbound packet%s}\n");
+ p(ips_out_polvio, "\t{:discarded-policy-violation/%ju} "
+ "{N:/outbound packet%s violated process security policy}\n");
+ p(ips_out_nosa, "\t{:discarded-no-sa/%ju} "
+ "{N:/outbound packet%s with no SA available}\n");
+ p(ips_out_nomem, "\t{:discarded-no-memory/%ju} "
+ "{N:/outbound packet%s failed due to insufficient memory}\n");
+ p(ips_out_noroute, "\t{:discarded-no-route/%ju} "
+ "{N:/outbound packet%s with no route available}\n");
+ p(ips_out_inval, "\t{:discarded-invalid/%ju} "
+ "{N:/invalid outbound packet%s}\n");
+ p(ips_out_bundlesa, "\t{:send-bundled-sa/%ju} "
+ "{N:/outbound packet%s with bundled SAs}\n");
+ p(ips_spdcache_hits, "\t{:spdcache-hits/%ju} "
+ "{N:/spd cache hit%s}\n");
+ p2(ips_spdcache_misses, "\t{:spdcache-misses/%ju} "
+ "{N:/spd cache miss%s}\n");
+ p(ips_clcopied, "\t{:clusters-copied-during-clone/%ju} "
+ "{N:/cluster%s copied during clone}\n");
+ p(ips_mbinserted, "\t{:mbufs-inserted/%ju} "
+ "{N:/mbuf%s inserted during makespace}\n");
+#undef p2
+#undef p
+ xo_close_container(tag);
+}
+
+void
+ipsec_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct ipsecstat ipsecstat;
+ const char *tag;
+
+ if (strcmp(name, "ipsec6") == 0) {
+ if (fetch_stats("net.inet6.ipsec6.ipsecstats", off,&ipsecstat,
+ sizeof(ipsecstat), kread_counters) != 0)
+ return;
+ tag = "ipsec6-statistics";
+ } else {
+ if (fetch_stats("net.inet.ipsec.ipsecstats", off, &ipsecstat,
+ sizeof(ipsecstat), kread_counters) != 0)
+ return;
+ tag = "ipsec-statistics";
+ }
+
+ xo_emit("{T:/%s}:\n", name);
+
+ print_ipsecstats(tag, &ipsecstat);
+}
+
+
+static void print_ahstats(const struct ahstat *ahstat);
+static void print_espstats(const struct espstat *espstat);
+static void print_ipcompstats(const struct ipcompstat *ipcompstat);
+
+/*
+ * Dump IPSEC statistics structure.
+ */
+static void
+ipsec_hist_new(const uint64_t *hist, size_t histmax,
+ const struct val2str *name, const char *title, const char *cname)
+{
+ int first;
+ size_t proto;
+ const struct val2str *p;
+
+ first = 1;
+ for (proto = 0; proto < histmax; proto++) {
+ if (hist[proto] <= 0)
+ continue;
+ if (first) {
+ xo_open_list(cname);
+ xo_emit("\t{T:/%s histogram}:\n", title);
+ first = 0;
+ }
+ xo_open_instance(cname);
+ for (p = name; p && p->str; p++) {
+ if (p->val == (int)proto)
+ break;
+ }
+ if (p && p->str) {
+ xo_emit("\t\t{k:name}: {:count/%ju}\n", p->str,
+ (uintmax_t)hist[proto]);
+ } else {
+ xo_emit("\t\t#{k:name/%lu}: {:count/%ju}\n",
+ (unsigned long)proto, (uintmax_t)hist[proto]);
+ }
+ xo_close_instance(cname);
+ }
+ if (!first)
+ xo_close_list(cname);
+}
+
+static void
+print_ahstats(const struct ahstat *ahstat)
+{
+ xo_open_container("ah-statictics");
+
+#define p(f, n, m) if (ahstat->f || sflag <= 1) \
+ xo_emit("\t{:" n "/%ju} {N:/" m "}\n", \
+ (uintmax_t)ahstat->f, plural(ahstat->f))
+#define hist(f, n, t, c) \
+ ipsec_hist_new((f), sizeof(f)/sizeof(f[0]), (n), (t), (c))
+
+ p(ahs_hdrops, "dropped-short-header",
+ "packet%s shorter than header shows");
+ p(ahs_nopf, "dropped-bad-protocol",
+ "packet%s dropped; protocol family not supported");
+ p(ahs_notdb, "dropped-no-tdb", "packet%s dropped; no TDB");
+ p(ahs_badkcr, "dropped-bad-kcr", "packet%s dropped; bad KCR");
+ p(ahs_qfull, "dropped-queue-full", "packet%s dropped; queue full");
+ p(ahs_noxform, "dropped-no-transform",
+ "packet%s dropped; no transform");
+ p(ahs_wrap, "replay-counter-wraps", "replay counter wrap%s");
+ p(ahs_badauth, "dropped-bad-auth",
+ "packet%s dropped; bad authentication detected");
+ p(ahs_badauthl, "dropped-bad-auth-level",
+ "packet%s dropped; bad authentication length");
+ p(ahs_replay, "possile-replay-detected",
+ "possible replay packet%s detected");
+ p(ahs_input, "received-packets", "packet%s in");
+ p(ahs_output, "send-packets", "packet%s out");
+ p(ahs_invalid, "dropped-bad-tdb", "packet%s dropped; invalid TDB");
+ p(ahs_ibytes, "received-bytes", "byte%s in");
+ p(ahs_obytes, "send-bytes", "byte%s out");
+ p(ahs_toobig, "dropped-too-large",
+ "packet%s dropped; larger than IP_MAXPACKET");
+ p(ahs_pdrops, "dropped-policy-violation",
+ "packet%s blocked due to policy");
+ p(ahs_crypto, "crypto-failures", "crypto processing failure%s");
+ p(ahs_tunnel, "tunnel-failures", "tunnel sanity check failure%s");
+ hist(ahstat->ahs_hist, ipsec_ahnames,
+ "AH output", "ah-output-histogram");
+
+#undef p
+#undef hist
+ xo_close_container("ah-statictics");
+}
+
+void
+ah_stats(u_long off, const char *name, int family __unused, int proto __unused)
+{
+ struct ahstat ahstat;
+
+ if (fetch_stats("net.inet.ah.stats", off, &ahstat,
+ sizeof(ahstat), kread_counters) != 0)
+ return;
+
+ xo_emit("{T:/%s}:\n", name);
+
+ print_ahstats(&ahstat);
+}
+
+static void
+print_espstats(const struct espstat *espstat)
+{
+ xo_open_container("esp-statictics");
+#define p(f, n, m) if (espstat->f || sflag <= 1) \
+ xo_emit("\t{:" n "/%ju} {N:/" m "}\n", \
+ (uintmax_t)espstat->f, plural(espstat->f))
+#define hist(f, n, t, c) \
+ ipsec_hist_new((f), sizeof(f)/sizeof(f[0]), (n), (t), (c));
+
+ p(esps_hdrops, "dropped-short-header",
+ "packet%s shorter than header shows");
+ p(esps_nopf, "dropped-bad-protocol",
+ "packet%s dropped; protocol family not supported");
+ p(esps_notdb, "dropped-no-tdb", "packet%s dropped; no TDB");
+ p(esps_badkcr, "dropped-bad-kcr", "packet%s dropped; bad KCR");
+ p(esps_qfull, "dropped-queue-full", "packet%s dropped; queue full");
+ p(esps_noxform, "dropped-no-transform",
+ "packet%s dropped; no transform");
+ p(esps_badilen, "dropped-bad-length", "packet%s dropped; bad ilen");
+ p(esps_wrap, "replay-counter-wraps", "replay counter wrap%s");
+ p(esps_badenc, "dropped-bad-crypto",
+ "packet%s dropped; bad encryption detected");
+ p(esps_badauth, "dropped-bad-auth",
+ "packet%s dropped; bad authentication detected");
+ p(esps_replay, "possible-replay-detected",
+ "possible replay packet%s detected");
+ p(esps_input, "received-packets", "packet%s in");
+ p(esps_output, "sent-packets", "packet%s out");
+ p(esps_invalid, "dropped-bad-tdb", "packet%s dropped; invalid TDB");
+ p(esps_ibytes, "receive-bytes", "byte%s in");
+ p(esps_obytes, "sent-bytes", "byte%s out");
+ p(esps_toobig, "dropped-too-large",
+ "packet%s dropped; larger than IP_MAXPACKET");
+ p(esps_pdrops, "dropped-policy-violation",
+ "packet%s blocked due to policy");
+ p(esps_crypto, "crypto-failures", "crypto processing failure%s");
+ p(esps_tunnel, "tunnel-failures", "tunnel sanity check failure%s");
+ hist(espstat->esps_hist, ipsec_espnames,
+ "ESP output", "esp-output-histogram");
+
+#undef p
+#undef hist
+ xo_close_container("esp-statictics");
+}
+
+void
+esp_stats(u_long off, const char *name, int family __unused, int proto __unused)
+{
+ struct espstat espstat;
+
+ if (fetch_stats("net.inet.esp.stats", off, &espstat,
+ sizeof(espstat), kread_counters) != 0)
+ return;
+
+ xo_emit("{T:/%s}:\n", name);
+
+ print_espstats(&espstat);
+}
+
+static void
+print_ipcompstats(const struct ipcompstat *ipcompstat)
+{
+ xo_open_container("ipcomp-statictics");
+
+#define p(f, n, m) if (ipcompstat->f || sflag <= 1) \
+ xo_emit("\t{:" n "/%ju} {N:/" m "}\n", \
+ (uintmax_t)ipcompstat->f, plural(ipcompstat->f))
+#define hist(f, n, t, c) \
+ ipsec_hist_new((f), sizeof(f)/sizeof(f[0]), (n), (t), (c));
+
+ p(ipcomps_hdrops, "dropped-short-header",
+ "packet%s shorter than header shows");
+ p(ipcomps_nopf, "dropped-bad-protocol",
+ "packet%s dropped; protocol family not supported");
+ p(ipcomps_notdb, "dropped-no-tdb", "packet%s dropped; no TDB");
+ p(ipcomps_badkcr, "dropped-bad-kcr", "packet%s dropped; bad KCR");
+ p(ipcomps_qfull, "dropped-queue-full", "packet%s dropped; queue full");
+ p(ipcomps_noxform, "dropped-no-transform",
+ "packet%s dropped; no transform");
+ p(ipcomps_wrap, "replay-counter-wraps", "replay counter wrap%s");
+ p(ipcomps_input, "receive-packets", "packet%s in");
+ p(ipcomps_output, "sent-packets", "packet%s out");
+ p(ipcomps_invalid, "dropped-bad-tdb", "packet%s dropped; invalid TDB");
+ p(ipcomps_ibytes, "received-bytes", "byte%s in");
+ p(ipcomps_obytes, "sent-bytes", "byte%s out");
+ p(ipcomps_toobig, "dropped-too-large",
+ "packet%s dropped; larger than IP_MAXPACKET");
+ p(ipcomps_pdrops, "dropped-policy-violation",
+ "packet%s blocked due to policy");
+ p(ipcomps_crypto, "crypto-failure", "crypto processing failure%s");
+ hist(ipcompstat->ipcomps_hist, ipsec_compnames,
+ "COMP output", "comp-output-histogram");
+ p(ipcomps_threshold, "sent-uncompressed-small-packets",
+ "packet%s sent uncompressed; size < compr. algo. threshold");
+ p(ipcomps_uncompr, "sent-uncompressed-useless-packets",
+ "packet%s sent uncompressed; compression was useless");
+
+#undef p
+#undef hist
+ xo_close_container("ipcomp-statictics");
+}
+
+void
+ipcomp_stats(u_long off, const char *name, int family __unused,
+ int proto __unused)
+{
+ struct ipcompstat ipcompstat;
+
+ if (fetch_stats("net.inet.ipcomp.stats", off, &ipcompstat,
+ sizeof(ipcompstat), kread_counters) != 0)
+ return;
+
+ xo_emit("{T:/%s}:\n", name);
+
+ print_ipcompstats(&ipcompstat);
+}
+
+#endif /*IPSEC*/
diff --git a/usr.bin/netstat/main.c b/usr.bin/netstat/main.c
new file mode 100644
index 000000000000..e8f657006982
--- /dev/null
+++ b/usr.bin/netstat/main.c
@@ -0,0 +1,935 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1988, 1993
+ * Regents of the University of California. 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.
+ * 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. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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>
+#ifdef JAIL
+#include <sys/jail.h>
+#endif
+#include <sys/protosw.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+
+#include <netinet/in.h>
+
+#ifdef NETGRAPH
+#include <netgraph/ng_socket.h>
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#ifdef JAIL
+#include <jail.h>
+#endif
+#include <kvm.h>
+#include <limits.h>
+#include <netdb.h>
+#include <nlist.h>
+#include <paths.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include "netstat.h"
+#include "nl_defs.h"
+#include <libxo/xo.h>
+
+static struct protox {
+ int pr_index; /* index into nlist of cb head */
+ int pr_sindex; /* index into nlist of stat block */
+ u_char pr_wanted; /* 1 if wanted, 0 otherwise */
+ void (*pr_cblocks)(u_long, const char *, int, int);
+ /* control blocks printing routine */
+ void (*pr_stats)(u_long, const char *, int, int);
+ /* statistics printing routine */
+ void (*pr_istats)(char *); /* per/if statistics printing routine */
+ const char *pr_name; /* well-known name */
+ int pr_usesysctl; /* non-zero if we use sysctl, not kvm */
+ int pr_protocol;
+} protox[] = {
+ { -1 , N_TCPSTAT, 1, protopr,
+ tcp_stats, NULL, "tcp", 1, IPPROTO_TCP },
+ { -1 , N_UDPSTAT, 1, protopr,
+ udp_stats, NULL, "udp", 1, IPPROTO_UDP },
+#ifdef SCTP
+ { -1, N_SCTPSTAT, 1, sctp_protopr,
+ sctp_stats, NULL, "sctp", 1, IPPROTO_SCTP },
+#endif
+#ifdef SDP
+ { -1, -1, 1, protopr,
+ NULL, NULL, "sdp", 1, IPPROTO_TCP },
+#endif
+ { -1 , -1, 1, protopr,
+ divert_stats, NULL, "divert", 1, 0 },
+ { -1 , N_IPSTAT, 1, protopr,
+ ip_stats, NULL, "ip", 1, IPPROTO_RAW },
+ { -1 , N_ICMPSTAT, 1, protopr,
+ icmp_stats, NULL, "icmp", 1, IPPROTO_ICMP },
+ { -1 , N_IGMPSTAT, 1, protopr,
+ igmp_stats, NULL, "igmp", 1, IPPROTO_IGMP },
+#ifdef IPSEC
+ { -1, N_IPSEC4STAT, 1, NULL, /* keep as compat */
+ ipsec_stats, NULL, "ipsec", 1, 0},
+ { -1, N_AHSTAT, 1, NULL,
+ ah_stats, NULL, "ah", 1, 0},
+ { -1, N_ESPSTAT, 1, NULL,
+ esp_stats, NULL, "esp", 1, 0},
+ { -1, N_IPCOMPSTAT, 1, NULL,
+ ipcomp_stats, NULL, "ipcomp", 1, 0},
+#endif
+ { -1 , N_PIMSTAT, 1, protopr,
+ pim_stats, NULL, "pim", 1, IPPROTO_PIM },
+ { -1, N_CARPSTATS, 1, NULL,
+ carp_stats, NULL, "carp", 1, 0 },
+#ifdef PF
+ { -1, N_PFSYNCSTATS, 1, NULL,
+ pfsync_stats, NULL, "pfsync", 1, 0 },
+ { -1, N_PFLOWSTATS, 1, NULL,
+ pflow_stats, NULL, "pflow", 1, 0 },
+#endif
+ { -1, N_ARPSTAT, 1, NULL,
+ arp_stats, NULL, "arp", 1, 0 },
+ { -1, -1, 0, NULL,
+ NULL, NULL, NULL, 0, 0 }
+};
+
+#ifdef INET6
+static struct protox ip6protox[] = {
+ { -1 , N_TCPSTAT, 1, protopr,
+ tcp_stats, NULL, "tcp", 1, IPPROTO_TCP },
+ { -1 , N_UDPSTAT, 1, protopr,
+ udp_stats, NULL, "udp", 1, IPPROTO_UDP },
+ { -1 , N_IP6STAT, 1, protopr,
+ ip6_stats, ip6_ifstats, "ip6", 1, IPPROTO_RAW },
+ { -1 , N_ICMP6STAT, 1, protopr,
+ icmp6_stats, icmp6_ifstats, "icmp6", 1, IPPROTO_ICMPV6 },
+#ifdef SDP
+ { -1, -1, 1, protopr,
+ NULL, NULL, "sdp", 1, IPPROTO_TCP },
+#endif
+#ifdef IPSEC
+ { -1, N_IPSEC6STAT, 1, NULL,
+ ipsec_stats, NULL, "ipsec6", 1, 0 },
+#endif
+#ifdef notyet
+ { -1, N_PIM6STAT, 1, NULL,
+ pim6_stats, NULL, "pim6", 1, 0 },
+#endif
+ { -1, N_RIP6STAT, 1, NULL,
+ rip6_stats, NULL, "rip6", 1, 0 },
+ { -1, -1, 0, NULL,
+ NULL, NULL, NULL, 0, 0 }
+};
+#endif /*INET6*/
+
+#ifdef IPSEC
+static struct protox pfkeyprotox[] = {
+ { -1, N_PFKEYSTAT, 1, NULL,
+ pfkey_stats, NULL, "pfkey", 0, 0 },
+ { -1, -1, 0, NULL,
+ NULL, NULL, NULL, 0, 0 }
+};
+#endif
+
+#ifdef NETGRAPH
+static struct protox netgraphprotox[] = {
+ { N_NGSOCKLIST, -1, 1, netgraphprotopr,
+ NULL, NULL, "ctrl", 0, 0 },
+ { N_NGSOCKLIST, -1, 1, netgraphprotopr,
+ NULL, NULL, "data", 0, 0 },
+ { -1, -1, 0, NULL,
+ NULL, NULL, NULL, 0, 0 }
+};
+#endif
+
+static struct protox *protoprotox[] = {
+ protox,
+#ifdef INET6
+ ip6protox,
+#endif
+#ifdef IPSEC
+ pfkeyprotox,
+#endif
+ NULL };
+
+static void printproto(struct protox *, const char *, bool *);
+static void usage(void) __dead2;
+static struct protox *name2protox(const char *);
+static struct protox *knownname(const char *);
+
+static int kresolve_list(struct nlist *_nl);
+
+static kvm_t *kvmd;
+static char *nlistf = NULL, *memf = NULL;
+
+bool Aflag; /* show addresses of protocol control block */
+bool aflag; /* show all sockets (including servers) */
+static bool Bflag; /* show information about bpf consumers */
+bool bflag; /* show i/f total bytes in/out */
+bool cflag; /* show TCP congestion control stack */
+bool Cflag; /* show congestion control algo and vars */
+bool dflag; /* show i/f dropped packets */
+bool gflag; /* show group (multicast) routing or stats */
+bool hflag; /* show counters in human readable format */
+bool iflag; /* show interfaces */
+bool Lflag; /* show size of listen queues */
+bool mflag; /* show memory stats */
+int noutputs = 0; /* how much outputs before we exit */
+u_int numeric_addr = 0; /* show addresses numerically */
+bool numeric_port; /* show ports numerically */
+bool Oflag; /* show nhgrp objects*/
+bool oflag; /* show nexthop objects*/
+bool Pflag; /* show TCP log ID */
+static bool pflag; /* show given protocol */
+static bool Qflag; /* show netisr information */
+bool rflag; /* show routing tables (or routing stats) */
+bool Rflag; /* show flow / RSS statistics */
+int sflag; /* show protocol statistics */
+bool Wflag; /* wide display */
+bool Tflag; /* TCP Information */
+bool xflag; /* extra information, includes all socket buffer info */
+bool zflag; /* zero stats */
+
+int interval; /* repeat interval for i/f stats */
+
+char *interface; /* desired i/f for stats, or NULL for all i/fs */
+int unit; /* unit number for above */
+#ifdef JAIL
+char *jail_name; /* desired jail to operate in */
+#endif
+
+static int af; /* address family */
+int live; /* true if we are examining a live system */
+
+int
+main(int argc, char *argv[])
+{
+ struct protox *tp = NULL; /* for printing cblocks & stats */
+ int ch;
+ int fib = -1;
+ char *endptr;
+ bool first = true;
+#ifdef JAIL
+ int jid;
+#endif
+
+ af = AF_UNSPEC;
+
+ argc = xo_parse_args(argc, argv);
+ if (argc < 0)
+ exit(EXIT_FAILURE);
+
+ while ((ch = getopt(argc, argv, "46AaBbCcdF:f:ghI:ij:LlM:mN:nOoPp:Qq:RrSTsuWw:xz"))
+ != -1)
+ switch(ch) {
+ case '4':
+#ifdef INET
+ af = AF_INET;
+#else
+ xo_errx(EX_UNAVAILABLE, "IPv4 support is not compiled in");
+#endif
+ break;
+ case '6':
+#ifdef INET6
+ af = AF_INET6;
+#else
+ xo_errx(EX_UNAVAILABLE, "IPv6 support is not compiled in");
+#endif
+ break;
+ case 'A':
+ Aflag = true;
+ break;
+ case 'a':
+ aflag = true;
+ break;
+ case 'B':
+ Bflag = true;
+ break;
+ case 'b':
+ bflag = true;
+ break;
+ case 'c':
+ cflag = true;
+ break;
+ case 'C':
+ Cflag = true;
+ break;
+ case 'd':
+ dflag = true;
+ break;
+ case 'F':
+ fib = strtol(optarg, &endptr, 0);
+ if (*endptr != '\0' ||
+ (fib == 0 && (errno == EINVAL || errno == ERANGE)))
+ xo_errx(EX_DATAERR, "%s: invalid fib", optarg);
+ break;
+ case 'f':
+ if (strcmp(optarg, "inet") == 0)
+ af = AF_INET;
+#ifdef INET6
+ else if (strcmp(optarg, "inet6") == 0)
+ af = AF_INET6;
+#endif
+#ifdef IPSEC
+ else if (strcmp(optarg, "pfkey") == 0)
+ af = PF_KEY;
+#endif
+ else if (strcmp(optarg, "unix") == 0 ||
+ strcmp(optarg, "local") == 0)
+ af = AF_UNIX;
+#ifdef NETGRAPH
+ else if (strcmp(optarg, "ng") == 0
+ || strcmp(optarg, "netgraph") == 0)
+ af = AF_NETGRAPH;
+#endif
+ else if (strcmp(optarg, "link") == 0)
+ af = AF_LINK;
+ else {
+ xo_errx(EX_DATAERR, "%s: unknown address family",
+ optarg);
+ }
+ break;
+ case 'g':
+ gflag = true;
+ break;
+ case 'h':
+ hflag = true;
+ break;
+ case 'I': {
+ char *cp;
+
+ iflag = true;
+ for (cp = interface = optarg; isalpha(*cp); cp++)
+ continue;
+ unit = atoi(cp);
+ break;
+ }
+ case 'i':
+ iflag = true;
+ break;
+ case 'j':
+#ifdef JAIL
+ if (optarg == NULL)
+ usage();
+ jail_name = optarg;
+#else
+ xo_errx(EX_UNAVAILABLE, "Jail support is not compiled in");
+#endif
+ break;
+ case 'L':
+ Lflag = true;
+ break;
+ case 'M':
+ memf = optarg;
+ break;
+ case 'm':
+ mflag = true;
+ break;
+ case 'N':
+ nlistf = optarg;
+ break;
+ case 'n':
+ numeric_addr++;
+ numeric_port = true;
+ break;
+ case 'o':
+ oflag = true;
+ break;
+ case 'O':
+ Oflag = true;
+ break;
+ case 'P':
+ Pflag = true;
+ break;
+ case 'p':
+ if ((tp = name2protox(optarg)) == NULL) {
+ xo_errx(EX_DATAERR, "%s: unknown or uninstrumented "
+ "protocol", optarg);
+ }
+ pflag = true;
+ break;
+ case 'Q':
+ Qflag = true;
+ break;
+ case 'q':
+ noutputs = atoi(optarg);
+ if (noutputs != 0)
+ noutputs++;
+ break;
+ case 'r':
+ rflag = true;
+ break;
+ case 'R':
+ Rflag = true;
+ break;
+ case 's':
+ ++sflag;
+ break;
+ case 'S':
+ numeric_addr = 1;
+ break;
+ case 'u':
+ af = AF_UNIX;
+ break;
+ case 'W':
+ case 'l':
+ Wflag = true;
+ break;
+ case 'w':
+ interval = atoi(optarg);
+ iflag = true;
+ break;
+ case 'T':
+ Tflag = true;
+ break;
+ case 'x':
+ xflag = true;
+ break;
+ case 'z':
+ zflag = true;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argv += optind;
+ argc -= optind;
+
+#define BACKWARD_COMPATIBILITY
+#ifdef BACKWARD_COMPATIBILITY
+ if (*argv) {
+ if (isdigit(**argv)) {
+ interval = atoi(*argv);
+ if (interval <= 0)
+ usage();
+ ++argv;
+ iflag = true;
+ }
+ if (*argv) {
+ nlistf = *argv;
+ if (*++argv)
+ memf = *argv;
+ }
+ }
+#endif
+
+#ifdef JAIL
+ if (jail_name != NULL) {
+ jid = jail_getid(jail_name);
+ if (jid == -1)
+ xo_errx(EX_UNAVAILABLE, "Jail not found");
+ if (jail_attach(jid) != 0)
+ xo_errx(EX_UNAVAILABLE, "Cannot attach to jail");
+ }
+#endif
+
+ live = (nlistf == NULL && memf == NULL);
+ /* Load all necessary kvm symbols */
+ if (!live)
+ kresolve_list(nl);
+
+ if (xflag && Tflag)
+ xo_errx(EX_USAGE, "-x and -T are incompatible, pick one.");
+
+ if (Bflag) {
+ if (!live)
+ usage();
+ bpf_stats(interface);
+ if (xo_finish() < 0)
+ xo_err(EX_IOERR, "stdout");
+ exit(EX_OK);
+ }
+ if (mflag) {
+ if (!live) {
+ if (kread(0, NULL, 0) == 0)
+ mbpr(kvmd, nl[N_SFSTAT].n_value);
+ } else
+ mbpr(NULL, 0);
+ if (xo_finish() < 0)
+ xo_err(EX_IOERR, "stdout");
+ exit(EX_OK);
+ }
+ if (Qflag) {
+ if (!live) {
+ if (kread(0, NULL, 0) == 0)
+ netisr_stats();
+ } else
+ netisr_stats();
+ if (xo_finish() < 0)
+ xo_err(EX_IOERR, "stdout");
+ exit(EX_OK);
+ }
+#if 0
+ /*
+ * Keep file descriptors open to avoid overhead
+ * of open/close on each call to get* routines.
+ */
+ sethostent(1);
+ setnetent(1);
+#else
+ /*
+ * This does not make sense any more with DNS being default over
+ * the files. Doing a setXXXXent(1) causes a tcp connection to be
+ * used for the queries, which is slower.
+ */
+#endif
+ if (iflag && !sflag) {
+ xo_open_container("statistics");
+ xo_set_version(NETSTAT_XO_VERSION);
+ intpr(NULL, af);
+ xo_close_container("statistics");
+ if (xo_finish() < 0)
+ xo_err(EX_IOERR, "stdout");
+ exit(EX_OK);
+ }
+ if (rflag) {
+ xo_open_container("statistics");
+ xo_set_version(NETSTAT_XO_VERSION);
+ if (sflag)
+ rt_stats();
+ else
+ routepr(fib, af);
+ xo_close_container("statistics");
+ if (xo_finish() < 0)
+ xo_err(EX_IOERR, "stdout");
+ exit(EX_OK);
+ }
+ if (oflag) {
+ xo_open_container("statistics");
+ xo_set_version(NETSTAT_XO_VERSION);
+ nhops_print(fib, af);
+ xo_close_container("statistics");
+ if (xo_finish() < 0)
+ xo_err(EX_IOERR, "stdout");
+ exit(EX_OK);
+ }
+ if (Oflag) {
+ xo_open_container("statistics");
+ xo_set_version(NETSTAT_XO_VERSION);
+ nhgrp_print(fib, af);
+ xo_close_container("statistics");
+ if (xo_finish() < 0)
+ xo_err(EX_IOERR, "stdout");
+ exit(EX_OK);
+ }
+
+
+
+ if (gflag) {
+ xo_open_container("statistics");
+ xo_set_version(NETSTAT_XO_VERSION);
+ if (sflag) {
+ if (af == AF_INET || af == AF_UNSPEC)
+ mrt_stats();
+#ifdef INET6
+ if (af == AF_INET6 || af == AF_UNSPEC)
+ mrt6_stats();
+#endif
+ } else {
+ if (af == AF_INET || af == AF_UNSPEC)
+ mroutepr();
+#ifdef INET6
+ if (af == AF_INET6 || af == AF_UNSPEC)
+ mroute6pr();
+#endif
+ }
+ xo_close_container("statistics");
+ if (xo_finish() < 0)
+ xo_err(EX_IOERR, "stdout");
+ exit(EX_OK);
+ }
+
+ if (tp) {
+ xo_open_container("statistics");
+ xo_set_version(NETSTAT_XO_VERSION);
+ printproto(tp, tp->pr_name, &first);
+ if (!first)
+ xo_close_list("socket");
+ xo_close_container("statistics");
+ if (xo_finish() < 0)
+ xo_err(EX_IOERR, "stdout");
+ exit(EX_OK);
+ }
+
+ xo_open_container("statistics");
+ xo_set_version(NETSTAT_XO_VERSION);
+ if (af == AF_INET || af == AF_UNSPEC)
+ for (tp = protox; tp->pr_name; tp++)
+ printproto(tp, tp->pr_name, &first);
+#ifdef INET6
+ if (af == AF_INET6 || af == AF_UNSPEC)
+ for (tp = ip6protox; tp->pr_name; tp++)
+ printproto(tp, tp->pr_name, &first);
+#endif /*INET6*/
+#ifdef IPSEC
+ if (af == PF_KEY || af == AF_UNSPEC)
+ for (tp = pfkeyprotox; tp->pr_name; tp++)
+ printproto(tp, tp->pr_name, &first);
+#endif /*IPSEC*/
+#ifdef NETGRAPH
+ if (af == AF_NETGRAPH || af == AF_UNSPEC)
+ for (tp = netgraphprotox; tp->pr_name; tp++)
+ printproto(tp, tp->pr_name, &first);
+#endif /* NETGRAPH */
+ if ((af == AF_UNIX || af == AF_UNSPEC) && !sflag)
+ unixpr(nl[N_UNP_COUNT].n_value, nl[N_UNP_GENCNT].n_value,
+ nl[N_UNP_DHEAD].n_value, nl[N_UNP_SHEAD].n_value,
+ nl[N_UNP_SPHEAD].n_value, &first);
+
+ if (!first)
+ xo_close_list("socket");
+ xo_close_container("statistics");
+ if (xo_finish() < 0)
+ xo_err(EX_IOERR, "stdout");
+ exit(EX_OK);
+}
+
+static int
+fetch_stats_internal(const char *sysctlname, u_long off, void *stats,
+ size_t len, kreadfn_t kreadfn, int zero)
+{
+ int error;
+
+ if (live) {
+ memset(stats, 0, len);
+ if (zero)
+ error = sysctlbyname(sysctlname, NULL, NULL, stats,
+ len);
+ else
+ error = sysctlbyname(sysctlname, stats, &len, NULL, 0);
+ if (error == -1 && errno != ENOENT)
+ xo_warn("sysctl %s", sysctlname);
+ } else {
+ if (off == 0)
+ return (1);
+ error = kreadfn(off, stats, len);
+ }
+ return (error);
+}
+
+int
+fetch_stats(const char *sysctlname, u_long off, void *stats,
+ size_t len, kreadfn_t kreadfn)
+{
+
+ return (fetch_stats_internal(sysctlname, off, stats, len, kreadfn,
+ zflag));
+}
+
+int
+fetch_stats_ro(const char *sysctlname, u_long off, void *stats,
+ size_t len, kreadfn_t kreadfn)
+{
+
+ return (fetch_stats_internal(sysctlname, off, stats, len, kreadfn, 0));
+}
+
+/*
+ * Print out protocol statistics or control blocks (per sflag).
+ * If the interface was not specifically requested, and the symbol
+ * is not in the namelist, ignore this one.
+ */
+static void
+printproto(struct protox *tp, const char *name, bool *first)
+{
+ void (*pr)(u_long, const char *, int, int);
+ u_long off;
+ bool doingdblocks = false;
+
+ if (sflag) {
+ if (iflag) {
+ if (tp->pr_istats)
+ intpr(tp->pr_istats, af);
+ else if (pflag)
+ xo_message("%s: no per-interface stats routine",
+ tp->pr_name);
+ return;
+ } else {
+ pr = tp->pr_stats;
+ if (!pr) {
+ if (pflag)
+ xo_message("%s: no stats routine",
+ tp->pr_name);
+ return;
+ }
+ if (tp->pr_usesysctl && live)
+ off = 0;
+ else if (tp->pr_sindex < 0) {
+ if (pflag)
+ xo_message("%s: stats routine doesn't "
+ "work on cores", tp->pr_name);
+ return;
+ } else
+ off = nl[tp->pr_sindex].n_value;
+ }
+ } else {
+ doingdblocks = true;
+ pr = tp->pr_cblocks;
+ if (!pr) {
+ if (pflag)
+ xo_message("%s: no PCB routine", tp->pr_name);
+ return;
+ }
+ if (tp->pr_usesysctl && live)
+ off = 0;
+ else if (tp->pr_index < 0) {
+ if (pflag)
+ xo_message("%s: PCB routine doesn't work on "
+ "cores", tp->pr_name);
+ return;
+ } else
+ off = nl[tp->pr_index].n_value;
+ }
+ if (pr != NULL && (off || (live && tp->pr_usesysctl) ||
+ af != AF_UNSPEC)) {
+ if (doingdblocks && *first) {
+ xo_open_list("socket");
+ *first = false;
+ }
+
+ (*pr)(off, name, af, tp->pr_protocol);
+ }
+}
+
+static int
+kvmd_init(void)
+{
+ char errbuf[_POSIX2_LINE_MAX];
+
+ if (kvmd != NULL)
+ return (0);
+
+ kvmd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY, errbuf);
+ if (kvmd == NULL) {
+ xo_warnx("kvm not available: %s", errbuf);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Resolve symbol list, return 0 on success.
+ */
+static int
+kresolve_list(struct nlist *_nl)
+{
+
+ if ((kvmd == NULL) && (kvmd_init() != 0))
+ return (-1);
+
+ if (_nl[0].n_type != 0)
+ return (0);
+
+ if (kvm_nlist(kvmd, _nl) < 0) {
+ if (nlistf)
+ xo_errx(EX_UNAVAILABLE, "%s: kvm_nlist: %s", nlistf,
+ kvm_geterr(kvmd));
+ else
+ xo_errx(EX_UNAVAILABLE, "kvm_nlist: %s", kvm_geterr(kvmd));
+ }
+
+ return (0);
+}
+
+/*
+ * Wrapper of kvm_dpcpu_setcpu().
+ */
+void
+kset_dpcpu(u_int cpuid)
+{
+
+ if ((kvmd == NULL) && (kvmd_init() != 0))
+ xo_errx(EX_UNAVAILABLE, "%s: kvm is not available", __func__);
+
+ if (kvm_dpcpu_setcpu(kvmd, cpuid) < 0)
+ xo_errx(EX_UNAVAILABLE, "%s: kvm_dpcpu_setcpu(%u): %s", __func__,
+ cpuid, kvm_geterr(kvmd));
+ return;
+}
+
+/*
+ * Read kernel memory, return 0 on success.
+ */
+int
+kread(u_long addr, void *buf, size_t size)
+{
+
+ if (kvmd_init() < 0)
+ return (-1);
+
+ if (!buf)
+ return (0);
+ if (kvm_read(kvmd, addr, buf, size) != (ssize_t)size) {
+ xo_warnx("%s", kvm_geterr(kvmd));
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Read single counter(9).
+ */
+uint64_t
+kread_counter(u_long addr)
+{
+
+ if (kvmd_init() < 0)
+ return (-1);
+
+ return (kvm_counter_u64_fetch(kvmd, addr));
+}
+
+/*
+ * Read an array of N counters in kernel memory into array of N uint64_t's.
+ */
+int
+kread_counters(u_long addr, void *buf, size_t size)
+{
+ uint64_t *c;
+ u_long *counters;
+ size_t i, n;
+
+ if (kvmd_init() < 0)
+ return (-1);
+
+ if (size % sizeof(uint64_t) != 0) {
+ xo_warnx("kread_counters: invalid counter set size");
+ return (-1);
+ }
+
+ n = size / sizeof(uint64_t);
+ if ((counters = malloc(n * sizeof(u_long))) == NULL)
+ xo_err(EX_OSERR, "malloc");
+ if (kread(addr, counters, n * sizeof(u_long)) < 0) {
+ free(counters);
+ return (-1);
+ }
+
+ c = buf;
+ for (i = 0; i < n; i++)
+ c[i] = kvm_counter_u64_fetch(kvmd, counters[i]);
+
+ free(counters);
+ return (0);
+}
+
+const char *
+plural(uintmax_t n)
+{
+ return (n != 1 ? "s" : "");
+}
+
+const char *
+plurales(uintmax_t n)
+{
+ return (n != 1 ? "es" : "");
+}
+
+const char *
+pluralies(uintmax_t n)
+{
+ return (n != 1 ? "ies" : "y");
+}
+
+/*
+ * Find the protox for the given "well-known" name.
+ */
+static struct protox *
+knownname(const char *name)
+{
+ struct protox **tpp, *tp;
+
+ for (tpp = protoprotox; *tpp; tpp++)
+ for (tp = *tpp; tp->pr_name; tp++)
+ if (strcmp(tp->pr_name, name) == 0)
+ return (tp);
+ return (NULL);
+}
+
+/*
+ * Find the protox corresponding to name.
+ */
+static struct protox *
+name2protox(const char *name)
+{
+ struct protox *tp;
+ char **alias; /* alias from p->aliases */
+ struct protoent *p;
+
+ /*
+ * Try to find the name in the list of "well-known" names. If that
+ * fails, check if name is an alias for an Internet protocol.
+ */
+ if ((tp = knownname(name)) != NULL)
+ return (tp);
+
+ setprotoent(1); /* make protocol lookup cheaper */
+ while ((p = getprotoent()) != NULL) {
+ /* assert: name not same as p->name */
+ for (alias = p->p_aliases; *alias; alias++)
+ if (strcmp(name, *alias) == 0) {
+ endprotoent();
+ return (knownname(p->p_name));
+ }
+ }
+ endprotoent();
+ return (NULL);
+}
+
+static void
+usage(void)
+{
+ xo_error("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+"usage: netstat [-j jail] [-46AaCcLnRSTWx] [-f protocol_family | -p protocol]\n"
+" [-M core] [-N system]",
+" netstat [-j jail] -i | -I interface [-46abdhnW] [-f address_family]\n"
+" [-M core] [-N system]",
+" netstat [-j jail] -w wait [-I interface] [-46d] [-M core] [-N system]\n"
+" [-q howmany]",
+" netstat [-j jail] -s [-46sz] [-f protocol_family | -p protocol]\n"
+" [-M core] [-N system]",
+" netstat [-j jail] -i | -I interface -s [-46s]\n"
+" [-f protocol_family | -p protocol] [-M core] [-N system]",
+" netstat [-j jail] -m [-M core] [-N system]",
+" netstat [-j jail] -B [-z] [-I interface]",
+" netstat [-j jail] -r [-46AnW] [-F fibnum] [-f address_family]\n"
+" [-M core] [-N system]",
+" netstat [-j jail] -rs [-s] [-M core] [-N system]",
+" netstat [-j jail] -g [-46W] [-f address_family] [-M core] [-N system]",
+" netstat [-j jail] -gs [-46s] [-f address_family] [-M core] [-N system]",
+" netstat [-j jail] -Q");
+ exit(EX_USAGE);
+}
diff --git a/usr.bin/netstat/mbuf.c b/usr.bin/netstat/mbuf.c
new file mode 100644
index 000000000000..9a43e0115223
--- /dev/null
+++ b/usr.bin/netstat/mbuf.c
@@ -0,0 +1,354 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 1983, 1988, 1993
+ * The Regents of the University of California.
+ * Copyright (c) 2005 Robert N. M. Watson
+ * 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.
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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/mbuf.h>
+#include <sys/protosw.h>
+#include <sys/sf_buf.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+
+#include <kvm.h>
+#include <memstat.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+
+/*
+ * Print mbuf statistics.
+ */
+void
+mbpr(void *kvmd, u_long mbaddr)
+{
+ struct memory_type_list *mtlp;
+ struct memory_type *mtp;
+ uintmax_t mbuf_count, mbuf_bytes, mbuf_free, mbuf_failures, mbuf_size;
+ uintmax_t mbuf_sleeps;
+ uintmax_t cluster_count, cluster_limit, cluster_free;
+ uintmax_t cluster_failures, cluster_size, cluster_sleeps;
+ uintmax_t packet_count, packet_bytes, packet_free, packet_failures;
+ uintmax_t packet_sleeps;
+ uintmax_t tag_bytes;
+ uintmax_t jumbop_count, jumbop_limit, jumbop_free;
+ uintmax_t jumbop_failures, jumbop_sleeps, jumbop_size;
+ uintmax_t jumbo9_count, jumbo9_limit, jumbo9_free;
+ uintmax_t jumbo9_failures, jumbo9_sleeps, jumbo9_size;
+ uintmax_t jumbo16_count, jumbo16_limit, jumbo16_free;
+ uintmax_t jumbo16_failures, jumbo16_sleeps, jumbo16_size;
+ uintmax_t bytes_inuse, bytes_incache, bytes_total;
+ int nsfbufs, nsfbufspeak, nsfbufsused;
+ struct sfstat sfstat;
+ size_t mlen;
+ int error;
+
+ mtlp = memstat_mtl_alloc();
+ if (mtlp == NULL) {
+ xo_warn("memstat_mtl_alloc");
+ return;
+ }
+
+ /*
+ * Use memstat_*_all() because some mbuf-related memory is in uma(9),
+ * and some malloc(9).
+ */
+ if (live) {
+ if (memstat_sysctl_all(mtlp, 0) < 0) {
+ xo_warnx("memstat_sysctl_all: %s",
+ memstat_strerror(memstat_mtl_geterror(mtlp)));
+ goto out;
+ }
+ } else {
+ if (memstat_kvm_all(mtlp, kvmd) < 0) {
+ error = memstat_mtl_geterror(mtlp);
+ if (error == MEMSTAT_ERROR_KVM)
+ xo_warnx("memstat_kvm_all: %s",
+ kvm_geterr(kvmd));
+ else
+ xo_warnx("memstat_kvm_all: %s",
+ memstat_strerror(error));
+ goto out;
+ }
+ }
+
+ mtp = memstat_mtl_find(mtlp, ALLOCATOR_UMA, MBUF_MEM_NAME);
+ if (mtp == NULL) {
+ xo_warnx("memstat_mtl_find: zone %s not found", MBUF_MEM_NAME);
+ goto out;
+ }
+ mbuf_count = memstat_get_count(mtp);
+ mbuf_bytes = memstat_get_bytes(mtp);
+ mbuf_free = memstat_get_free(mtp);
+ mbuf_failures = memstat_get_failures(mtp);
+ mbuf_sleeps = memstat_get_sleeps(mtp);
+ mbuf_size = memstat_get_size(mtp);
+
+ mtp = memstat_mtl_find(mtlp, ALLOCATOR_UMA, MBUF_PACKET_MEM_NAME);
+ if (mtp == NULL) {
+ xo_warnx("memstat_mtl_find: zone %s not found",
+ MBUF_PACKET_MEM_NAME);
+ goto out;
+ }
+ packet_count = memstat_get_count(mtp);
+ packet_bytes = memstat_get_bytes(mtp);
+ packet_free = memstat_get_free(mtp);
+ packet_sleeps = memstat_get_sleeps(mtp);
+ packet_failures = memstat_get_failures(mtp);
+
+ mtp = memstat_mtl_find(mtlp, ALLOCATOR_UMA, MBUF_CLUSTER_MEM_NAME);
+ if (mtp == NULL) {
+ xo_warnx("memstat_mtl_find: zone %s not found",
+ MBUF_CLUSTER_MEM_NAME);
+ goto out;
+ }
+ cluster_count = memstat_get_count(mtp);
+ cluster_limit = memstat_get_countlimit(mtp);
+ cluster_free = memstat_get_free(mtp);
+ cluster_failures = memstat_get_failures(mtp);
+ cluster_sleeps = memstat_get_sleeps(mtp);
+ cluster_size = memstat_get_size(mtp);
+
+ mtp = memstat_mtl_find(mtlp, ALLOCATOR_MALLOC, MBUF_TAG_MEM_NAME);
+ if (mtp == NULL) {
+ xo_warnx("memstat_mtl_find: malloc type %s not found",
+ MBUF_TAG_MEM_NAME);
+ goto out;
+ }
+ tag_bytes = memstat_get_bytes(mtp);
+
+ mtp = memstat_mtl_find(mtlp, ALLOCATOR_UMA, MBUF_JUMBOP_MEM_NAME);
+ if (mtp == NULL) {
+ xo_warnx("memstat_mtl_find: zone %s not found",
+ MBUF_JUMBOP_MEM_NAME);
+ goto out;
+ }
+ jumbop_count = memstat_get_count(mtp);
+ jumbop_limit = memstat_get_countlimit(mtp);
+ jumbop_free = memstat_get_free(mtp);
+ jumbop_failures = memstat_get_failures(mtp);
+ jumbop_sleeps = memstat_get_sleeps(mtp);
+ jumbop_size = memstat_get_size(mtp);
+
+ mtp = memstat_mtl_find(mtlp, ALLOCATOR_UMA, MBUF_JUMBO9_MEM_NAME);
+ if (mtp == NULL) {
+ xo_warnx("memstat_mtl_find: zone %s not found",
+ MBUF_JUMBO9_MEM_NAME);
+ goto out;
+ }
+ jumbo9_count = memstat_get_count(mtp);
+ jumbo9_limit = memstat_get_countlimit(mtp);
+ jumbo9_free = memstat_get_free(mtp);
+ jumbo9_failures = memstat_get_failures(mtp);
+ jumbo9_sleeps = memstat_get_sleeps(mtp);
+ jumbo9_size = memstat_get_size(mtp);
+
+ mtp = memstat_mtl_find(mtlp, ALLOCATOR_UMA, MBUF_JUMBO16_MEM_NAME);
+ if (mtp == NULL) {
+ xo_warnx("memstat_mtl_find: zone %s not found",
+ MBUF_JUMBO16_MEM_NAME);
+ goto out;
+ }
+ jumbo16_count = memstat_get_count(mtp);
+ jumbo16_limit = memstat_get_countlimit(mtp);
+ jumbo16_free = memstat_get_free(mtp);
+ jumbo16_failures = memstat_get_failures(mtp);
+ jumbo16_sleeps = memstat_get_sleeps(mtp);
+ jumbo16_size = memstat_get_size(mtp);
+
+ xo_open_container("mbuf-statistics");
+
+ xo_emit("{:mbuf-current/%ju}/{:mbuf-cache/%ju}/{:mbuf-total/%ju} "
+ "{N:mbufs in use (current\\/cache\\/total)}\n",
+ mbuf_count + packet_count, mbuf_free + packet_free,
+ mbuf_count + packet_count + mbuf_free + packet_free);
+
+ xo_emit("{:cluster-current/%ju}/{:cluster-cache/%ju}/"
+ "{:cluster-total/%ju}/{:cluster-max/%ju} "
+ "{N:mbuf clusters in use (current\\/cache\\/total\\/max)}\n",
+ cluster_count - packet_free, cluster_free + packet_free,
+ cluster_count + cluster_free, cluster_limit);
+
+ xo_emit("{:packet-count/%ju}/{:packet-free/%ju} "
+ "{N:mbuf+clusters out of packet secondary zone in use "
+ "(current\\/cache)}\n",
+ packet_count, packet_free);
+
+ xo_emit("{:jumbo-count/%ju}/{:jumbo-cache/%ju}/{:jumbo-total/%ju}/"
+ "{:jumbo-max/%ju} {:jumbo-page-size/%ju}{U:k} {N:(page size)} "
+ "{N:jumbo clusters in use (current\\/cache\\/total\\/max)}\n",
+ jumbop_count, jumbop_free, jumbop_count + jumbop_free,
+ jumbop_limit, jumbop_size / 1024);
+
+ xo_emit("{:jumbo9-count/%ju}/{:jumbo9-cache/%ju}/"
+ "{:jumbo9-total/%ju}/{:jumbo9-max/%ju} "
+ "{N:9k jumbo clusters in use (current\\/cache\\/total\\/max)}\n",
+ jumbo9_count, jumbo9_free, jumbo9_count + jumbo9_free,
+ jumbo9_limit);
+
+ xo_emit("{:jumbo16-count/%ju}/{:jumbo16-cache/%ju}/"
+ "{:jumbo16-total/%ju}/{:jumbo16-limit/%ju} "
+ "{N:16k jumbo clusters in use (current\\/cache\\/total\\/max)}\n",
+ jumbo16_count, jumbo16_free, jumbo16_count + jumbo16_free,
+ jumbo16_limit);
+
+#if 0
+ xo_emit("{:tag-count/%ju} {N:mbuf tags in use}\n", tag_count);
+#endif
+
+ /*-
+ * Calculate in-use bytes as:
+ * - straight mbuf memory
+ * - mbuf memory in packets
+ * - the clusters attached to packets
+ * - and the rest of the non-packet-attached clusters.
+ * - m_tag memory
+ * This avoids counting the clusters attached to packets in the cache.
+ * This currently excludes sf_buf space.
+ */
+ bytes_inuse =
+ mbuf_bytes + /* straight mbuf memory */
+ packet_bytes + /* mbufs in packets */
+ (packet_count * cluster_size) + /* clusters in packets */
+ /* other clusters */
+ ((cluster_count - packet_count - packet_free) * cluster_size) +
+ tag_bytes +
+ (jumbop_count * jumbop_size) + /* jumbo clusters */
+ (jumbo9_count * jumbo9_size) +
+ (jumbo16_count * jumbo16_size);
+
+ /*
+ * Calculate in-cache bytes as:
+ * - cached straught mbufs
+ * - cached packet mbufs
+ * - cached packet clusters
+ * - cached straight clusters
+ * This currently excludes sf_buf space.
+ */
+ bytes_incache =
+ (mbuf_free * mbuf_size) + /* straight free mbufs */
+ (packet_free * mbuf_size) + /* mbufs in free packets */
+ (packet_free * cluster_size) + /* clusters in free packets */
+ (cluster_free * cluster_size) + /* free clusters */
+ (jumbop_free * jumbop_size) + /* jumbo clusters */
+ (jumbo9_free * jumbo9_size) +
+ (jumbo16_free * jumbo16_size);
+
+ /*
+ * Total is bytes in use + bytes in cache. This doesn't take into
+ * account various other misc data structures, overhead, etc, but
+ * gives the user something useful despite that.
+ */
+ bytes_total = bytes_inuse + bytes_incache;
+
+ xo_emit("{:bytes-in-use/%ju}{U:K}/{:bytes-in-cache/%ju}{U:K}/"
+ "{:bytes-total/%ju}{U:K} "
+ "{N:bytes allocated to network (current\\/cache\\/total)}\n",
+ bytes_inuse / 1024, bytes_incache / 1024, bytes_total / 1024);
+
+ xo_emit("{:mbuf-failures/%ju}/{:cluster-failures/%ju}/"
+ "{:packet-failures/%ju} {N:requests for mbufs denied "
+ "(mbufs\\/clusters\\/mbuf+clusters)}\n",
+ mbuf_failures, cluster_failures, packet_failures);
+ xo_emit("{:mbuf-sleeps/%ju}/{:cluster-sleeps/%ju}/{:packet-sleeps/%ju} "
+ "{N:requests for mbufs delayed "
+ "(mbufs\\/clusters\\/mbuf+clusters)}\n",
+ mbuf_sleeps, cluster_sleeps, packet_sleeps);
+
+ xo_emit("{:jumbop-sleeps/%ju}/{:jumbo9-sleeps/%ju}/"
+ "{:jumbo16-sleeps/%ju} {N:/requests for jumbo clusters delayed "
+ "(%juk\\/9k\\/16k)}\n",
+ jumbop_sleeps, jumbo9_sleeps, jumbo16_sleeps, jumbop_size / 1024);
+ xo_emit("{:jumbop-failures/%ju}/{:jumbo9-failures/%ju}/"
+ "{:jumbo16-failures/%ju} {N:/requests for jumbo clusters denied "
+ "(%juk\\/9k\\/16k)}\n",
+ jumbop_failures, jumbo9_failures, jumbo16_failures,
+ jumbop_size / 1024);
+
+ mlen = sizeof(nsfbufs);
+ if (live &&
+ sysctlbyname("kern.ipc.nsfbufs", &nsfbufs, &mlen, NULL, 0) == 0 &&
+ sysctlbyname("kern.ipc.nsfbufsused", &nsfbufsused, &mlen,
+ NULL, 0) == 0 &&
+ sysctlbyname("kern.ipc.nsfbufspeak", &nsfbufspeak, &mlen,
+ NULL, 0) == 0)
+ xo_emit("{:nsfbufs-current/%d}/{:nsfbufs-peak/%d}/"
+ "{:nsfbufs/%d} "
+ "{N:sfbufs in use (current\\/peak\\/max)}\n",
+ nsfbufsused, nsfbufspeak, nsfbufs);
+
+ if (fetch_stats("kern.ipc.sfstat", mbaddr, &sfstat, sizeof(sfstat),
+ kread_counters) != 0)
+ goto out;
+
+ xo_emit("{:sendfile-syscalls/%ju} {N:sendfile syscalls}\n",
+ (uintmax_t)sfstat.sf_syscalls);
+ xo_emit("{:sendfile-no-io/%ju} "
+ "{N:sendfile syscalls completed without I\\/O request}\n",
+ (uintmax_t)sfstat.sf_noiocnt);
+ xo_emit("{:sendfile-io-count/%ju} "
+ "{N:requests for I\\/O initiated by sendfile}\n",
+ (uintmax_t)sfstat.sf_iocnt);
+ xo_emit("{:sendfile-pages-sent/%ju} "
+ "{N:pages read by sendfile as part of a request}\n",
+ (uintmax_t)sfstat.sf_pages_read);
+ xo_emit("{:sendfile-pages-valid/%ju} "
+ "{N:pages were valid at time of a sendfile request}\n",
+ (uintmax_t)sfstat.sf_pages_valid);
+ xo_emit("{:sendfile-pages-bogus/%ju} "
+ "{N:pages were valid and substituted to bogus page}\n",
+ (uintmax_t)sfstat.sf_pages_bogus);
+ xo_emit("{:sendfile-requested-readahead/%ju} "
+ "{N:pages were requested for read ahead by applications}\n",
+ (uintmax_t)sfstat.sf_rhpages_requested);
+ xo_emit("{:sendfile-readahead/%ju} "
+ "{N:pages were read ahead by sendfile}\n",
+ (uintmax_t)sfstat.sf_rhpages_read);
+ xo_emit("{:sendfile-busy-encounters/%ju} "
+ "{N:times sendfile encountered an already busy page}\n",
+ (uintmax_t)sfstat.sf_busy);
+ xo_emit("{:sfbufs-alloc-failed/%ju} {N:requests for sfbufs denied}\n",
+ (uintmax_t)sfstat.sf_allocfail);
+ xo_emit("{:sfbufs-alloc-wait/%ju} {N:requests for sfbufs delayed}\n",
+ (uintmax_t)sfstat.sf_allocwait);
+out:
+ xo_close_container("mbuf-statistics");
+ memstat_mtl_free(mtlp);
+}
diff --git a/usr.bin/netstat/mroute.c b/usr.bin/netstat/mroute.c
new file mode 100644
index 000000000000..1577a6ae73ac
--- /dev/null
+++ b/usr.bin/netstat/mroute.c
@@ -0,0 +1,462 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 1989 Stephen Deering
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Stephen Deering of Stanford University.
+ *
+ * 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.
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+/*
+ * Print multicast routing structures and statistics.
+ *
+ * MROUTING 1.0
+ */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+#include <sys/protosw.h>
+#include <sys/mbuf.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/igmp.h>
+#include <net/route.h>
+
+#define _NETSTAT 1
+#include <netinet/ip_mroute.h>
+#undef _NETSTAT_
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+#include "nl_defs.h"
+
+static void print_bw_meter(struct bw_meter *, int *);
+static void print_mfc(struct mfc *, int, int *);
+
+static void
+print_bw_meter(struct bw_meter *bw_meter, int *banner_printed)
+{
+ char s1[256], s2[256], s3[256];
+ struct timeval now, end, delta;
+
+ gettimeofday(&now, NULL);
+
+ if (! *banner_printed) {
+ xo_open_list("bandwidth-meter");
+ xo_emit(" {T:Bandwidth Meters}\n");
+ xo_emit(" {T:/%-30s}", "Measured(Start|Packets|Bytes)");
+ xo_emit(" {T:/%s}", "Type");
+ xo_emit(" {T:/%-30s}", "Thresh(Interval|Packets|Bytes)");
+ xo_emit(" {T:Remain}");
+ xo_emit("\n");
+ *banner_printed = 1;
+ }
+
+ xo_open_instance("bandwidth-meter");
+
+ /* The measured values */
+ if (bw_meter->bm_flags & BW_METER_UNIT_PACKETS) {
+ snprintf(s1, sizeof(s1), "%ju",
+ (uintmax_t)bw_meter->bm_measured.b_packets);
+ xo_emit("{e:measured-packets/%ju}",
+ (uintmax_t)bw_meter->bm_measured.b_packets);
+ } else
+ strcpy(s1, "?");
+ if (bw_meter->bm_flags & BW_METER_UNIT_BYTES) {
+ snprintf(s2, sizeof(s2), "%ju",
+ (uintmax_t)bw_meter->bm_measured.b_bytes);
+ xo_emit("{e:measured-bytes/%ju}",
+ (uintmax_t)bw_meter->bm_measured.b_bytes);
+ } else
+ strcpy(s2, "?");
+ xo_emit(" {[:-30}{:start-time/%lu.%06lu}|{q:measured-packets/%s}"
+ "|{q:measured-bytes%s}{]:}",
+ (u_long)bw_meter->bm_start_time.tv_sec,
+ (u_long)bw_meter->bm_start_time.tv_usec, s1, s2);
+
+ /* The type of entry */
+ xo_emit(" {t:type/%-3s}", (bw_meter->bm_flags & BW_METER_GEQ) ? ">=" :
+ (bw_meter->bm_flags & BW_METER_LEQ) ? "<=" : "?");
+
+ /* The threshold values */
+ if (bw_meter->bm_flags & BW_METER_UNIT_PACKETS) {
+ snprintf(s1, sizeof(s1), "%ju",
+ (uintmax_t)bw_meter->bm_threshold.b_packets);
+ xo_emit("{e:threshold-packets/%ju}",
+ (uintmax_t)bw_meter->bm_threshold.b_packets);
+ } else
+ strcpy(s1, "?");
+ if (bw_meter->bm_flags & BW_METER_UNIT_BYTES) {
+ snprintf(s2, sizeof(s2), "%ju",
+ (uintmax_t)bw_meter->bm_threshold.b_bytes);
+ xo_emit("{e:threshold-bytes/%ju}",
+ (uintmax_t)bw_meter->bm_threshold.b_bytes);
+ } else
+ strcpy(s2, "?");
+
+ xo_emit(" {[:-30}{:threshold-time/%lu.%06lu}|{q:threshold-packets/%s}"
+ "|{q:threshold-bytes%s}{]:}",
+ (u_long)bw_meter->bm_threshold.b_time.tv_sec,
+ (u_long)bw_meter->bm_threshold.b_time.tv_usec, s1, s2);
+
+ /* Remaining time */
+ timeradd(&bw_meter->bm_start_time,
+ &bw_meter->bm_threshold.b_time, &end);
+ if (timercmp(&now, &end, <=)) {
+ timersub(&end, &now, &delta);
+ snprintf(s3, sizeof(s3), "%lu.%06lu",
+ (u_long)delta.tv_sec,
+ (u_long)delta.tv_usec);
+ } else {
+ /* Negative time */
+ timersub(&now, &end, &delta);
+ snprintf(s3, sizeof(s3), "-%lu.06%lu",
+ (u_long)delta.tv_sec,
+ (u_long)delta.tv_usec);
+ }
+ xo_emit(" {:remaining-time/%s}", s3);
+
+ xo_open_instance("bandwidth-meter");
+
+ xo_emit("\n");
+}
+
+static void
+print_mfc(struct mfc *m, int maxvif, int *banner_printed)
+{
+ struct sockaddr_in sin;
+ struct sockaddr *sa = (struct sockaddr *)&sin;
+ struct bw_meter bw_meter, *bwm;
+ int bw_banner_printed;
+ int error;
+ vifi_t vifi;
+
+ bw_banner_printed = 0;
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+
+ if (! *banner_printed) {
+ xo_open_list("multicast-forwarding-entry");
+ xo_emit("\n{T:IPv4 Multicast Forwarding Table}\n"
+ " {T:Origin} {T:Group} "
+ " {T:Packets In-Vif} {T:Out-Vifs:Ttls}\n");
+ *banner_printed = 1;
+ }
+
+ memcpy(&sin.sin_addr, &m->mfc_origin, sizeof(sin.sin_addr));
+ xo_emit(" {:origin-address/%-15.15s}", routename(sa, numeric_addr));
+ memcpy(&sin.sin_addr, &m->mfc_mcastgrp, sizeof(sin.sin_addr));
+ xo_emit(" {:group-address/%-15.15s}",
+ routename(sa, numeric_addr));
+ xo_emit(" {:sent-packets/%9lu}", m->mfc_pkt_cnt);
+ xo_emit(" {:parent/%3d} ", m->mfc_parent);
+ xo_open_list("vif-ttl");
+ for (vifi = 0; vifi <= maxvif; vifi++) {
+ if (m->mfc_ttls[vifi] > 0) {
+ xo_open_instance("vif-ttl");
+ xo_emit(" {k:vif/%u}:{:ttl/%u}", vifi,
+ m->mfc_ttls[vifi]);
+ xo_close_instance("vif-ttl");
+ }
+ }
+ xo_close_list("vif-ttl");
+ xo_emit("\n");
+
+ /*
+ * XXX We break the rules and try to use KVM to read the
+ * bandwidth meters, they are not retrievable via sysctl yet.
+ */
+ bwm = m->mfc_bw_meter_leq;
+ while (bwm != NULL) {
+ error = kread((u_long)bwm, (char *)&bw_meter,
+ sizeof(bw_meter));
+ if (error)
+ break;
+ print_bw_meter(&bw_meter, &bw_banner_printed);
+ bwm = bw_meter.bm_mfc_next;
+ }
+ bwm = m->mfc_bw_meter_geq;
+ while (bwm != NULL) {
+ error = kread((u_long)bwm, (char *)&bw_meter,
+ sizeof(bw_meter));
+ if (error)
+ break;
+ print_bw_meter(&bw_meter, &bw_banner_printed);
+ bwm = bw_meter.bm_mfc_next;
+ }
+ if (banner_printed)
+ xo_close_list("bandwidth-meter");
+}
+
+void
+mroutepr(void)
+{
+ struct sockaddr_in sin;
+ struct sockaddr *sa = (struct sockaddr *)&sin;
+ struct vif viftable[MAXVIFS];
+ struct vif *v;
+ struct mfc *m;
+ u_long pmfchashtbl, pmfctablesize, pviftbl;
+ int banner_printed;
+ int saved_numeric_addr;
+ size_t len;
+ vifi_t vifi, maxvif;
+
+ saved_numeric_addr = numeric_addr;
+ numeric_addr = 1;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+
+ /*
+ * TODO:
+ * The VIF table will move to hanging off the struct if_info for
+ * each IPv4 configured interface. Currently it is statically
+ * allocated, and retrieved either using KVM or an opaque SYSCTL.
+ *
+ * This can't happen until the API documented in multicast(4)
+ * is itself refactored. The historical reason why VIFs use
+ * a separate ifindex space is entirely due to the legacy
+ * capability of the MROUTING code to create IPIP tunnels on
+ * the fly to support DVMRP. When gif(4) became available, this
+ * functionality was deprecated, as PIM does not use it.
+ */
+ maxvif = 0;
+ pmfchashtbl = pmfctablesize = pviftbl = 0;
+
+ len = sizeof(viftable);
+ if (live) {
+ if (sysctlbyname("net.inet.ip.viftable", viftable, &len, NULL,
+ 0) < 0) {
+ xo_warn("sysctl: net.inet.ip.viftable");
+ return;
+ }
+ } else {
+ pmfchashtbl = nl[N_MFCHASHTBL].n_value;
+ pmfctablesize = nl[N_MFCTABLESIZE].n_value;
+ pviftbl = nl[N_VIFTABLE].n_value;
+
+ if (pmfchashtbl == 0 || pmfctablesize == 0 || pviftbl == 0) {
+ xo_warnx("No IPv4 MROUTING kernel support.");
+ return;
+ }
+
+ kread(pviftbl, (char *)viftable, sizeof(viftable));
+ }
+
+ banner_printed = 0;
+ for (vifi = 0, v = viftable; vifi < MAXVIFS; ++vifi, ++v) {
+ if (v->v_lcl_addr.s_addr == 0)
+ continue;
+
+ maxvif = vifi;
+ if (!banner_printed) {
+ xo_emit("\n{T:IPv4 Virtual Interface Table\n"
+ " Vif Thresh Local-Address "
+ "Remote-Address Pkts-In Pkts-Out}\n");
+ banner_printed = 1;
+ xo_open_list("vif");
+ }
+
+ xo_open_instance("vif");
+ memcpy(&sin.sin_addr, &v->v_lcl_addr, sizeof(sin.sin_addr));
+ xo_emit(" {:vif/%2u} {:threshold/%6u} {:route/%-15.15s}",
+ /* opposite math of add_vif() */
+ vifi, v->v_threshold,
+ routename(sa, numeric_addr));
+ memcpy(&sin.sin_addr, &v->v_rmt_addr, sizeof(sin.sin_addr));
+ xo_emit(" {:source/%-15.15s}", (v->v_flags & VIFF_TUNNEL) ?
+ routename(sa, numeric_addr) : "");
+
+ xo_emit(" {:received-packets/%9lu} {:sent-packets/%9lu}\n",
+ v->v_pkt_in, v->v_pkt_out);
+ xo_close_instance("vif");
+ }
+ if (banner_printed)
+ xo_close_list("vif");
+ else
+ xo_emit("\n{T:IPv4 Virtual Interface Table is empty}\n");
+
+ banner_printed = 0;
+
+ /*
+ * TODO:
+ * The MFC table will move into the AF_INET radix trie in future.
+ * In 8.x, it becomes a dynamically allocated structure referenced
+ * by a hashed LIST, allowing more than 256 entries w/o kernel tuning.
+ *
+ * If retrieved via opaque SYSCTL, the kernel will coalesce it into
+ * a static table for us.
+ * If retrieved via KVM, the hash list pointers must be followed.
+ */
+ if (live) {
+ struct mfc *mfctable;
+
+ len = 0;
+ if (sysctlbyname("net.inet.ip.mfctable", NULL, &len, NULL,
+ 0) < 0) {
+ xo_warn("sysctl: net.inet.ip.mfctable");
+ return;
+ }
+
+ mfctable = malloc(len);
+ if (mfctable == NULL) {
+ xo_warnx("malloc %lu bytes", (u_long)len);
+ return;
+ }
+ if (sysctlbyname("net.inet.ip.mfctable", mfctable, &len, NULL,
+ 0) < 0) {
+ free(mfctable);
+ xo_warn("sysctl: net.inet.ip.mfctable");
+ return;
+ }
+
+ m = mfctable;
+ while (len >= sizeof(*m)) {
+ print_mfc(m++, maxvif, &banner_printed);
+ len -= sizeof(*m);
+ }
+ if (banner_printed)
+ xo_close_list("multicast-forwarding-entry");
+ if (len != 0)
+ xo_warnx("print_mfc: %lu trailing bytes", (u_long)len);
+
+ free(mfctable);
+ } else {
+ LIST_HEAD(, mfc) *mfchashtbl;
+ u_long i, mfctablesize;
+ struct mfc mfc;
+ int error;
+
+ error = kread(pmfctablesize, (char *)&mfctablesize,
+ sizeof(u_long));
+ if (error) {
+ xo_warn("kread: mfctablesize");
+ return;
+ }
+
+ len = sizeof(*mfchashtbl) * mfctablesize;
+ mfchashtbl = malloc(len);
+ if (mfchashtbl == NULL) {
+ xo_warnx("malloc %lu bytes", (u_long)len);
+ return;
+ }
+ kread(pmfchashtbl, (char *)&mfchashtbl, len);
+
+ for (i = 0; i < mfctablesize; i++) {
+ LIST_FOREACH(m, &mfchashtbl[i], mfc_hash) {
+ kread((u_long)m, (char *)&mfc, sizeof(mfc));
+ print_mfc(m, maxvif, &banner_printed);
+ }
+ }
+ if (banner_printed)
+ xo_close_list("multicast-forwarding-entry");
+
+ free(mfchashtbl);
+ }
+
+ if (!banner_printed)
+ xo_emit("\n{T:IPv4 Multicast Forwarding Table is empty}\n");
+
+ xo_emit("\n");
+ numeric_addr = saved_numeric_addr;
+}
+
+void
+mrt_stats(void)
+{
+ struct mrtstat mrtstat;
+ u_long mstaddr;
+
+ mstaddr = nl[N_MRTSTAT].n_value;
+
+ if (fetch_stats("net.inet.ip.mrtstat", mstaddr, &mrtstat,
+ sizeof(mrtstat), kread_counters) != 0) {
+ if ((live && errno == ENOENT) || (!live && mstaddr == 0))
+ fprintf(stderr, "No IPv4 MROUTING kernel support.\n");
+ return;
+ }
+
+ xo_emit("{T:IPv4 multicast forwarding}:\n");
+
+#define p(f, m) if (mrtstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)mrtstat.f, plural(mrtstat.f))
+#define p2(f, m) if (mrtstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)mrtstat.f, plurales(mrtstat.f))
+
+ xo_open_container("multicast-statistics");
+
+ p(mrts_mfc_lookups, "\t{:cache-lookups/%ju} "
+ "{N:/multicast forwarding cache lookup%s}\n");
+ p2(mrts_mfc_misses, "\t{:cache-misses/%ju} "
+ "{N:/multicast forwarding cache miss%s}\n");
+ p(mrts_upcalls, "\t{:upcalls-total/%ju} "
+ "{N:/upcall%s to multicast routing daemon}\n");
+ p(mrts_upq_ovflw, "\t{:upcall-overflows/%ju} "
+ "{N:/upcall queue overflow%s}\n");
+ p(mrts_upq_sockfull,
+ "\t{:upcalls-dropped-full-buffer/%ju} "
+ "{N:/upcall%s dropped due to full socket buffer}\n");
+ p(mrts_cache_cleanups, "\t{:cache-cleanups/%ju} "
+ "{N:/cache cleanup%s}\n");
+ p(mrts_no_route, "\t{:dropped-no-origin/%ju} "
+ "{N:/datagram%s with no route for origin}\n");
+ p(mrts_bad_tunnel, "\t{:dropped-bad-tunnel/%ju} "
+ "{N:/datagram%s arrived with bad tunneling}\n");
+ p(mrts_cant_tunnel, "\t{:dropped-could-not-tunnel/%ju} "
+ "{N:/datagram%s could not be tunneled}\n");
+ p(mrts_wrong_if, "\t{:dropped-wrong-incoming-interface/%ju} "
+ "{N:/datagram%s arrived on wrong interface}\n");
+ p(mrts_drop_sel, "\t{:dropped-selectively/%ju} "
+ "{N:/datagram%s selectively dropped}\n");
+ p(mrts_q_overflow, "\t{:dropped-queue-overflow/%ju} "
+ "{N:/datagram%s dropped due to queue overflow}\n");
+ p(mrts_pkt2large, "\t{:dropped-too-large/%ju} "
+ "{N:/datagram%s dropped for being too large}\n");
+
+#undef p2
+#undef p
+}
diff --git a/usr.bin/netstat/mroute6.c b/usr.bin/netstat/mroute6.c
new file mode 100644
index 000000000000..0bb44b8292e7
--- /dev/null
+++ b/usr.bin/netstat/mroute6.c
@@ -0,0 +1,274 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause AND BSD-3-Clause
+ *
+ * Copyright (C) 1998 WIDE Project.
+ * 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.
+ * 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. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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.
+ */
+/*-
+ * Copyright (c) 1989 Stephen Deering
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Stephen Deering of Stanford University.
+ *
+ * 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.
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifdef INET6
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/mbuf.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <libxo/xo.h>
+
+#define KERNEL 1
+struct sockopt;
+#include <netinet6/ip6_mroute.h>
+#undef KERNEL
+
+#include "netstat.h"
+
+#define WID_ORG (Wflag ? 39 : (numeric_addr ? 29 : 18)) /* width of origin column */
+#define WID_GRP (Wflag ? 18 : (numeric_addr ? 16 : 18)) /* width of group column */
+
+void
+mroute6pr(void)
+{
+ struct mf6c *mf6ctable[MF6CTBLSIZ], *mfcp;
+ struct mif6_sctl mif6table[MAXMIFS];
+ struct mf6c mfc;
+ struct rtdetq rte, *rtep;
+ struct mif6_sctl *mifp;
+ mifi_t mifi;
+ int i;
+ int banner_printed;
+ int saved_numeric_addr;
+ mifi_t maxmif = 0;
+ long int waitings;
+ size_t len;
+
+ if (live == 0)
+ return;
+
+ len = sizeof(mif6table);
+ if (sysctlbyname("net.inet6.ip6.mif6table", mif6table, &len, NULL, 0) <
+ 0) {
+ xo_warn("sysctl: net.inet6.ip6.mif6table");
+ return;
+ }
+
+ saved_numeric_addr = numeric_addr;
+ numeric_addr = 1;
+ banner_printed = 0;
+
+ for (mifi = 0, mifp = mif6table; mifi < MAXMIFS; ++mifi, ++mifp) {
+ char ifname[IFNAMSIZ];
+
+ if (mifp->m6_ifp == 0)
+ continue;
+
+ maxmif = mifi;
+ if (!banner_printed) {
+ xo_open_list("multicast-interface");
+ xo_emit("\n{T:IPv6 Multicast Interface Table}\n"
+ "{T: Mif Rate PhyIF Pkts-In Pkts-Out}\n");
+ banner_printed = 1;
+ }
+
+ xo_open_instance("multicast-interface");
+ xo_emit(" {:mif/%2u} {:rate-limit/%4d}",
+ mifi, mifp->m6_rate_limit);
+ xo_emit(" {:ifname/%5s}", (mifp->m6_flags & MIFF_REGISTER) ?
+ "reg0" : if_indextoname(mifp->m6_ifp, ifname));
+
+ xo_emit(" {:received-packets/%9ju} {:sent-packets/%9ju}\n",
+ (uintmax_t)mifp->m6_pkt_in,
+ (uintmax_t)mifp->m6_pkt_out);
+ xo_close_instance("multicast-interface");
+ }
+ if (banner_printed)
+ xo_open_list("multicast-interface");
+ else
+ xo_emit("\n{T:IPv6 Multicast Interface Table is empty}\n");
+
+ len = sizeof(mf6ctable);
+ if (sysctlbyname("net.inet6.ip6.mf6ctable", mf6ctable, &len, NULL, 0) <
+ 0) {
+ xo_warn("sysctl: net.inet6.ip6.mf6ctable");
+ return;
+ }
+
+ banner_printed = 0;
+
+ for (i = 0; i < MF6CTBLSIZ; ++i) {
+ mfcp = mf6ctable[i];
+ while(mfcp) {
+ kread((u_long)mfcp, (char *)&mfc, sizeof(mfc));
+ if (!banner_printed) {
+ xo_open_list("multicast-forwarding-cache");
+ xo_emit("\n"
+ "{T:IPv6 Multicast Forwarding Cache}\n");
+ xo_emit(" {T:%-*.*s} {T:%-*.*s} {T:%s}",
+ WID_ORG, WID_ORG, "Origin",
+ WID_GRP, WID_GRP, "Group",
+ " Packets Waits In-Mif Out-Mifs\n");
+ banner_printed = 1;
+ }
+
+ xo_open_instance("multicast-forwarding-cache");
+
+ xo_emit(" {:origin/%-*.*s}", WID_ORG, WID_ORG,
+ routename(sin6tosa(&mfc.mf6c_origin),
+ numeric_addr));
+ xo_emit(" {:group/%-*.*s}", WID_GRP, WID_GRP,
+ routename(sin6tosa(&mfc.mf6c_mcastgrp),
+ numeric_addr));
+ xo_emit(" {:total-packets/%9ju}",
+ (uintmax_t)mfc.mf6c_pkt_cnt);
+
+ for (waitings = 0, rtep = mfc.mf6c_stall; rtep; ) {
+ waitings++;
+ /* XXX KVM */
+ kread((u_long)rtep, (char *)&rte, sizeof(rte));
+ rtep = rte.next;
+ }
+ xo_emit(" {:waitings/%3ld}", waitings);
+
+ if (mfc.mf6c_parent == MF6C_INCOMPLETE_PARENT)
+ xo_emit(" --- ");
+ else
+ xo_emit(" {:parent/%3d} ", mfc.mf6c_parent);
+ xo_open_list("mif");
+ for (mifi = 0; mifi <= maxmif; mifi++) {
+ if (IF_ISSET(mifi, &mfc.mf6c_ifset))
+ xo_emit(" {l:%u}", mifi);
+ }
+ xo_close_list("mif");
+ xo_emit("\n");
+
+ mfcp = mfc.mf6c_next;
+ xo_close_instance("multicast-forwarding-cache");
+ }
+ }
+ if (banner_printed)
+ xo_close_list("multicast-forwarding-cache");
+ else
+ xo_emit("\n{T:IPv6 Multicast Forwarding Table is empty}\n");
+
+ xo_emit("\n");
+ numeric_addr = saved_numeric_addr;
+}
+
+void
+mrt6_stats(void)
+{
+ struct mrt6stat mrtstat;
+
+ if (fetch_stats("net.inet6.ip6.mrt6stat", 0, &mrtstat,
+ sizeof(mrtstat), kread_counters) != 0)
+ return;
+
+ xo_open_container("multicast-statistics");
+ xo_emit("{T:IPv6 multicast forwarding}:\n");
+
+#define p(f, m) if (mrtstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)mrtstat.f, plural(mrtstat.f))
+#define p2(f, m) if (mrtstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)mrtstat.f, plurales(mrtstat.f))
+
+ p(mrt6s_mfc_lookups, "\t{:cache-lookups/%ju} "
+ "{N:/multicast forwarding cache lookup%s}\n");
+ p2(mrt6s_mfc_misses, "\t{:cache-misses/%ju} "
+ "{N:/multicast forwarding cache miss%s}\n");
+ p(mrt6s_upcalls, "\t{:upcalls/%ju} "
+ "{N:/upcall%s to multicast routing daemon}\n");
+ p(mrt6s_upq_ovflw, "\t{:upcall-overflows/%ju} "
+ "{N:/upcall queue overflow%s}\n");
+ p(mrt6s_upq_sockfull, "\t{:upcalls-dropped-full-buffer/%ju} "
+ "{N:/upcall%s dropped due to full socket buffer}\n");
+ p(mrt6s_cache_cleanups, "\t{:cache-cleanups/%ju} "
+ "{N:/cache cleanup%s}\n");
+ p(mrt6s_no_route, "\t{:dropped-no-origin/%ju} "
+ "{N:/datagram%s with no route for origin}\n");
+ p(mrt6s_bad_tunnel, "\t{:dropped-bad-tunnel/%ju} "
+ "{N:/datagram%s arrived with bad tunneling}\n");
+ p(mrt6s_cant_tunnel, "\t{:dropped-could-not-tunnel/%ju} "
+ "{N:/datagram%s could not be tunneled}\n");
+ p(mrt6s_wrong_if, "\t{:dropped-wrong-incoming-interface/%ju} "
+ "{N:/datagram%s arrived on wrong interface}\n");
+ p(mrt6s_drop_sel, "\t{:dropped-selectively/%ju} "
+ "{N:/datagram%s selectively dropped}\n");
+ p(mrt6s_q_overflow, "\t{:dropped-queue-overflow/%ju} "
+ "{N:/datagram%s dropped due to queue overflow}\n");
+ p(mrt6s_pkt2large, "\t{:dropped-too-large/%ju} "
+ "{N:/datagram%s dropped for being too large}\n");
+
+#undef p2
+#undef p
+ xo_close_container("multicast-statistics");
+}
+#endif /*INET6*/
diff --git a/usr.bin/netstat/netgraph.c b/usr.bin/netstat/netgraph.c
new file mode 100644
index 000000000000..3a4c11de1db8
--- /dev/null
+++ b/usr.bin/netstat/netgraph.c
@@ -0,0 +1,141 @@
+/*-
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#define _WANT_SOCKET
+#include <sys/socketvar.h>
+#include <sys/protosw.h>
+#include <sys/linker.h>
+
+#include <net/route.h>
+
+#include <netgraph.h>
+#include <netgraph/ng_message.h>
+#include <netgraph/ng_socket.h>
+#include <netgraph/ng_socketvar.h>
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+
+static int first = 1;
+static int csock = -1;
+
+void
+netgraphprotopr(u_long off, const char *name, int af1 __unused,
+ int proto __unused)
+{
+ struct ngpcb *this, *next;
+ struct ngpcb ngpcb;
+ struct socket sockb;
+ int debug = 1;
+
+ /* If symbol not found, try looking in the KLD module */
+ if (off == 0) {
+ if (debug)
+ xo_warnx("Error reading symbols from ng_socket.ko");
+ return;
+ }
+
+ /* Get pointer to first socket */
+ kread(off, (char *)&this, sizeof(this));
+
+ /* Get my own socket node */
+ if (csock == -1)
+ NgMkSockNode(NULL, &csock, NULL);
+
+ for (; this != NULL; this = next) {
+ u_char rbuf[sizeof(struct ng_mesg) + sizeof(struct nodeinfo)];
+ struct ng_mesg *resp = (struct ng_mesg *) rbuf;
+ struct nodeinfo *ni = (struct nodeinfo *) resp->data;
+ char path[64];
+
+ /* Read in ngpcb structure */
+ kread((u_long)this, (char *)&ngpcb, sizeof(ngpcb));
+ next = LIST_NEXT(&ngpcb, socks);
+
+ /* Read in socket structure */
+ kread((u_long)ngpcb.ng_socket, (char *)&sockb, sizeof(sockb));
+
+ /* Check type of socket */
+ if (strcmp(name, "ctrl") == 0 && ngpcb.type != NG_CONTROL)
+ continue;
+ if (strcmp(name, "data") == 0 && ngpcb.type != NG_DATA)
+ continue;
+
+ /* Do headline */
+ if (first) {
+ xo_emit("{T:Netgraph sockets}\n");
+ if (Aflag)
+ xo_emit("{T:/%-8.8s} ", "PCB");
+ xo_emit("{T:/%-5.5s} {T:/%-6.6s} {T:/%-6.6s} "
+ "{T:/%-14.14s} {T:/%s}\n",
+ "Type", "Recv-Q", "Send-Q", "Node Address",
+ "#Hooks");
+ first = 0;
+ }
+
+ /* Show socket */
+ if (Aflag)
+ xo_emit("{:address/%8lx} ", (u_long) this);
+ xo_emit("{t:name/%-5.5s} {:receive-bytes-waiting/%6u} "
+ "{:send-byte-waiting/%6u} ",
+ name, sockb.so_rcv.sb_ccc, sockb.so_snd.sb_ccc);
+
+ /* Get info on associated node */
+ if (ngpcb.node_id == 0 || csock == -1)
+ goto finish;
+ snprintf(path, sizeof(path), "[%x]:", ngpcb.node_id);
+ if (NgSendMsg(csock, path,
+ NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0) < 0)
+ goto finish;
+ if (NgRecvMsg(csock, resp, sizeof(rbuf), NULL) < 0)
+ goto finish;
+
+ /* Display associated node info */
+ if (*ni->name != '\0')
+ snprintf(path, sizeof(path), "%s:", ni->name);
+ xo_emit("{t:path/%-14.14s} {:hooks/%4d}", path, ni->hooks);
+finish:
+ xo_emit("\n");
+ }
+}
+
diff --git a/usr.bin/netstat/netisr.c b/usr.bin/netstat/netisr.c
new file mode 100644
index 000000000000..1708a8f01c79
--- /dev/null
+++ b/usr.bin/netstat/netisr.c
@@ -0,0 +1,506 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010-2011 Juniper Networks, Inc.
+ * All rights reserved.
+ *
+ * This software was developed by Robert N. M. Watson under contract
+ * to Juniper Networks, Inc.
+ *
+ * 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.
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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/sysctl.h>
+
+#include <sys/_lock.h>
+#include <sys/_mutex.h>
+
+#define _WANT_NETISR_INTERNAL
+#include <net/netisr.h>
+#include <net/netisr_internal.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sysexits.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+#include "nl_defs.h"
+
+/*
+ * Print statistics for the kernel netisr subsystem.
+ */
+static u_int bindthreads;
+static u_int maxthreads;
+static u_int numthreads;
+
+static u_int defaultqlimit;
+static u_int maxqlimit;
+
+static char dispatch_policy[20];
+
+static struct sysctl_netisr_proto *proto_array;
+static u_int proto_array_len;
+
+static struct sysctl_netisr_workstream *workstream_array;
+static u_int workstream_array_len;
+
+static struct sysctl_netisr_work *work_array;
+static u_int work_array_len;
+
+static u_int *nws_array;
+
+static u_int maxprot;
+
+static void
+netisr_dispatch_policy_to_string(u_int policy, char *buf,
+ size_t buflen)
+{
+ const char *str;
+
+ switch (policy) {
+ case NETISR_DISPATCH_DEFAULT:
+ str = "default";
+ break;
+ case NETISR_DISPATCH_DEFERRED:
+ str = "deferred";
+ break;
+ case NETISR_DISPATCH_HYBRID:
+ str = "hybrid";
+ break;
+ case NETISR_DISPATCH_DIRECT:
+ str = "direct";
+ break;
+ default:
+ str = "unknown";
+ break;
+ }
+ snprintf(buf, buflen, "%s", str);
+}
+
+/*
+ * Load a nul-terminated string from KVM up to 'limit', guarantee that the
+ * string in local memory is nul-terminated.
+ */
+static void
+netisr_load_kvm_string(uintptr_t addr, char *dest, u_int limit)
+{
+ u_int i;
+
+ for (i = 0; i < limit; i++) {
+ if (kread(addr + i, &dest[i], sizeof(dest[i])) != 0)
+ xo_errx(EX_OSERR, "%s: kread()", __func__);
+ if (dest[i] == '\0')
+ break;
+ }
+ dest[limit - 1] = '\0';
+}
+
+static const char *
+netisr_proto2name(u_int proto)
+{
+ u_int i;
+
+ for (i = 0; i < proto_array_len; i++) {
+ if (proto_array[i].snp_proto == proto)
+ return (proto_array[i].snp_name);
+ }
+ return ("unknown");
+}
+
+static int
+netisr_protoispresent(u_int proto)
+{
+ u_int i;
+
+ for (i = 0; i < proto_array_len; i++) {
+ if (proto_array[i].snp_proto == proto)
+ return (1);
+ }
+ return (0);
+}
+
+static void
+netisr_load_kvm_config(void)
+{
+ u_int tmp;
+
+ kread(nl[N_NETISR_BINDTHREADS].n_value, &bindthreads, sizeof(u_int));
+ kread(nl[N_NETISR_MAXTHREADS].n_value, &maxthreads, sizeof(u_int));
+ kread(nl[N_NWS_COUNT].n_value, &numthreads, sizeof(u_int));
+ kread(nl[N_NETISR_DEFAULTQLIMIT].n_value, &defaultqlimit,
+ sizeof(u_int));
+ kread(nl[N_NETISR_MAXQLIMIT].n_value, &maxqlimit, sizeof(u_int));
+ kread(nl[N_NETISR_DISPATCH_POLICY].n_value, &tmp, sizeof(u_int));
+
+ netisr_dispatch_policy_to_string(tmp, dispatch_policy,
+ sizeof(dispatch_policy));
+}
+
+static void
+netisr_load_sysctl_uint(const char *name, u_int *p)
+{
+ size_t retlen;
+
+ retlen = sizeof(u_int);
+ if (sysctlbyname(name, p, &retlen, NULL, 0) < 0)
+ xo_err(EX_OSERR, "%s", name);
+ if (retlen != sizeof(u_int))
+ xo_errx(EX_DATAERR, "%s: invalid len %ju", name, (uintmax_t)retlen);
+}
+
+static void
+netisr_load_sysctl_string(const char *name, char *p, size_t len)
+{
+ size_t retlen;
+
+ retlen = len;
+ if (sysctlbyname(name, p, &retlen, NULL, 0) < 0)
+ xo_err(EX_OSERR, "%s", name);
+ p[len - 1] = '\0';
+}
+
+static void
+netisr_load_sysctl_config(void)
+{
+
+ netisr_load_sysctl_uint("net.isr.bindthreads", &bindthreads);
+ netisr_load_sysctl_uint("net.isr.maxthreads", &maxthreads);
+ netisr_load_sysctl_uint("net.isr.numthreads", &numthreads);
+
+ netisr_load_sysctl_uint("net.isr.defaultqlimit", &defaultqlimit);
+ netisr_load_sysctl_uint("net.isr.maxqlimit", &maxqlimit);
+
+ netisr_load_sysctl_string("net.isr.dispatch", dispatch_policy,
+ sizeof(dispatch_policy));
+}
+
+static void
+netisr_load_kvm_proto(void)
+{
+ struct netisr_proto *np_array, *npp;
+ u_int i, protocount;
+ struct sysctl_netisr_proto *snpp;
+ size_t len;
+
+ /*
+ * Kernel compile-time and user compile-time definitions of
+ * NETISR_MAXPROT must match, as we use that to size work arrays.
+ */
+ kread(nl[N_NETISR_MAXPROT].n_value, &maxprot, sizeof(u_int));
+ if (maxprot != NETISR_MAXPROT)
+ xo_errx(EX_DATAERR, "%s: NETISR_MAXPROT mismatch", __func__);
+ len = maxprot * sizeof(*np_array);
+ np_array = malloc(len);
+ if (np_array == NULL)
+ xo_err(EX_OSERR, "%s: malloc", __func__);
+ if (kread(nl[N_NETISR_PROTO].n_value, np_array, len) != 0)
+ xo_errx(EX_DATAERR, "%s: kread(_netisr_proto)", __func__);
+
+ /*
+ * Size and allocate memory to hold only live protocols.
+ */
+ protocount = 0;
+ for (i = 0; i < maxprot; i++) {
+ if (np_array[i].np_name == NULL)
+ continue;
+ protocount++;
+ }
+ proto_array = calloc(protocount, sizeof(*proto_array));
+ if (proto_array == NULL)
+ xo_err(EX_OSERR, "malloc");
+ protocount = 0;
+ for (i = 0; i < maxprot; i++) {
+ npp = &np_array[i];
+ if (npp->np_name == NULL)
+ continue;
+ snpp = &proto_array[protocount];
+ snpp->snp_version = sizeof(*snpp);
+ netisr_load_kvm_string((uintptr_t)npp->np_name,
+ snpp->snp_name, sizeof(snpp->snp_name));
+ snpp->snp_proto = i;
+ snpp->snp_qlimit = npp->np_qlimit;
+ snpp->snp_policy = npp->np_policy;
+ snpp->snp_dispatch = npp->np_dispatch;
+ if (npp->np_m2flow != NULL)
+ snpp->snp_flags |= NETISR_SNP_FLAGS_M2FLOW;
+ if (npp->np_m2cpuid != NULL)
+ snpp->snp_flags |= NETISR_SNP_FLAGS_M2CPUID;
+ if (npp->np_drainedcpu != NULL)
+ snpp->snp_flags |= NETISR_SNP_FLAGS_DRAINEDCPU;
+ protocount++;
+ }
+ proto_array_len = protocount;
+ free(np_array);
+}
+
+static void
+netisr_load_sysctl_proto(void)
+{
+ size_t len;
+
+ if (sysctlbyname("net.isr.proto", NULL, &len, NULL, 0) < 0)
+ xo_err(EX_OSERR, "net.isr.proto: query len");
+ if (len % sizeof(*proto_array) != 0)
+ xo_errx(EX_DATAERR, "net.isr.proto: invalid len");
+ proto_array = malloc(len);
+ if (proto_array == NULL)
+ xo_err(EX_OSERR, "malloc");
+ if (sysctlbyname("net.isr.proto", proto_array, &len, NULL, 0) < 0)
+ xo_err(EX_OSERR, "net.isr.proto: query data");
+ if (len % sizeof(*proto_array) != 0)
+ xo_errx(EX_DATAERR, "net.isr.proto: invalid len");
+ proto_array_len = len / sizeof(*proto_array);
+ if (proto_array_len < 1)
+ xo_errx(EX_DATAERR, "net.isr.proto: no data");
+ if (proto_array[0].snp_version != sizeof(proto_array[0]))
+ xo_errx(EX_DATAERR, "net.isr.proto: invalid version");
+}
+
+static void
+netisr_load_kvm_workstream(void)
+{
+ struct netisr_workstream nws;
+ struct sysctl_netisr_workstream *snwsp;
+ struct sysctl_netisr_work *snwp;
+ struct netisr_work *nwp;
+ u_int counter, cpuid, proto, wsid;
+ size_t len;
+
+ len = numthreads * sizeof(*nws_array);
+ nws_array = malloc(len);
+ if (nws_array == NULL)
+ xo_err(EX_OSERR, "malloc");
+ if (kread(nl[N_NWS_ARRAY].n_value, nws_array, len) != 0)
+ xo_errx(EX_OSERR, "%s: kread(_nws_array)", __func__);
+ workstream_array = calloc(numthreads, sizeof(*workstream_array));
+ if (workstream_array == NULL)
+ xo_err(EX_OSERR, "calloc");
+ workstream_array_len = numthreads;
+ work_array = calloc(numthreads * proto_array_len, sizeof(*work_array));
+ if (work_array == NULL)
+ xo_err(EX_OSERR, "calloc");
+ counter = 0;
+ for (wsid = 0; wsid < numthreads; wsid++) {
+ cpuid = nws_array[wsid];
+ kset_dpcpu(cpuid);
+ if (kread(nl[N_NWS].n_value, &nws, sizeof(nws)) != 0)
+ xo_errx(EX_OSERR, "%s: kread(nw)", __func__);
+ snwsp = &workstream_array[wsid];
+ snwsp->snws_version = sizeof(*snwsp);
+ snwsp->snws_wsid = cpuid;
+ snwsp->snws_cpu = cpuid;
+ if (nws.nws_intr_event != NULL)
+ snwsp->snws_flags |= NETISR_SNWS_FLAGS_INTR;
+
+ /*
+ * Extract the CPU's per-protocol work information.
+ */
+ xo_emit("counting to maxprot: {:maxprot/%u}\n", maxprot);
+ for (proto = 0; proto < maxprot; proto++) {
+ if (!netisr_protoispresent(proto))
+ continue;
+ nwp = &nws.nws_work[proto];
+ snwp = &work_array[counter];
+ snwp->snw_version = sizeof(*snwp);
+ snwp->snw_wsid = cpuid;
+ snwp->snw_proto = proto;
+ snwp->snw_len = nwp->nw_len;
+ snwp->snw_watermark = nwp->nw_watermark;
+ snwp->snw_dispatched = nwp->nw_dispatched;
+ snwp->snw_hybrid_dispatched =
+ nwp->nw_hybrid_dispatched;
+ snwp->snw_qdrops = nwp->nw_qdrops;
+ snwp->snw_queued = nwp->nw_queued;
+ snwp->snw_handled = nwp->nw_handled;
+ counter++;
+ }
+ }
+ work_array_len = counter;
+}
+
+static void
+netisr_load_sysctl_workstream(void)
+{
+ size_t len;
+
+ if (sysctlbyname("net.isr.workstream", NULL, &len, NULL, 0) < 0)
+ xo_err(EX_OSERR, "net.isr.workstream: query len");
+ if (len % sizeof(*workstream_array) != 0)
+ xo_errx(EX_DATAERR, "net.isr.workstream: invalid len");
+ workstream_array = malloc(len);
+ if (workstream_array == NULL)
+ xo_err(EX_OSERR, "malloc");
+ if (sysctlbyname("net.isr.workstream", workstream_array, &len, NULL,
+ 0) < 0)
+ xo_err(EX_OSERR, "net.isr.workstream: query data");
+ if (len % sizeof(*workstream_array) != 0)
+ xo_errx(EX_DATAERR, "net.isr.workstream: invalid len");
+ workstream_array_len = len / sizeof(*workstream_array);
+ if (workstream_array_len < 1)
+ xo_errx(EX_DATAERR, "net.isr.workstream: no data");
+ if (workstream_array[0].snws_version != sizeof(workstream_array[0]))
+ xo_errx(EX_DATAERR, "net.isr.workstream: invalid version");
+}
+
+static void
+netisr_load_sysctl_work(void)
+{
+ size_t len;
+
+ if (sysctlbyname("net.isr.work", NULL, &len, NULL, 0) < 0)
+ xo_err(EX_OSERR, "net.isr.work: query len");
+ if (len % sizeof(*work_array) != 0)
+ xo_errx(EX_DATAERR, "net.isr.work: invalid len");
+ work_array = malloc(len);
+ if (work_array == NULL)
+ xo_err(EX_OSERR, "malloc");
+ if (sysctlbyname("net.isr.work", work_array, &len, NULL, 0) < 0)
+ xo_err(EX_OSERR, "net.isr.work: query data");
+ if (len % sizeof(*work_array) != 0)
+ xo_errx(EX_DATAERR, "net.isr.work: invalid len");
+ work_array_len = len / sizeof(*work_array);
+ if (work_array_len < 1)
+ xo_errx(EX_DATAERR, "net.isr.work: no data");
+ if (work_array[0].snw_version != sizeof(work_array[0]))
+ xo_errx(EX_DATAERR, "net.isr.work: invalid version");
+}
+
+static void
+netisr_print_proto(struct sysctl_netisr_proto *snpp)
+{
+ char tmp[20];
+
+ xo_emit("{[:-6}{k:name/%s}{]:}", snpp->snp_name);
+ xo_emit(" {:protocol/%5u}", snpp->snp_proto);
+ xo_emit(" {:queue-limit/%6u}", snpp->snp_qlimit);
+ xo_emit(" {:policy-type/%6s}",
+ (snpp->snp_policy == NETISR_POLICY_SOURCE) ? "source" :
+ (snpp->snp_policy == NETISR_POLICY_FLOW) ? "flow" :
+ (snpp->snp_policy == NETISR_POLICY_CPU) ? "cpu" : "-");
+ netisr_dispatch_policy_to_string(snpp->snp_dispatch, tmp,
+ sizeof(tmp));
+ xo_emit(" {:policy/%8s}", tmp);
+ xo_emit(" {:flags/%s%s%s}\n",
+ (snpp->snp_flags & NETISR_SNP_FLAGS_M2CPUID) ? "C" : "-",
+ (snpp->snp_flags & NETISR_SNP_FLAGS_DRAINEDCPU) ? "D" : "-",
+ (snpp->snp_flags & NETISR_SNP_FLAGS_M2FLOW) ? "F" : "-");
+}
+
+static void
+netisr_print_workstream(struct sysctl_netisr_workstream *snwsp)
+{
+ struct sysctl_netisr_work *snwp;
+ u_int i;
+
+ xo_open_list("work");
+ for (i = 0; i < work_array_len; i++) {
+ snwp = &work_array[i];
+ if (snwp->snw_wsid != snwsp->snws_wsid)
+ continue;
+ xo_open_instance("work");
+ xo_emit("{t:workstream/%4u} ", snwsp->snws_wsid);
+ xo_emit("{t:cpu/%3u} ", snwsp->snws_cpu);
+ xo_emit("{P: }");
+ xo_emit("{t:name/%-6s}", netisr_proto2name(snwp->snw_proto));
+ xo_emit(" {t:length/%5u}", snwp->snw_len);
+ xo_emit(" {t:watermark/%5u}", snwp->snw_watermark);
+ xo_emit(" {t:dispatched/%8ju}", snwp->snw_dispatched);
+ xo_emit(" {t:hybrid-dispatched/%8ju}",
+ snwp->snw_hybrid_dispatched);
+ xo_emit(" {t:queue-drops/%8ju}", snwp->snw_qdrops);
+ xo_emit(" {t:queued/%8ju}", snwp->snw_queued);
+ xo_emit(" {t:handled/%8ju}", snwp->snw_handled);
+ xo_emit("\n");
+ xo_close_instance("work");
+ }
+ xo_close_list("work");
+}
+
+void
+netisr_stats(void)
+{
+ struct sysctl_netisr_workstream *snwsp;
+ struct sysctl_netisr_proto *snpp;
+ u_int i;
+
+ if (live) {
+ netisr_load_sysctl_config();
+ netisr_load_sysctl_proto();
+ netisr_load_sysctl_workstream();
+ netisr_load_sysctl_work();
+ } else {
+ netisr_load_kvm_config();
+ netisr_load_kvm_proto();
+ netisr_load_kvm_workstream(); /* Also does work. */
+ }
+
+ xo_open_container("netisr");
+
+ xo_emit("{T:Configuration}:\n");
+ xo_emit("{T:/%-25s} {T:/%12s} {T:/%12s}\n",
+ "Setting", "Current", "Limit");
+ xo_emit("{T:/%-25s} {T:/%12u} {T:/%12u}\n",
+ "Thread count", numthreads, maxthreads);
+ xo_emit("{T:/%-25s} {T:/%12u} {T:/%12u}\n",
+ "Default queue limit", defaultqlimit, maxqlimit);
+ xo_emit("{T:/%-25s} {T:/%12s} {T:/%12s}\n",
+ "Dispatch policy", dispatch_policy, "n/a");
+ xo_emit("{T:/%-25s} {T:/%12s} {T:/%12s}\n",
+ "Threads bound to CPUs", bindthreads ? "enabled" : "disabled",
+ "n/a");
+ xo_emit("\n");
+
+ xo_emit("{T:Protocols}:\n");
+ xo_emit("{T:/%-6s} {T:/%5s} {T:/%6s} {T:/%-6s} {T:/%-8s} {T:/%-5s}\n",
+ "Name", "Proto", "QLimit", "Policy", "Dispatch", "Flags");
+ xo_open_list("protocol");
+ for (i = 0; i < proto_array_len; i++) {
+ xo_open_instance("protocol");
+ snpp = &proto_array[i];
+ netisr_print_proto(snpp);
+ xo_close_instance("protocol");
+ }
+ xo_close_list("protocol");
+ xo_emit("\n");
+
+ xo_emit("{T:Workstreams}:\n");
+ xo_emit("{T:/%4s} {T:/%3s} ", "WSID", "CPU");
+ xo_emit("{P:/%2s}", "");
+ xo_emit("{T:/%-6s} {T:/%5s} {T:/%5s} {T:/%8s} {T:/%8s} {T:/%8s} "
+ "{T:/%8s} {T:/%8s}\n",
+ "Name", "Len", "WMark", "Disp'd", "HDisp'd", "QDrops", "Queued",
+ "Handled");
+ xo_open_list("workstream");
+ for (i = 0; i < workstream_array_len; i++) {
+ xo_open_instance("workstream");
+ snwsp = &workstream_array[i];
+ netisr_print_workstream(snwsp);
+ xo_close_instance("workstream");
+ }
+ xo_close_list("workstream");
+ xo_close_container("netisr");
+}
diff --git a/usr.bin/netstat/netstat.1 b/usr.bin/netstat/netstat.1
new file mode 100644
index 000000000000..1931c38a1fad
--- /dev/null
+++ b/usr.bin/netstat/netstat.1
@@ -0,0 +1,978 @@
+.\" Copyright (c) 1983, 1990, 1992, 1993
+.\" The Regents of the University of California. 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.
+.\" 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. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
+.\"
+.Dd July 16, 2025
+.Dt NETSTAT 1
+.Os
+.Sh NAME
+.Nm netstat
+.Nd show network status and statistics
+.Sh SYNOPSIS
+.Bk -words
+.Bl -tag -width "netstat"
+.It Nm
+.Op Fl j Ar jail
+.Op Fl -libxo
+.Op Fl 46AaCLnPRSTWx
+.Op Fl f Ar protocol_family | Fl p Ar protocol
+.It Nm Fl i | I Ar interface
+.Op Fl j Ar jail
+.Op Fl -libxo
+.Op Fl 46abdhnW
+.Op Fl f Ar address_family
+.Op Fl M Ar core
+.Op Fl N Ar system
+.It Nm Fl w Ar wait
+.Op Fl j Ar jail
+.Op Fl -libxo
+.Op Fl I Ar interface
+.Op Fl 46d
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Op Fl q Ar howmany
+.It Nm Fl s
+.Op Fl j Ar jail
+.Op Fl -libxo
+.Op Fl 46sz
+.Op Fl f Ar protocol_family | Fl p Ar protocol
+.Op Fl M Ar core
+.Op Fl N Ar system
+.It Nm Fl i | I Ar interface Fl s
+.Op Fl j Ar jail
+.Op Fl -libxo
+.Op Fl 46s
+.Op Fl f Ar protocol_family | Fl p Ar protocol
+.Op Fl M Ar core
+.Op Fl N Ar system
+.It Nm Fl m
+.Op Fl j Ar jail
+.Op Fl -libxo
+.Op Fl M Ar core
+.Op Fl N Ar system
+.It Nm Fl B
+.Op Fl j Ar jail
+.Op Fl -libxo
+.Op Fl z
+.Op Fl I Ar interface
+.It Nm Fl r
+.Op Fl j Ar jail
+.Op Fl -libxo
+.Op Fl 46nW
+.Op Fl F Ar fibnum
+.Op Fl f Ar address_family
+.It Nm Fl rs
+.Op Fl j Ar jail
+.Op Fl -libxo
+.Op Fl s
+.Op Fl M Ar core
+.Op Fl N Ar system
+.It Nm Fl g
+.Op Fl j Ar jail
+.Op Fl -libxo
+.Op Fl 46W
+.Op Fl f Ar address_family
+.It Nm Fl gs
+.Op Fl j Ar jail
+.Op Fl -libxo
+.Op Fl 46s
+.Op Fl f Ar address_family
+.Op Fl M Ar core
+.Op Fl N Ar system
+.It Nm Fl Q
+.Op Fl j Ar jail
+.Op Fl -libxo
+.It Nm Fl o
+.Fl 4 | Fl 6
+.It Nm Fl O
+.Fl 4 | Fl 6
+.El
+.Ek
+.Sh DESCRIPTION
+The
+.Nm
+command shows the contents of various network-related
+data structures.
+The arguments passed determine which of the below output formats the
+command uses.
+.Bl -tag -width indent
+.It Xo
+.Bk -words
+.Nm
+.Op Fl 46AaCLnRSTWx
+.Op Fl f Ar protocol_family | Fl p Ar protocol
+.Op Fl j Ar jail
+.Ek
+.Xc
+Display a list of active sockets
+(protocol control blocks)
+for each network protocol.
+.Pp
+The default display for active sockets shows the local
+and remote addresses, send and receive queue sizes (in bytes), protocol,
+and the internal state of the protocol.
+Address formats are of the form
+.Dq host.port
+or
+.Dq network.port
+if a socket's address specifies a network but no specific host address.
+When known, the host and network addresses are displayed symbolically
+according to the databases
+.Xr hosts 5
+and
+.Xr networks 5 ,
+respectively.
+If a symbolic name for an address is unknown, or if
+the
+.Fl n
+option is specified, the address is printed numerically, according
+to the address family.
+For more information regarding
+the Internet IPv4
+.Dq dot format ,
+refer to
+.Xr inet 3 .
+Unspecified,
+or
+.Dq wildcard ,
+addresses and ports appear as
+.Dq Li * .
+.Bl -tag -width indent
+.It Fl -libxo
+Generate output via
+.Xr libxo 3
+in a selection of different human and machine readable formats.
+See
+.Xr xo_options 7
+for details on command line arguments.
+.It Fl 4
+Show IPv4 only.
+See
+.Sx GENERAL OPTIONS .
+.It Fl 6
+Show IPv6 only.
+See
+.Sx GENERAL OPTIONS .
+.It Fl A
+Show the address of a protocol control block (PCB)
+associated with a socket; used for debugging.
+.It Fl a
+Show the state of all sockets;
+normally sockets used by server processes are not shown.
+.It Fl c
+Show the used TCP stack for each session.
+.It Fl C
+Show the congestion control algorithm and diagnostic information of TCP sockets.
+.It Fl L
+Show the size of the various listen queues.
+The first count shows the number of unaccepted connections,
+the second count shows the amount of unaccepted incomplete connections,
+and the third count is the maximum number of queued connections.
+.It Fl n
+Do not resolve numeric addresses and port numbers to names.
+See
+.Sx GENERAL OPTIONS .
+.It Fl P
+Display the log ID for each socket.
+.It Fl R
+Display the flowid and flowtype for each socket.
+flowid is a 32 bit hardware specific identifier for each flow.
+flowtype defines which protocol fields are hashed to produce the id.
+A complete listing is available in
+.Pa sys/mbuf.h
+under
+.Dv M_HASHTYPE_* .
+.It Fl S
+Show network addresses as numbers (as with
+.Fl n )
+but show ports symbolically.
+.It Fl T
+Display diagnostic information from the TCP control block.
+Fields include the number of packets requiring retransmission,
+received out-of-order, and those advertising a zero-sized window.
+.It Fl W
+Avoid truncating addresses even if this causes some fields to overflow.
+.It Fl x
+Display socket buffer and TCP timer statistics for each
+internet socket.
+.Pp
+The
+.Fl x
+flag causes
+.Nm
+to output all the information recorded about data
+stored in the socket buffers.
+The fields are:
+.Bl -column ".Li R-HIWA"
+.It Li R-HIWA Ta Receive buffer high water mark, in bytes.
+.It Li S-HIWA Ta Send buffer high water mark, in bytes.
+.It Li R-LOWA Ta Receive buffer low water mark, in bytes.
+.It Li S-LOWA Ta Send buffer low water mark, in bytes.
+.It Li R-BCNT Ta Receive buffer byte count.
+.It Li S-BCNT Ta Send buffer byte count.
+.It Li R-BMAX Ta Maximum bytes that can be used in the receive buffer.
+.It Li S-BMAX Ta Maximum bytes that can be used in the send buffer.
+.It Li rexmt Ta Time, in seconds, to fire Retransmit Timer, or 0 if not armed.
+.It Li persist Ta Time, in seconds, to fire Retransmit Persistence, or 0 if not armed.
+.It Li keep Ta Time, in seconds, to fire Keep Alive, or 0 if not armed.
+.It Li 2msl Ta Time, in seconds, to fire 2*msl TIME_WAIT Timer, or 0 if not armed.
+.It Li delack Ta Time, in seconds, to fire Delayed ACK Timer, or 0 if not armed.
+.It Li rcvtime Ta Time, in seconds, since last packet received.
+.El
+.It Fl f Ar protocol_family
+Filter by
+.Ar protocol_family .
+See
+.Sx GENERAL OPTIONS .
+.It Fl p Ar protocol
+Filter by
+.Ar protocol .
+See
+.Sx GENERAL OPTIONS .
+.It Fl j Ar jail
+Run inside a jail.
+See
+.Sx GENERAL OPTIONS .
+.El
+.It Xo
+.Bk -words
+.Nm
+.Fl i | I Ar interface
+.Op Fl 46abdhnW
+.Op Fl f Ar address_family
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Op Fl j Ar jail
+.Ek
+.Xc
+Show the state of all network interfaces or a single
+.Ar interface
+which have been auto-configured
+(interfaces statically configured into a system, but not
+located at boot time are not shown).
+An asterisk
+.Pq Dq Li *
+after an interface name indicates that the interface is
+.Dq down .
+.Pp
+When
+.Nm
+is invoked with
+.Fl i
+.Pq all interfaces
+or
+.Fl I Ar interface ,
+it provides a table of cumulative
+statistics regarding packets transferred, errors, and collisions.
+The network addresses of the interface
+and the maximum transmission unit
+.Pq Dq mtu
+are also displayed.
+If both
+.Fl i
+and
+.Fl I
+are specified,
+.Fl I
+overrides any instances of
+.Fl i .
+.Bl -tag -width indent
+.It Fl 4
+Show IPv4 only.
+See
+.Sx GENERAL OPTIONS .
+.It Fl 6
+Show IPv6 only.
+See
+.Sx GENERAL OPTIONS .
+.It Fl a
+Multicast addresses currently in use are shown
+for each Ethernet interface and for each IP interface address.
+Multicast addresses are shown on separate lines following the interface
+address with which they are associated.
+.It Fl b
+Show the number of bytes in and out.
+.It Fl d
+Show the number of dropped output packets.
+.It Fl h
+Print all counters in human readable form.
+.It Fl n
+Do not resolve numeric addresses and port numbers to names.
+See
+.Sx GENERAL OPTIONS .
+.It Fl W
+Avoid truncating addresses even if this causes some fields to overflow.
+See
+.Sx GENERAL OPTIONS .
+However, in most cases field widths are determined automatically with the
+.Fl i
+option, and this option has little effect.
+.It Fl f Ar protocol_family
+Filter by
+.Ar protocol_family .
+See
+.Sx GENERAL OPTIONS .
+.It Fl j Ar jail
+Run inside a jail.
+See
+.Sx GENERAL OPTIONS .
+.El
+.It Xo
+.Bk -words
+.Nm
+.Fl w Ar wait
+.Op Fl I Ar interface
+.Op Fl 46d
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Op Fl q Ar howmany
+.Op Fl j Ar jail
+.Ek
+.Xc
+At intervals of
+.Ar wait
+seconds, display the information regarding packet traffic on all
+configured network interfaces or a single
+.Ar interface .
+.Pp
+When
+.Nm
+is invoked with the
+.Fl w
+option and a
+.Ar wait
+interval argument, it displays a running count of statistics related to
+network interfaces.
+An obsolescent version of this option used a numeric parameter
+with no option, and is currently supported for backward compatibility.
+By default, this display summarizes information for all interfaces.
+Information for a specific interface may be displayed with the
+.Fl I Ar interface
+option.
+.Bl -tag -width indent
+.It Fl I Ar interface
+Only show information regarding
+.Ar interface
+.It Fl 4
+Show IPv4 only.
+See
+.Sx GENERAL OPTIONS .
+.It Fl 6
+Show IPv6 only.
+See
+.Sx GENERAL OPTIONS .
+.It Fl d
+Show the number of dropped output packets.
+.It Fl M
+Use an alternative core.
+See
+.Sx GENERAL OPTIONS .
+.It Fl N
+Use an alternative kernel image.
+See
+.Sx GENERAL OPTIONS .
+.It Fl q
+Exit after
+.Ar howmany
+outputs.
+.It Fl j Ar jail
+Run inside a jail.
+See
+.Sx GENERAL OPTIONS .
+.El
+.It Xo
+.Bk -words
+.Nm netstat
+.Fl o
+.Fl 4 | Fl 6
+.Ek
+.Xc
+Print nexthop (nhops) information associated with routing entries.
+When used with
+.Fl 4
+or
+.Fl 6 ,
+limit the output to IPv4 or IPv6 routes respectively.
+This option provides details about individual nexthop addresses
+used in routing decisions.
+.It Xo
+.Bk -words
+.Nm netstat
+.Fl O
+.Fl 4 | Fl 6
+.Ek
+.Xc
+Print nexthop groups (nhgrp) information associated with routing entries.
+When used with
+.Fl 4
+or
+.Fl 6 ,
+restrict the output to IPv4 or IPv6 nexthop groups respectively.
+This option shows grouped nexthop entries for multipath or
+load-balanced routing setups.
+.It Xo
+.Bk -words
+.Nm
+.Fl s
+.Op Fl 46sz
+.Op Fl f Ar protocol_family | Fl p Ar protocol
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Op Fl j Ar jail
+.Ek
+.Xc
+Display system-wide statistics for each network protocol.
+.Bl -tag -width indent
+.It Fl 4
+Show IPv4 only.
+See
+.Sx GENERAL OPTIONS .
+.It Fl 6
+Show IPv6 only.
+See
+.Sx GENERAL OPTIONS .
+.It Fl s
+If
+.Fl s
+is repeated, counters with a value of zero are suppressed.
+.It Fl z
+Reset statistic counters after displaying them.
+.It Fl f Ar protocol_family
+Filter by
+.Ar protocol_family .
+See
+.Sx GENERAL OPTIONS .
+.It Fl p Ar protocol
+Filter by
+.Ar protocol .
+See
+.Sx GENERAL OPTIONS .
+.It Fl M
+Use an alternative core.
+See
+.Sx GENERAL OPTIONS .
+.It Fl N
+Use an alternative kernel image
+See
+.Sx GENERAL OPTIONS .
+.It Fl j Ar jail
+Run inside a jail.
+See
+.Sx GENERAL OPTIONS .
+.El
+.It Xo
+.Bk -words
+.Nm
+.Fl i | I Ar interface Fl s
+.Op Fl 46s
+.Op Fl f Ar protocol_family | Fl p Ar protocol
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Op Fl j Ar jail
+.Ek
+.Xc
+Display per-interface statistics for each network protocol.
+If both
+.Fl i
+and
+.Fl I
+are specified,
+.Fl I
+overrides any instances of
+.Fl i .
+.Bl -tag -width indent
+.It Fl 4
+Show IPv4 only
+See
+.Sx GENERAL OPTIONS .
+.It Fl 6
+Show IPv6 only
+See
+.Sx GENERAL OPTIONS .
+.It Fl s
+If
+.Fl s
+is repeated, counters with a value of zero are suppressed.
+.It Fl f Ar protocol_family
+Filter by
+.Ar protocol_family .
+See
+.Sx GENERAL OPTIONS .
+.It Fl p Ar protocol
+Filter by
+.Ar protocol .
+See
+.Sx GENERAL OPTIONS .
+.It Fl M
+Use an alternative core
+See
+.Sx GENERAL OPTIONS .
+.It Fl N
+Use an alternative kernel image
+See
+.Sx GENERAL OPTIONS .
+.It Fl j Ar jail
+Run inside a jail.
+See
+.Sx GENERAL OPTIONS .
+.El
+.It Xo
+.Bk -words
+.Nm
+.Fl m
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Op Fl j Ar jail
+.Ek
+.Xc
+Show statistics recorded by the memory management routines
+.Pq Xr mbuf 9 .
+The network manages a private pool of memory buffers.
+.Bl -tag -width indent
+.It Fl M
+Use an alternative core
+See
+.Sx GENERAL OPTIONS .
+.It Fl N
+Use an alternative kernel image
+See
+.Sx GENERAL OPTIONS .
+.It Fl j Ar jail
+Run inside a jail.
+See
+.Sx GENERAL OPTIONS .
+.El
+.It Xo
+.Bk -words
+.Nm
+.Fl B
+.Op Fl z
+.Op Fl I Ar interface
+.Op Fl j Ar jail
+.Ek
+.Xc
+Show statistics about
+.Xr bpf 4
+peers.
+This includes information like
+how many packets have been matched, dropped and received by the
+bpf device, also information about current buffer sizes and device
+states.
+.Pp
+The
+.Xr bpf 4
+flags displayed when
+.Nm
+is invoked with the
+.Fl B
+option represent the underlying parameters of the bpf peer.
+Each flag is
+represented as a single lower case letter.
+The mapping between the letters and flags in order of appearance are:
+.Bl -column ".Li i"
+.It Li p Ta Set if listening promiscuously
+.It Li i Ta Dv BIOCIMMEDIATE No has been set on the device
+.It Li f Ta Dv BIOCGHDRCMPLT No status: source link addresses are being
+filled automatically
+.It Li s Ta Dv BIOCGSEESENT No status: see packets originating locally and
+remotely on the interface.
+.It Li a Ta Packet reception generates a signal
+.It Li l Ta Dv BIOCLOCK No status: descriptor has been locked
+.El
+.Pp
+For more information about these flags, please refer to
+.Xr bpf 4 .
+.Bl -tag -width indent
+.It Fl z
+Reset statistic counters after displaying them.
+.It Fl j Ar jail
+Run inside a jail.
+See
+.Sx GENERAL OPTIONS .
+.El
+.It Xo
+.Bk -words
+.Nm
+.Fl r
+.Op Fl 46AnW
+.Op Fl F Ar fibnum
+.Op Fl f Ar address_family
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Op Fl j Ar jail
+.Ek
+.Xc
+Display the contents of routing tables.
+.Pp
+When
+.Nm
+is invoked with the routing table option
+.Fl r ,
+it lists the available routes and their status.
+Each route consists of a destination host or network, and a gateway to use
+in forwarding packets.
+The flags field shows a collection of information about the route stored
+as binary choices.
+The individual flags are discussed in more detail in the
+.Xr route 8
+and
+.Xr route 4
+manual pages.
+The mapping between letters and flags is:
+.Bl -column ".Li W" ".Dv RTF_WASCLONED"
+.It Li 1 Ta Dv RTF_PROTO1 Ta "Protocol specific routing flag #1"
+.It Li 2 Ta Dv RTF_PROTO2 Ta "Protocol specific routing flag #2"
+.It Li 3 Ta Dv RTF_PROTO3 Ta "Protocol specific routing flag #3"
+.It Li B Ta Dv RTF_BLACKHOLE Ta "Just discard pkts (during updates)"
+.It Li b Ta Dv RTF_BROADCAST Ta "The route represents a broadcast address"
+.It Li D Ta Dv RTF_DYNAMIC Ta "Created dynamically (by redirect)"
+.It Li G Ta Dv RTF_GATEWAY Ta "Destination requires forwarding by intermediary"
+.It Li H Ta Dv RTF_HOST Ta "Host entry (net otherwise)"
+.It Li L Ta Dv RTF_LLINFO Ta "Valid protocol to link address translation"
+.It Li M Ta Dv RTF_MODIFIED Ta "Modified dynamically (by redirect)"
+.It Li R Ta Dv RTF_REJECT Ta "Host or net unreachable"
+.It Li S Ta Dv RTF_STATIC Ta "Manually added"
+.It Li U Ta Dv RTF_UP Ta "Route usable"
+.It Li X Ta Dv RTF_XRESOLVE Ta "External daemon translates proto to link address"
+.El
+.Pp
+Direct routes are created for each
+interface attached to the local host;
+the gateway field for such entries shows the address of the outgoing interface.
+The refcnt field gives the
+current number of active uses of the route.
+Connection oriented
+protocols normally hold on to a single route for the duration of
+a connection while connectionless protocols obtain a route while sending
+to the same destination.
+The use field provides a count of the number of packets
+sent using that route.
+The interface entry indicates the network interface utilized for the route.
+.Bl -tag -width indent
+.It Fl 4
+Show IPv4 only.
+See
+.Sx GENERAL OPTIONS .
+.It Fl 6
+Show IPv6 only.
+See
+.Sx GENERAL OPTIONS .
+.It Fl n
+Do not resolve numeric addresses and port numbers to names.
+See
+.Sx GENERAL OPTIONS .
+.It Fl W
+Show the path MTU for each route, and print interface names with a
+wider field size.
+.It Fl F
+Display the routing table with the number
+.Ar fibnum .
+If the specified
+.Ar fibnum
+is -1 or
+.Fl F
+is not specified,
+the default routing table is displayed.
+.It Fl f
+Display the routing table for a particular
+.Ar address_family .
+.It Fl M
+Use an alternative core
+See
+.Sx GENERAL OPTIONS .
+.It Fl N
+Use an alternative kernel image
+See
+.Sx GENERAL OPTIONS .
+.It Fl j Ar jail
+Run inside a jail.
+See
+.Sx GENERAL OPTIONS .
+.El
+.It Xo
+.Bk -words
+.Nm
+.Fl rs
+.Op Fl s
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Op Fl j Ar jail
+.Ek
+.Xc
+Display routing statistics.
+.Bl -tag -width indent
+.It Fl s
+If
+.Fl s
+is repeated, counters with a value of zero are suppressed.
+.It Fl M
+Use an alternative core
+See
+.Sx GENERAL OPTIONS .
+.It Fl N
+Use an alternative kernel image
+See
+.Sx GENERAL OPTIONS .
+.It Fl j Ar jail
+Run inside a jail.
+See
+.Sx GENERAL OPTIONS .
+.El
+.It Xo
+.Bk -words
+.Nm
+.Fl g
+.Op Fl 46W
+.Op Fl f Ar address_family
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Op Fl j Ar jail
+.Ek
+.Xc
+Display the contents of the multicast virtual interface tables,
+and multicast forwarding caches.
+Entries in these tables will appear only when the kernel is
+actively forwarding multicast sessions.
+This option is applicable only to the
+.Cm inet
+and
+.Cm inet6
+address families.
+.Bl -tag -width indent
+.It Fl 4
+Show IPv4 only
+See
+.Sx GENERAL OPTIONS .
+.It Fl 6
+Show IPv6 only
+See
+.Sx GENERAL OPTIONS .
+.It Fl W
+Avoid truncating addresses even if this causes some fields to overflow.
+.It Fl f Ar protocol_family
+Filter by
+.Ar protocol_family .
+See
+.Sx GENERAL OPTIONS .
+.It Fl M
+Use an alternative core
+See
+.Sx GENERAL OPTIONS .
+.It Fl N
+Use an alternative kernel image
+See
+.Sx GENERAL OPTIONS .
+.It Fl j Ar jail
+Run inside a jail.
+See
+.Sx GENERAL OPTIONS .
+.El
+.It Xo
+.Bk -words
+.Nm
+.Fl gs
+.Op Fl 46s
+.Op Fl f Ar address_family
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Op Fl j Ar jail
+.Ek
+.Xc
+Show multicast routing statistics.
+.Bl -tag -width indent
+.It Fl 4
+Show IPv4 only
+See
+.Sx GENERAL OPTIONS .
+.It Fl 6
+Show IPv6 only
+See
+.Sx GENERAL OPTIONS .
+.It Fl s
+If
+.Fl s
+is repeated, counters with a value of zero are suppressed.
+.It Fl f Ar protocol_family
+Filter by
+.Ar protocol_family .
+See
+.Sx GENERAL OPTIONS .
+.It Fl M
+Use an alternative core
+See
+.Sx GENERAL OPTIONS .
+.It Fl N
+Use an alternative kernel image
+See
+.Sx GENERAL OPTIONS .
+.It Fl j Ar jail
+Run inside a jail.
+See
+.Sx GENERAL OPTIONS .
+.El
+.It Xo
+.Bk -words
+.Nm
+.Fl Q
+.Op Fl j Ar jail
+.Ek
+.Xc
+Show
+.Xr netisr 9
+statistics.
+The flags field shows available ISR handlers:
+.Bl -column ".Li W" ".Dv NETISR_SNP_FLAGS_DRAINEDCPU"
+.It Li C Ta Dv NETISR_SNP_FLAGS_M2CPUID Ta "Able to map mbuf to cpu id"
+.It Li D Ta Dv NETISR_SNP_FLAGS_DRAINEDCPU Ta "Has queue drain handler"
+.It Li F Ta Dv NETISR_SNP_FLAGS_M2FLOW Ta "Able to map mbuf to flow id"
+.It Fl j Ar jail
+Run inside a jail.
+See
+.Sx GENERAL OPTIONS .
+.El
+.El
+.Ss GENERAL OPTIONS
+Some options have the general meaning:
+.Bl -tag -width flag
+.It Fl 4
+Is shorthand for
+.Fl f
+.Ar inet
+.Pq Show only IPv4
+.It Fl 6
+Is shorthand for
+.Fl f
+.Ar inet6
+.Pq Show only IPv6
+.It Fl f Ar address_family , Fl p Ar protocol
+Limit display to those records
+of the specified
+.Ar address_family
+or a single
+.Ar protocol .
+The following address families and protocols are recognized:
+.Pp
+.Bl -tag -width ".Cm netgraph , ng Pq Dv AF_NETGRAPH" -compact
+.It Em Family
+.Em Protocols
+.It Cm inet Pq Dv AF_INET
+.Cm divert , icmp , igmp , ip , ipsec , pim, sctp , tcp , udp
+.It Cm inet6 Pq Dv AF_INET6
+.Cm icmp6 , ip6 , ipsec6 , rip6 , sctp , tcp , udp
+.It Cm pfkey Pq Dv PF_KEY
+.Cm pfkey
+.It Cm netgraph , ng Pq Dv AF_NETGRAPH
+.Cm ctrl , data
+.It Cm unix Pq Dv AF_UNIX
+.It Cm link Pq Dv AF_LINK
+.El
+.Pp
+The program will complain if
+.Ar protocol
+is unknown or if there is no statistics routine for it.
+.It Fl M
+Extract values associated with the name list from the specified core
+instead of the default
+.Pa /dev/kmem .
+.It Fl N
+Extract the name list from the specified system instead of the default,
+which is the kernel image the system has booted from.
+.It Fl n
+Show network addresses and ports as numbers.
+Normally
+.Nm
+attempts to resolve addresses and ports,
+and display them symbolically.
+Specifying
+.Fl n
+twice will also disable printing the keyword
+.Qq Dv default
+for the default IPv4 and IPv6 routes when displaying contents of routing
+tables.
+.It Fl W
+Wider output; expand address fields, etc, to avoid truncation.
+Non-numeric values such as domain names may still be truncated; use the
+.Fl n
+option if necessary to avoid ambiguity.
+.It Fl j Ar jail
+Perform the actions inside the
+.Ar jail .
+This allows network state to be accessed even if the
+.Cm netstat
+binary is not available in the
+.Ar jail .
+.El
+.Sh EXAMPLES
+Show packet traffic information (packets, bytes, errors, packet drops, etc) for
+interface re0 updated every 2 seconds and exit after 5 outputs:
+.Pp
+.Dl netstat -w 2 -q 5 -I re0
+.Pp
+Show statistics for ICMP on any interface:
+.Pp
+.Dl netstat -s -p icmp
+.Pp
+Show routing tables:
+.Pp
+.Dl netstat -r
+.Pp
+Same as above, but without resolving numeric addresses and port numbers to
+names:
+.Pp
+.Dl netstat -rn
+.Pp
+Show IPv4 listening sockets:
+.Pp
+.Dl netstat -4l
+.Sh SEE ALSO
+.Xr fstat 1 ,
+.Xr nfsstat 1 ,
+.Xr procstat 1 ,
+.Xr ps 1 ,
+.Xr sockstat 1 ,
+.Xr libxo 3 ,
+.Xr xo_options 7 ,
+.Xr bpf 4 ,
+.Xr inet 4 ,
+.Xr route 4 ,
+.Xr unix 4 ,
+.Xr hosts 5 ,
+.Xr networks 5 ,
+.Xr protocols 5 ,
+.Xr services 5 ,
+.Xr iostat 8 ,
+.Xr route 8 ,
+.Xr vmstat 8 ,
+.Xr mbuf 9
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.2 .
+.Pp
+IPv6 support was added by WIDE/KAME project.
+.Sh BUGS
+The notion of errors is ill-defined.
diff --git a/usr.bin/netstat/netstat.h b/usr.bin/netstat/netstat.h
new file mode 100644
index 000000000000..1255bfdf2e57
--- /dev/null
+++ b/usr.bin/netstat/netstat.h
@@ -0,0 +1,168 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1992, 1993
+ * Regents of the University of California. 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.
+ * 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. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#define NETSTAT_XO_VERSION "1"
+
+#define satosin(sa) ((struct sockaddr_in *)(sa))
+#define satosin6(sa) ((struct sockaddr_in6 *)(sa))
+#define sin6tosa(sin6) ((struct sockaddr *)(sin6))
+
+extern bool Aflag; /* show addresses of protocol control block */
+extern bool aflag; /* show all sockets (including servers) */
+extern bool bflag; /* show i/f total bytes in/out */
+extern bool cflag; /* show congestion control stats */
+extern bool Cflag; /* show congestion control algo and stack */
+extern bool dflag; /* show i/f dropped packets */
+extern bool gflag; /* show group (multicast) routing or stats */
+extern bool hflag; /* show counters in human readable format */
+extern bool iflag; /* show interfaces */
+extern bool Lflag; /* show size of listen queues */
+extern bool mflag; /* show memory stats */
+extern int noutputs; /* how much outputs before we exit */
+extern u_int numeric_addr; /* show addresses numerically */
+extern bool numeric_port; /* show ports numerically */
+extern bool Pflag; /* show TCP log ID */
+extern bool rflag; /* show routing tables (or routing stats) */
+extern bool Rflag; /* show flowid / RSS information */
+extern int sflag; /* show protocol statistics */
+extern bool Tflag; /* show TCP control block info */
+extern bool Wflag; /* wide display */
+extern bool xflag; /* extended display, includes all socket buffer info */
+extern bool zflag; /* zero stats */
+
+extern int interval; /* repeat interval for i/f stats */
+
+extern char *interface; /* desired i/f for stats, or NULL for all i/fs */
+extern int unit; /* unit number for above */
+
+extern int live; /* true if we are examining a live system */
+
+typedef int kreadfn_t(u_long, void *, size_t);
+int fetch_stats(const char *, u_long, void *, size_t, kreadfn_t);
+int fetch_stats_ro(const char *, u_long, void *, size_t, kreadfn_t);
+
+int kread(u_long addr, void *buf, size_t size);
+uint64_t kread_counter(u_long addr);
+int kread_counters(u_long addr, void *buf, size_t size);
+void kset_dpcpu(u_int);
+const char *plural(uintmax_t);
+const char *plurales(uintmax_t);
+const char *pluralies(uintmax_t);
+
+struct sockaddr;
+struct socket;
+struct xsocket;
+int sotoxsocket(struct socket *, struct xsocket *);
+void protopr(u_long, const char *, int, int);
+void tcp_stats(u_long, const char *, int, int);
+void udp_stats(u_long, const char *, int, int);
+#ifdef SCTP
+void sctp_protopr(u_long, const char *, int, int);
+void sctp_stats(u_long, const char *, int, int);
+#endif
+void arp_stats(u_long, const char *, int, int);
+void divert_stats(u_long, const char *, int, int);
+void ip_stats(u_long, const char *, int, int);
+void icmp_stats(u_long, const char *, int, int);
+void igmp_stats(u_long, const char *, int, int);
+void pim_stats(u_long, const char *, int, int);
+void carp_stats(u_long, const char *, int, int);
+void pfsync_stats(u_long, const char *, int, int);
+void pflow_stats(u_long, const char *, int, int);
+#ifdef IPSEC
+void ipsec_stats(u_long, const char *, int, int);
+void esp_stats(u_long, const char *, int, int);
+void ah_stats(u_long, const char *, int, int);
+void ipcomp_stats(u_long, const char *, int, int);
+#endif
+
+#ifdef INET
+struct in_addr;
+
+char *inetname(struct in_addr *);
+#endif
+
+#ifdef INET6
+struct in6_addr;
+
+char *inet6name(struct in6_addr *);
+void ip6_stats(u_long, const char *, int, int);
+void ip6_ifstats(char *);
+void icmp6_stats(u_long, const char *, int, int);
+void icmp6_ifstats(char *);
+void pim6_stats(u_long, const char *, int, int);
+void rip6_stats(u_long, const char *, int, int);
+void mroute6pr(void);
+void mrt6_stats(void);
+
+struct sockaddr_in6;
+struct in6_addr;
+void in6_fillscopeid(struct sockaddr_in6 *);
+void inet6print(const char *, struct in6_addr *, int, const char *, int);
+#endif /*INET6*/
+
+#ifdef IPSEC
+void pfkey_stats(u_long, const char *, int, int);
+#endif
+
+void mbpr(void *, u_long);
+
+void netisr_stats(void);
+
+void hostpr(u_long, u_long);
+void impstats(u_long, u_long);
+
+void intpr(void (*)(char *), int);
+
+void pr_family(int);
+void rt_stats(void);
+
+char *routename(struct sockaddr *, int);
+const char *netname(struct sockaddr *, struct sockaddr *);
+void routepr(int, int);
+int p_sockaddr(const char *name, struct sockaddr *sa,
+ struct sockaddr *mask, int flags, int width);
+const char *fmt_sockaddr(struct sockaddr *sa, struct sockaddr *mask,
+ int flags);
+
+#ifdef NETGRAPH
+void netgraphprotopr(u_long, const char *, int, int);
+#endif
+
+void unixpr(u_long, u_long, u_long, u_long, u_long, bool *);
+
+void mroutepr(void);
+void mrt_stats(void);
+void bpf_stats(char *);
+void nhops_print(int fibnum, int af);
+void nhgrp_print(int fibnum, int af);
diff --git a/usr.bin/netstat/nhgrp.c b/usr.bin/netstat/nhgrp.c
new file mode 100644
index 000000000000..7cbaa1af94e3
--- /dev/null
+++ b/usr.bin/netstat/nhgrp.c
@@ -0,0 +1,352 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020 Alexander V. Chernikov
+ *
+ * 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.
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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/socket.h>
+#include <sys/sysctl.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+#include <net/route/nhop.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+#include "common.h"
+
+#define WID_GW_DEFAULT(af) (((af) == AF_INET6) ? 40 : 18)
+
+static int wid_gw;
+static int wid_if = 10;
+static int wid_nhidx = 8;
+static int wid_refcnt = 8;
+
+struct nhop_entry {
+ char gw[64];
+ char ifname[IFNAMSIZ];
+};
+
+struct nhop_map {
+ struct nhop_entry *ptr;
+ size_t size;
+};
+static struct nhop_map global_nhop_map;
+
+static struct ifmap_entry *ifmap;
+static size_t ifmap_size;
+
+static struct nhop_entry *
+nhop_get(struct nhop_map *map, uint32_t idx)
+{
+
+ if (idx >= map->size)
+ return (NULL);
+ if (*map->ptr[idx].ifname == '\0')
+ return (NULL);
+ return &map->ptr[idx];
+}
+
+static void
+print_nhgroup_header(int af1 __unused)
+{
+
+ xo_emit("{T:/%-*.*s}{T:/%-*.*s}{T:/%*.*s}{T:/%*.*s}{T:/%*.*s}"
+ "{T:/%*.*s}{T:/%*s}\n",
+ wid_nhidx, wid_nhidx, "GrpIdx",
+ wid_nhidx, wid_nhidx, "NhIdx",
+ wid_nhidx, wid_nhidx, "Weight",
+ wid_nhidx, wid_nhidx, "Slots",
+ wid_gw, wid_gw, "Gateway",
+ wid_if, wid_if, "Netif",
+ wid_refcnt, "Refcnt");
+}
+
+static void
+print_padding(char sym, int len)
+{
+ char buffer[56];
+
+ memset(buffer, sym, sizeof(buffer));
+ buffer[0] = '{';
+ buffer[1] = 'P';
+ buffer[2] = ':';
+ buffer[3] = ' ';
+ buffer[len + 3] = '}';
+ buffer[len + 4] = '\0';
+ xo_emit(buffer);
+}
+
+
+static void
+print_nhgroup_entry_sysctl(const char *name, struct rt_msghdr *rtm,
+ struct nhgrp_external *nhge)
+{
+ char buffer[128];
+ struct nhop_entry *ne;
+ struct nhgrp_nhop_external *ext_cp, *ext_dp;
+ struct nhgrp_container *nhg_cp, *nhg_dp;
+
+ nhg_cp = (struct nhgrp_container *)(nhge + 1);
+ if (nhg_cp->nhgc_type != NHG_C_TYPE_CNHOPS || nhg_cp->nhgc_subtype != 0)
+ return;
+ ext_cp = (struct nhgrp_nhop_external *)(nhg_cp + 1);
+
+ nhg_dp = (struct nhgrp_container *)((char *)nhg_cp + nhg_cp->nhgc_len);
+ if (nhg_dp->nhgc_type != NHG_C_TYPE_DNHOPS || nhg_dp->nhgc_subtype != 0)
+ return;
+ ext_dp = (struct nhgrp_nhop_external *)(nhg_dp + 1);
+
+ xo_open_instance(name);
+
+ snprintf(buffer, sizeof(buffer), "{[:-%d}{:nhgrp-index/%%lu}{]:} ", wid_nhidx);
+
+ xo_emit(buffer, nhge->nhg_idx);
+
+ /* nhidx */
+ print_padding('-', wid_nhidx);
+ /* weight */
+ print_padding('-', wid_nhidx);
+ /* slots */
+ print_padding('-', wid_nhidx);
+ print_padding('-', wid_gw);
+ print_padding('-', wid_if);
+ xo_emit("{t:nhg-refcnt/%*lu}", wid_refcnt, nhge->nhg_refcount);
+ xo_emit("\n");
+
+ xo_open_list("nhop-weights");
+ for (uint32_t i = 0; i < nhg_cp->nhgc_count; i++) {
+ /* TODO: optimize slots calculations */
+ uint32_t slots = 0;
+ for (uint32_t sidx = 0; sidx < nhg_dp->nhgc_count; sidx++) {
+ if (ext_dp[sidx].nh_idx == ext_cp[i].nh_idx)
+ slots++;
+ }
+ xo_open_instance("nhop-weight");
+ print_padding(' ', wid_nhidx);
+ // nh index
+ xo_emit("{t:nh-index/%*lu}", wid_nhidx, ext_cp[i].nh_idx);
+ xo_emit("{t:nh-weight/%*lu}", wid_nhidx, ext_cp[i].nh_weight);
+ xo_emit("{t:nh-slots/%*lu}", wid_nhidx, slots);
+ ne = nhop_get(&global_nhop_map, ext_cp[i].nh_idx);
+ if (ne != NULL) {
+ xo_emit("{t:nh-gw/%*.*s}", wid_gw, wid_gw, ne->gw);
+ xo_emit("{t:nh-interface/%*.*s}", wid_if, wid_if, ne->ifname);
+ }
+ xo_emit("\n");
+ xo_close_instance("nhop-weight");
+ }
+ xo_close_list("nhop-weights");
+ xo_close_instance(name);
+}
+
+static int
+cmp_nhg_idx(const void *_a, const void *_b)
+{
+ const struct nhops_map *a, *b;
+
+ a = _a;
+ b = _b;
+
+ if (a->idx > b->idx)
+ return (1);
+ else if (a->idx < b->idx)
+ return (-1);
+ return (0);
+}
+
+static void
+dump_nhgrp_sysctl(int fibnum, int af, struct nhops_dump *nd)
+{
+ size_t needed;
+ int mib[7];
+ char *buf, *next, *lim;
+ struct rt_msghdr *rtm;
+ struct nhgrp_external *nhg;
+ struct nhops_map *nhg_map;
+ size_t nhg_count, nhg_size;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = af;
+ mib[4] = NET_RT_NHGRP;
+ mib[5] = 0;
+ mib[6] = fibnum;
+ if (sysctl(mib, nitems(mib), NULL, &needed, NULL, 0) < 0)
+ xo_err(EX_OSERR, "sysctl: net.route.0.%d.nhgrpdump.%d estimate",
+ af, fibnum);
+ if ((buf = malloc(needed)) == NULL)
+ xo_errx(EX_OSERR, "malloc(%lu)", (unsigned long)needed);
+ if (sysctl(mib, nitems(mib), buf, &needed, NULL, 0) < 0)
+ xo_err(EX_OSERR, "sysctl: net.route.0.%d.nhgrpdump.%d", af, fibnum);
+ lim = buf + needed;
+
+ /*
+ * nexhops groups are received unsorted. Collect everything first,
+ * and sort prior displaying.
+ */
+ nhg_count = 0;
+ nhg_size = 16;
+ nhg_map = calloc(nhg_size, sizeof(struct nhops_map));
+ for (next = buf; next < lim; next += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)next;
+ if (rtm->rtm_version != RTM_VERSION)
+ continue;
+
+ if (nhg_count >= nhg_size) {
+ nhg_size *= 2;
+ nhg_map = realloc(nhg_map, nhg_size * sizeof(struct nhops_map));
+ }
+
+ nhg = (struct nhgrp_external *)(rtm + 1);
+ nhg_map[nhg_count].idx = nhg->nhg_idx;
+ nhg_map[nhg_count].rtm = rtm;
+ nhg_count++;
+ }
+
+ if (nhg_count > 0)
+ qsort(nhg_map, nhg_count, sizeof(struct nhops_map), cmp_nhg_idx);
+ nd->nh_buf = buf;
+ nd->nh_count = nhg_count;
+ nd->nh_map = nhg_map;
+}
+
+static void
+print_nhgrp_sysctl(int fibnum, int af)
+{
+ struct nhops_dump nd;
+ struct nhgrp_external *nhg;
+ struct rt_msghdr *rtm;
+
+ dump_nhgrp_sysctl(fibnum, af, &nd);
+
+ xo_open_container("nhgrp-table");
+ xo_open_list("rt-family");
+ if (nd.nh_count > 0) {
+ wid_gw = WID_GW_DEFAULT(af);
+ xo_open_instance("rt-family");
+ pr_family(af);
+ xo_open_list("nhgrp-entry");
+
+ print_nhgroup_header(af);
+
+ for (size_t i = 0; i < nd.nh_count; i++) {
+ rtm = nd.nh_map[i].rtm;
+ nhg = (struct nhgrp_external *)(rtm + 1);
+ print_nhgroup_entry_sysctl("nhgrp-entry", rtm, nhg);
+ }
+ }
+ xo_close_list("rt-family");
+ xo_close_container("nhgrp-table");
+ free(nd.nh_buf);
+}
+
+static void
+update_global_map(struct nhop_external *nh)
+{
+ char iface_name[128];
+ char gw_addr[64];
+ struct nhop_addrs *na;
+ struct sockaddr *sa_gw;
+
+ na = (struct nhop_addrs *)((char *)nh + nh->nh_len);
+ sa_gw = (struct sockaddr *)((char *)na + na->gw_sa_off);
+
+ memset(iface_name, 0, sizeof(iface_name));
+ if (nh->ifindex < (uint32_t)ifmap_size) {
+ strlcpy(iface_name, ifmap[nh->ifindex].ifname,
+ sizeof(iface_name));
+ if (*iface_name == '\0')
+ strlcpy(iface_name, "---", sizeof(iface_name));
+ }
+
+ if (nh->nh_flags & NHF_GATEWAY) {
+ const char *cp;
+ cp = fmt_sockaddr(sa_gw, NULL, RTF_HOST);
+ strlcpy(gw_addr, cp, sizeof(gw_addr));
+ } else
+ snprintf(gw_addr, sizeof(gw_addr), "%s/resolve", iface_name);
+
+ nhop_map_update(&global_nhop_map, nh->nh_idx, gw_addr, iface_name);
+}
+
+static void
+prepare_nh_map(int fibnum, int af)
+{
+ struct nhops_dump nd;
+ struct nhop_external *nh;
+ struct rt_msghdr *rtm;
+
+ dump_nhops_sysctl(fibnum, af, &nd);
+
+ for (size_t i = 0; i < nd.nh_count; i++) {
+ rtm = nd.nh_map[i].rtm;
+ nh = (struct nhop_external *)(rtm + 1);
+ update_global_map(nh);
+ }
+
+ free(nd.nh_buf);
+}
+
+void
+nhgrp_print(int fibnum, int af)
+{
+ size_t intsize;
+ int numfibs;
+
+ intsize = sizeof(int);
+ if (fibnum == -1 &&
+ sysctlbyname("net.my_fibnum", &fibnum, &intsize, NULL, 0) == -1)
+ fibnum = 0;
+ if (sysctlbyname("net.fibs", &numfibs, &intsize, NULL, 0) == -1)
+ numfibs = 1;
+ if (fibnum < 0 || fibnum > numfibs - 1)
+ xo_errx(EX_USAGE, "%d: invalid fib", fibnum);
+
+ ifmap = prepare_ifmap(&ifmap_size);
+ prepare_nh_map(fibnum, af);
+
+ xo_open_container("route-nhgrp-information");
+ xo_emit("{T:Nexthop groups data}");
+ if (fibnum)
+ xo_emit(" ({L:fib}: {:fib/%d})", fibnum);
+ xo_emit("\n");
+ print_nhgrp_sysctl(fibnum, af);
+ xo_close_container("route-nhgrp-information");
+}
+
diff --git a/usr.bin/netstat/nhops.c b/usr.bin/netstat/nhops.c
new file mode 100644
index 000000000000..48ec2006994e
--- /dev/null
+++ b/usr.bin/netstat/nhops.c
@@ -0,0 +1,477 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1988, 1993
+ * The Regents of the University of California. 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.
+ * 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. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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/protosw.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+#include <net/route/nhop.h>
+
+#include <netinet/in.h>
+#include <netgraph/ng_socket.h>
+
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <libutil.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+#include "common.h"
+
+/* column widths; each followed by one space */
+#define WID_IF_DEFAULT (Wflag ? IFNAMSIZ : 12) /* width of netif column */
+#ifndef INET6
+#define WID_DST_DEFAULT(af) 18 /* width of destination column */
+#define WID_GW_DEFAULT(af) 18 /* width of gateway column */
+#else
+#define WID_DST_DEFAULT(af) \
+ ((af) == AF_INET6 ? (numeric_addr ? 33: 18) : 18)
+#define WID_GW_DEFAULT(af) \
+ ((af) == AF_INET6 ? (numeric_addr ? 29 : 18) : 18)
+#endif /*INET6*/
+static int wid_dst;
+static int wid_gw;
+static int wid_flags;
+static int wid_pksent;
+static int wid_mtu;
+static int wid_if;
+static int wid_nhidx;
+static int wid_nhtype;
+static int wid_refcnt;
+static int wid_prepend;
+
+static struct bits nh_bits[] = {
+ { NHF_REJECT, 'R', "reject" },
+ { NHF_BLACKHOLE,'B', "blackhole" },
+ { NHF_REDIRECT, 'r', "redirect" },
+ { NHF_GATEWAY, 'G', "gateway" },
+ { NHF_DEFAULT, 'd', "default" },
+ { NHF_BROADCAST,'b', "broadcast" },
+ { 0 , 0, NULL }
+};
+
+static char *nh_types[] = {
+ "empty", /* 0 */
+ "v4/resolve", /* 1 */
+ "v4/gw",
+ "v6/resolve",
+ "v6/gw"
+};
+
+struct nhop_entry {
+ char gw[64];
+ char ifname[IFNAMSIZ];
+};
+
+struct nhop_map {
+ struct nhop_entry *ptr;
+ size_t size;
+};
+static struct nhop_map global_nhop_map;
+
+static struct nhop_entry *nhop_get(struct nhop_map *map, uint32_t idx);
+
+
+static struct ifmap_entry *ifmap;
+static size_t ifmap_size;
+
+static void
+print_sockaddr_buf(char *buf, size_t bufsize, const struct sockaddr *sa)
+{
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ inet_ntop(AF_INET, &((struct sockaddr_in *)sa)->sin_addr,
+ buf, bufsize);
+ break;
+ case AF_INET6:
+ inet_ntop(AF_INET6, &((struct sockaddr_in6 *)sa)->sin6_addr,
+ buf, bufsize);
+ break;
+ default:
+ snprintf(buf, bufsize, "unknown:%d", sa->sa_family);
+ break;
+ }
+}
+
+static int
+print_addr(const char *name, const char *addr, int width)
+{
+ char buf[128];
+ int protrusion;
+
+ if (width < 0) {
+ snprintf(buf, sizeof(buf), "{:%s/%%s} ", name);
+ xo_emit(buf, addr);
+ protrusion = 0;
+ } else {
+ if (Wflag != 0 || numeric_addr) {
+ snprintf(buf, sizeof(buf), "{[:%d}{:%s/%%s}{]:} ",
+ -width, name);
+ xo_emit(buf, addr);
+ protrusion = strlen(addr) - width;
+ if (protrusion < 0)
+ protrusion = 0;
+ } else {
+ snprintf(buf, sizeof(buf), "{[:%d}{:%s/%%-.*s}{]:} ",
+ -width, name);
+ xo_emit(buf, width, addr);
+ protrusion = 0;
+ }
+ }
+ return (protrusion);
+}
+
+
+static void
+print_nhop_header(int af1 __unused)
+{
+
+ if (Wflag) {
+ xo_emit("{T:/%-*.*s} {T:/%-*.*s} {T:/%-*.*s} {T:/%-*.*s} {T:/%*.*s} "
+ "{T:/%*.*s} {T:/%-*.*s} {T:/%*.*s} {T:/%*.*s} {T:/%*.*s} {T:/%*s}\n",
+ wid_nhidx, wid_nhidx, "Idx",
+ wid_nhtype, wid_nhtype, "Type",
+ wid_dst, wid_dst, "IFA",
+ wid_gw, wid_gw, "Gateway",
+ wid_flags, wid_flags, "Flags",
+ wid_pksent, wid_pksent, "Use",
+ wid_mtu, wid_mtu, "Mtu",
+ wid_if, wid_if, "Netif",
+ wid_if, wid_if, "Addrif",
+ wid_refcnt, wid_refcnt, "Refcnt",
+ wid_prepend, "Prepend");
+ } else {
+ xo_emit("{T:/%-*.*s} {T:/%-*.*s} {T:/%-*.*s} {T:/%-*.*s} {T:/%*.*s} "
+ " {T:/%*s}\n",
+ wid_nhidx, wid_nhidx, "Idx",
+ wid_dst, wid_dst, "IFA",
+ wid_gw, wid_gw, "Gateway",
+ wid_flags, wid_flags, "Flags",
+ wid_if, wid_if, "Netif",
+ wid_prepend, "Refcnt");
+ }
+}
+
+void
+nhop_map_update(struct nhop_map *map, uint32_t idx, char *gw, char *ifname)
+{
+ if (idx >= map->size) {
+ uint32_t new_size;
+ size_t sz;
+ if (map->size == 0)
+ new_size = 32;
+ else
+ new_size = map->size * 2;
+ if (new_size <= idx)
+ new_size = roundup2(idx + 1, 32);
+
+ sz = new_size * (sizeof(struct nhop_entry));
+ if ((map->ptr = realloc(map->ptr, sz)) == NULL)
+ xo_errx(EX_OSERR, "realloc(%zu) failed", sz);
+
+ memset(&map->ptr[map->size], 0, (new_size - map->size) * sizeof(struct nhop_entry));
+ map->size = new_size;
+ }
+
+ strlcpy(map->ptr[idx].ifname, ifname, sizeof(map->ptr[idx].ifname));
+ strlcpy(map->ptr[idx].gw, gw, sizeof(map->ptr[idx].gw));
+}
+
+static struct nhop_entry *
+nhop_get(struct nhop_map *map, uint32_t idx)
+{
+
+ if (idx >= map->size)
+ return (NULL);
+ if (*map->ptr[idx].ifname == '\0')
+ return (NULL);
+ return &map->ptr[idx];
+}
+
+static void
+print_nhop_entry_sysctl(const char *name, struct rt_msghdr *rtm, struct nhop_external *nh)
+{
+ char buffer[128];
+ char iface_name[128];
+ int protrusion;
+ char gw_addr[64];
+ struct nhop_addrs *na;
+ struct sockaddr *sa_gw, *sa_ifa;
+
+ xo_open_instance(name);
+
+ snprintf(buffer, sizeof(buffer), "{[:-%d}{:index/%%lu}{]:} ", wid_nhidx);
+ //xo_emit("{t:index/%-lu} ", wid_nhidx, nh->nh_idx);
+ xo_emit(buffer, nh->nh_idx);
+
+ if (Wflag) {
+ char *cp = nh_types[nh->nh_type];
+ xo_emit("{t:type_str/%*s} ", wid_nhtype, cp);
+ }
+ memset(iface_name, 0, sizeof(iface_name));
+ if (nh->ifindex < (uint32_t)ifmap_size) {
+ strlcpy(iface_name, ifmap[nh->ifindex].ifname,
+ sizeof(iface_name));
+ if (*iface_name == '\0')
+ strlcpy(iface_name, "---", sizeof(iface_name));
+ }
+
+ na = (struct nhop_addrs *)((char *)nh + nh->nh_len);
+ //inet_ntop(nh->nh_family, &nh->nh_src, src_addr, sizeof(src_addr));
+ //protrusion = p_addr("ifa", src_addr, wid_dst);
+ sa_gw = (struct sockaddr *)((char *)na + na->gw_sa_off);
+ sa_ifa = (struct sockaddr *)((char *)na + na->src_sa_off);
+ protrusion = p_sockaddr("ifa", sa_ifa, NULL, RTF_HOST, wid_dst);
+
+ if (nh->nh_flags & NHF_GATEWAY) {
+ const char *cp;
+ cp = fmt_sockaddr(sa_gw, NULL, RTF_HOST);
+ strlcpy(gw_addr, cp, sizeof(gw_addr));
+ } else
+ snprintf(gw_addr, sizeof(gw_addr), "%s/resolve", iface_name);
+ protrusion = print_addr("gateway", gw_addr, wid_dst - protrusion);
+
+ nhop_map_update(&global_nhop_map, nh->nh_idx, gw_addr, iface_name);
+
+ snprintf(buffer, sizeof(buffer), "{[:-%d}{:flags/%%s}{]:} ",
+ wid_flags - protrusion);
+
+ //p_nhflags(nh->nh_flags, buffer);
+ print_flags_generic(rtm->rtm_flags, rt_bits, buffer, "rt_flags_pretty");
+
+ if (Wflag) {
+ xo_emit("{t:use/%*lu} ", wid_pksent, nh->nh_pksent);
+ xo_emit("{t:mtu/%*lu} ", wid_mtu, nh->nh_mtu);
+ }
+ //printf("IDX: %d IFACE: %s FAMILY: %d TYPE: %d FLAGS: %X GW \n");
+
+ if (Wflag)
+ xo_emit("{t:interface-name/%*s}", wid_if, iface_name);
+ else
+ xo_emit("{t:interface-name/%*.*s}", wid_if, wid_if, iface_name);
+
+ memset(iface_name, 0, sizeof(iface_name));
+ if (nh->aifindex < (uint32_t)ifmap_size && nh->ifindex != nh->aifindex) {
+ strlcpy(iface_name, ifmap[nh->aifindex].ifname,
+ sizeof(iface_name));
+ if (*iface_name == '\0')
+ strlcpy(iface_name, "---", sizeof(iface_name));
+ }
+ if (Wflag)
+ xo_emit("{t:address-interface-name/%*s}", wid_if, iface_name);
+
+ xo_emit("{t:refcount/%*lu} ", wid_refcnt, nh->nh_refcount);
+ if (Wflag && nh->prepend_len) {
+ int max_bytes = MIN(nh->prepend_len, sizeof(buffer) / 2 - 1);
+ for (int i = 0; i < max_bytes; i++)
+ snprintf(&buffer[i * 2], 3, "%02X", nh->nh_prepend[i]);
+ xo_emit(" {:nhop-prepend/%*s}", wid_prepend, buffer);
+ }
+
+ xo_emit("\n");
+ xo_close_instance(name);
+}
+
+static int
+cmp_nh_idx(const void *_a, const void *_b)
+{
+ const struct nhops_map *a, *b;
+
+ a = _a;
+ b = _b;
+
+ if (a->idx > b->idx)
+ return (1);
+ else if (a->idx < b->idx)
+ return (-1);
+ return (0);
+}
+
+void
+dump_nhops_sysctl(int fibnum, int af, struct nhops_dump *nd)
+{
+ size_t needed;
+ int mib[7];
+ char *buf, *next, *lim;
+ struct rt_msghdr *rtm;
+ struct nhop_external *nh;
+ struct nhops_map *nh_map;
+ size_t nh_count, nh_size;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = af;
+ mib[4] = NET_RT_NHOP;
+ mib[5] = 0;
+ mib[6] = fibnum;
+ if (sysctl(mib, nitems(mib), NULL, &needed, NULL, 0) < 0)
+ xo_err(EX_OSERR, "sysctl: net.route.0.%d.nhdump.%d estimate", af,
+ fibnum);
+ if ((buf = malloc(needed)) == NULL)
+ xo_errx(EX_OSERR, "malloc(%lu)", (unsigned long)needed);
+ if (sysctl(mib, nitems(mib), buf, &needed, NULL, 0) < 0)
+ xo_err(EX_OSERR, "sysctl: net.route.0.%d.nhdump.%d", af, fibnum);
+ lim = buf + needed;
+
+ /*
+ * nexhops are received unsorted. Collect everything first, sort and then display
+ * sorted.
+ */
+ nh_count = 0;
+ nh_size = 16;
+ nh_map = calloc(nh_size, sizeof(struct nhops_map));
+ for (next = buf; next < lim; next += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)next;
+ if (rtm->rtm_version != RTM_VERSION)
+ continue;
+
+ if (nh_count >= nh_size) {
+ nh_size *= 2;
+ nh_map = realloc(nh_map, nh_size * sizeof(struct nhops_map));
+ }
+
+ nh = (struct nhop_external *)(rtm + 1);
+ nh_map[nh_count].idx = nh->nh_idx;
+ nh_map[nh_count].rtm = rtm;
+ nh_count++;
+ }
+
+ if (nh_count > 0)
+ qsort(nh_map, nh_count, sizeof(struct nhops_map), cmp_nh_idx);
+ nd->nh_buf = buf;
+ nd->nh_count = nh_count;
+ nd->nh_map = nh_map;
+}
+
+static void
+print_nhops_sysctl(int fibnum, int af)
+{
+ struct nhops_dump nd;
+ struct nhop_external *nh;
+ int fam;
+ struct rt_msghdr *rtm;
+
+ dump_nhops_sysctl(fibnum, af, &nd);
+
+ xo_open_container("nhop-table");
+ xo_open_list("rt-family");
+ if (nd.nh_count > 0) {
+ nh = (struct nhop_external *)(nd.nh_map[0].rtm + 1);
+ fam = nh->nh_family;
+
+ wid_dst = WID_GW_DEFAULT(fam);
+ wid_gw = WID_GW_DEFAULT(fam);
+ wid_nhidx = 5;
+ wid_nhtype = 12;
+ wid_refcnt = 6;
+ wid_flags = 6;
+ wid_pksent = 8;
+ wid_mtu = 6;
+ wid_if = WID_IF_DEFAULT;
+ xo_open_instance("rt-family");
+ pr_family(fam);
+ xo_open_list("nh-entry");
+
+ print_nhop_header(fam);
+
+ for (size_t i = 0; i < nd.nh_count; i++) {
+ rtm = nd.nh_map[i].rtm;
+ nh = (struct nhop_external *)(rtm + 1);
+ print_nhop_entry_sysctl("nh-entry", rtm, nh);
+ }
+
+ xo_close_list("nh-entry");
+ xo_close_instance("rt-family");
+ }
+ xo_close_list("rt-family");
+ xo_close_container("nhop-table");
+ free(nd.nh_buf);
+}
+
+static void
+p_nhflags(int f, const char *format)
+{
+ struct bits *p;
+ char *pretty_name = "nh_flags_pretty";
+
+ xo_emit(format, fmt_flags(nh_bits, f));
+
+ xo_open_list(pretty_name);
+ for (p = nh_bits; p->b_mask; p++)
+ if (p->b_mask & f)
+ xo_emit("{le:nh_flags_pretty/%s}", p->b_name);
+ xo_close_list(pretty_name);
+}
+
+void
+nhops_print(int fibnum, int af)
+{
+ size_t intsize;
+ int numfibs;
+
+ intsize = sizeof(int);
+ if (fibnum == -1 &&
+ sysctlbyname("net.my_fibnum", &fibnum, &intsize, NULL, 0) == -1)
+ fibnum = 0;
+ if (sysctlbyname("net.fibs", &numfibs, &intsize, NULL, 0) == -1)
+ numfibs = 1;
+ if (fibnum < 0 || fibnum > numfibs - 1)
+ xo_errx(EX_USAGE, "%d: invalid fib", fibnum);
+
+ ifmap = prepare_ifmap(&ifmap_size);
+
+ xo_open_container("route-nhop-information");
+ xo_emit("{T:Nexthop data}");
+ if (fibnum)
+ xo_emit(" ({L:fib}: {:fib/%d})", fibnum);
+ xo_emit("\n");
+ print_nhops_sysctl(fibnum, af);
+ xo_close_container("route-nhop-information");
+}
+
diff --git a/usr.bin/netstat/nlist_symbols b/usr.bin/netstat/nlist_symbols
new file mode 100644
index 000000000000..30cdd69bc54b
--- /dev/null
+++ b/usr.bin/netstat/nlist_symbols
@@ -0,0 +1,50 @@
+#
+# module_name symbol_name
+all _ahstat
+all _arpstat
+all _carpstats
+all _espstat
+all _icmp6stat
+all _icmpstat
+all _igmpstat
+all _ip6stat
+all _ipcompstat
+all _ipsec4stat
+all _ipsec6stat
+all _ipstat
+all _mf6ctable
+all _mfchashtbl
+all _mfctablesize
+all _mif6table
+all _mrt6stat
+all _mrtstat
+all _netisr_bindthreads
+all _netisr_defaultqlimit
+all _netisr_dispatch_policy
+all _netisr_maxprot
+all _netisr_maxqlimit
+all _netisr_maxthreads
+all _netisr_proto
+all _ngsocklist
+all _nws
+all _nws_array
+all _nws_count
+all _pfkeystat
+all _pfsyncstats
+all _pflowstats
+all _pim6stat
+all _pimstat
+all _rip6stat
+all _rtree
+all _rtstat
+all _sctpstat
+all _sfstat
+all _tcpstat
+all _tcps_states
+all _udpstat
+all _unp_count
+all _unp_dhead
+all _unp_gencnt
+all _unp_shead
+all _unp_sphead
+all _viftable
diff --git a/usr.bin/netstat/pfkey.c b/usr.bin/netstat/pfkey.c
new file mode 100644
index 000000000000..27dd61a0c4e5
--- /dev/null
+++ b/usr.bin/netstat/pfkey.c
@@ -0,0 +1,200 @@
+/* $NetBSD: inet.c,v 1.35.2.1 1999/04/29 14:57:08 perry Exp $ */
+/* $KAME: ipsec.c,v 1.25 2001/03/12 09:04:39 itojun Exp $ */
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (C) 1995, 1996, 1997, 1998, and 1999 WIDE Project.
+ * 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.
+ * 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. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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.
+ */
+/*-
+ * Copyright (c) 1983, 1988, 1993
+ * The Regents of the University of California. 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.
+ * 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. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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/queue.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+
+#include <netinet/in.h>
+
+#ifdef IPSEC
+#include <netipsec/keysock.h>
+#endif
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+
+#ifdef IPSEC
+
+static const char *pfkey_msgtypenames[] = {
+ "reserved", "getspi", "update", "add", "delete",
+ "get", "acquire", "register", "expire", "flush",
+ "dump", "x_promisc", "x_pchange", "x_spdupdate", "x_spdadd",
+ "x_spddelete", "x_spdget", "x_spdacquire", "x_spddump", "x_spdflush",
+ "x_spdsetidx", "x_spdexpire", "x_spddelete2"
+};
+
+static const char *pfkey_msgtype_names (int);
+
+
+static const char *
+pfkey_msgtype_names(int x)
+{
+ const int max = nitems(pfkey_msgtypenames);
+ static char buf[20];
+
+ if (x < max && pfkey_msgtypenames[x])
+ return pfkey_msgtypenames[x];
+ snprintf(buf, sizeof(buf), "#%d", x);
+ return buf;
+}
+
+void
+pfkey_stats(u_long off, const char *name, int family __unused,
+ int proto __unused)
+{
+ struct pfkeystat pfkeystat;
+ unsigned first, type;
+
+ if (off == 0)
+ return;
+ xo_emit("{T:/%s}:\n", name);
+ xo_open_container(name);
+ kread_counters(off, (char *)&pfkeystat, sizeof(pfkeystat));
+
+#define p(f, m) if (pfkeystat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)pfkeystat.f, plural(pfkeystat.f))
+
+ /* userland -> kernel */
+ p(out_total, "\t{:sent-requests/%ju} "
+ "{N:/request%s sent from userland}\n");
+ p(out_bytes, "\t{:sent-bytes/%ju} "
+ "{N:/byte%s sent from userland}\n");
+ for (first = 1, type = 0;
+ type<sizeof(pfkeystat.out_msgtype)/sizeof(pfkeystat.out_msgtype[0]);
+ type++) {
+ if (pfkeystat.out_msgtype[type] <= 0)
+ continue;
+ if (first) {
+ xo_open_list("output-histogram");
+ xo_emit("\t{T:histogram by message type}:\n");
+ first = 0;
+ }
+ xo_open_instance("output-histogram");
+ xo_emit("\t\t{k::type/%s}: {:count/%ju}\n",
+ pfkey_msgtype_names(type),
+ (uintmax_t)pfkeystat.out_msgtype[type]);
+ xo_close_instance("output-histogram");
+ }
+ if (!first)
+ xo_close_list("output-histogram");
+
+ p(out_invlen, "\t{:dropped-bad-length/%ju} "
+ "{N:/message%s with invalid length field}\n");
+ p(out_invver, "\t{:dropped-bad-version/%ju} "
+ "{N:/message%s with invalid version field}\n");
+ p(out_invmsgtype, "\t{:dropped-bad-type/%ju} "
+ "{N:/message%s with invalid message type field}\n");
+ p(out_tooshort, "\t{:dropped-too-short/%ju} "
+ "{N:/message%s too short}\n");
+ p(out_nomem, "\t{:dropped-no-memory/%ju} "
+ "{N:/message%s with memory allocation failure}\n");
+ p(out_dupext, "\t{:dropped-duplicate-extension/%ju} "
+ "{N:/message%s with duplicate extension}\n");
+ p(out_invexttype, "\t{:dropped-bad-extension/%ju} "
+ "{N:/message%s with invalid extension type}\n");
+ p(out_invsatype, "\t{:dropped-bad-sa-type/%ju} "
+ "{N:/message%s with invalid sa type}\n");
+ p(out_invaddr, "\t{:dropped-bad-address-extension/%ju} "
+ "{N:/message%s with invalid address extension}\n");
+
+ /* kernel -> userland */
+ p(in_total, "\t{:received-requests/%ju} "
+ "{N:/request%s sent to userland}\n");
+ p(in_bytes, "\t{:received-bytes/%ju} "
+ "{N:/byte%s sent to userland}\n");
+ for (first = 1, type = 0;
+ type < sizeof(pfkeystat.in_msgtype)/sizeof(pfkeystat.in_msgtype[0]);
+ type++) {
+ if (pfkeystat.in_msgtype[type] <= 0)
+ continue;
+ if (first) {
+ xo_open_list("input-histogram");
+ xo_emit("\t{T:histogram by message type}:\n");
+ first = 0;
+ }
+ xo_open_instance("input-histogram");
+ xo_emit("\t\t{k:type/%s}: {:count/%ju}\n",
+ pfkey_msgtype_names(type),
+ (uintmax_t)pfkeystat.in_msgtype[type]);
+ xo_close_instance("input-histogram");
+ }
+ if (!first)
+ xo_close_list("input-histogram");
+ p(in_msgtarget[KEY_SENDUP_ONE], "\t{:received-one-socket/%ju} "
+ "{N:/message%s toward single socket}\n");
+ p(in_msgtarget[KEY_SENDUP_ALL], "\t{:received-all-sockets/%ju} "
+ "{N:/message%s toward all sockets}\n");
+ p(in_msgtarget[KEY_SENDUP_REGISTERED],
+ "\t{:received-registered-sockets/%ju} "
+ "{N:/message%s toward registered sockets}\n");
+ p(in_nomem, "\t{:discarded-no-memory/%ju} "
+ "{N:/message%s with memory allocation failure}\n");
+#undef p
+ xo_close_container(name);
+}
+#endif /* IPSEC */
diff --git a/usr.bin/netstat/route.c b/usr.bin/netstat/route.c
new file mode 100644
index 000000000000..697c7ba2e9e1
--- /dev/null
+++ b/usr.bin/netstat/route.c
@@ -0,0 +1,724 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1988, 1993
+ * The Regents of the University of California. 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.
+ * 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. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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/protosw.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netgraph/ng_socket.h>
+
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <libutil.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+#include "common.h"
+#include "nl_defs.h"
+
+/*
+ * Definitions for showing gateway flags.
+ */
+struct bits rt_bits[] = {
+ { RTF_UP, 'U', "up" },
+ { RTF_GATEWAY, 'G', "gateway" },
+ { RTF_HOST, 'H', "host" },
+ { RTF_REJECT, 'R', "reject" },
+ { RTF_DYNAMIC, 'D', "dynamic" },
+ { RTF_MODIFIED, 'M', "modified" },
+ { RTF_DONE, 'd', "done" }, /* Completed -- for routing msgs only */
+ { RTF_XRESOLVE, 'X', "xresolve" },
+ { RTF_STATIC, 'S', "static" },
+ { RTF_PROTO1, '1', "proto1" },
+ { RTF_PROTO2, '2', "proto2" },
+ { RTF_PROTO3, '3', "proto3" },
+ { RTF_BLACKHOLE,'B', "blackhole" },
+ { RTF_BROADCAST,'b', "broadcast" },
+#ifdef RTF_LLINFO
+ { RTF_LLINFO, 'L', "llinfo" },
+#endif
+ { 0 , 0, NULL }
+};
+
+#ifdef WITHOUT_NETLINK
+static struct ifmap_entry *ifmap;
+static size_t ifmap_size;
+#endif
+static struct timespec uptime;
+
+static const char *netname4(in_addr_t, in_addr_t);
+#ifdef INET6
+static const char *netname6(struct sockaddr_in6 *, struct sockaddr_in6 *);
+#endif
+#ifdef WITHOUT_NETLINK
+static void p_rtable_sysctl(int, int);
+static void p_rtentry_sysctl(const char *name, struct rt_msghdr *);
+#endif
+static void domask(char *, size_t, u_long);
+
+const uint32_t rt_default_weight = RT_DEFAULT_WEIGHT;
+
+/*
+ * Print routing tables.
+ */
+void
+routepr(int fibnum, int af)
+{
+ size_t intsize;
+ int numfibs;
+
+ if (live == 0)
+ return;
+
+ intsize = sizeof(int);
+ if (fibnum == -1 &&
+ sysctlbyname("net.my_fibnum", &fibnum, &intsize, NULL, 0) == -1)
+ fibnum = 0;
+ if (sysctlbyname("net.fibs", &numfibs, &intsize, NULL, 0) == -1)
+ numfibs = 1;
+ if (fibnum < 0 || fibnum > numfibs - 1)
+ xo_errx(EX_USAGE, "%d: invalid fib", fibnum);
+ /*
+ * Since kernel & userland use different timebase
+ * (time_uptime vs time_second) and we are reading kernel memory
+ * directly we should do rt_expire --> expire_time conversion.
+ */
+ if (clock_gettime(CLOCK_UPTIME, &uptime) < 0)
+ xo_err(EX_OSERR, "clock_gettime() failed");
+
+ xo_open_container("route-information");
+ xo_emit("{T:Routing tables}");
+ if (fibnum)
+ xo_emit(" ({L:fib}: {:fib/%d})", fibnum);
+ xo_emit("\n");
+#ifdef WITHOUT_NETLINK
+ p_rtable_sysctl(fibnum, af);
+#else
+ p_rtable_netlink(fibnum, af);
+#endif
+ xo_close_container("route-information");
+}
+
+
+/*
+ * Print address family header before a section of the routing table.
+ */
+void
+pr_family(int af1)
+{
+ const char *afname;
+
+ switch (af1) {
+ case AF_INET:
+ afname = "Internet";
+ break;
+#ifdef INET6
+ case AF_INET6:
+ afname = "Internet6";
+ break;
+#endif /*INET6*/
+ case AF_ISO:
+ afname = "ISO";
+ break;
+ case AF_CCITT:
+ afname = "X.25";
+ break;
+ case AF_NETGRAPH:
+ afname = "Netgraph";
+ break;
+ default:
+ afname = NULL;
+ break;
+ }
+ if (afname)
+ xo_emit("\n{k:address-family/%s}:\n", afname);
+ else
+ xo_emit("\n{L:Protocol Family} {k:address-family/%d}:\n", af1);
+}
+
+/* column widths; each followed by one space */
+#define WID_IF_DEFAULT (Wflag ? IFNAMSIZ : 12) /* width of netif column */
+#ifndef INET6
+#define WID_DST_DEFAULT(af) 18 /* width of destination column */
+#define WID_GW_DEFAULT(af) 18 /* width of gateway column */
+#else
+#define WID_DST_DEFAULT(af) \
+ ((af) == AF_INET6 ? (numeric_addr ? 33: 18) : 18)
+#define WID_GW_DEFAULT(af) \
+ ((af) == AF_INET6 ? (numeric_addr ? 29 : 18) : 18)
+#endif /*INET6*/
+
+struct _wid wid;
+
+/*
+ * Print header for routing table columns.
+ */
+void
+pr_rthdr(int af1 __unused)
+{
+
+ if (Wflag) {
+ xo_emit("{T:/%-*.*s} {T:/%-*.*s} {T:/%-*.*s} {T:/%*.*s} "
+ "{T:/%*.*s} {T:/%*.*s} {T:/%*s}\n",
+ wid.dst, wid.dst, "Destination",
+ wid.gw, wid.gw, "Gateway",
+ wid.flags, wid.flags, "Flags",
+ wid.mtu, wid.mtu, "Nhop#",
+ wid.mtu, wid.mtu, "Mtu",
+ wid.iface, wid.iface, "Netif",
+ wid.expire, "Expire");
+ } else {
+ xo_emit("{T:/%-*.*s} {T:/%-*.*s} {T:/%-*.*s} {T:/%*.*s} "
+ "{T:/%*s}\n",
+ wid.dst, wid.dst, "Destination",
+ wid.gw, wid.gw, "Gateway",
+ wid.flags, wid.flags, "Flags",
+ wid.iface, wid.iface, "Netif",
+ wid.expire, "Expire");
+ }
+}
+
+void
+set_wid(int fam)
+{
+ wid.dst = WID_DST_DEFAULT(fam);
+ wid.gw = WID_GW_DEFAULT(fam);
+ wid.flags = 6;
+ wid.pksent = 8;
+ wid.mtu = 6;
+ wid.iface = WID_IF_DEFAULT;
+ wid.expire = 6;
+}
+
+#ifdef WITHOUT_NETLINK
+static void
+p_rtable_sysctl(int fibnum, int af)
+{
+ size_t needed;
+ int mib[7];
+ char *buf, *next, *lim;
+ struct rt_msghdr *rtm;
+ struct sockaddr *sa;
+ int fam = AF_UNSPEC;
+ int need_table_close = false;
+
+ ifmap = prepare_ifmap(&ifmap_size);
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = af;
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0;
+ mib[6] = fibnum;
+ if (sysctl(mib, nitems(mib), NULL, &needed, NULL, 0) < 0)
+ xo_err(EX_OSERR, "sysctl: net.route.0.%d.dump.%d estimate", af,
+ fibnum);
+ if ((buf = malloc(needed)) == NULL)
+ xo_errx(EX_OSERR, "malloc(%lu)", (unsigned long)needed);
+ if (sysctl(mib, nitems(mib), buf, &needed, NULL, 0) < 0)
+ xo_err(EX_OSERR, "sysctl: net.route.0.%d.dump.%d", af, fibnum);
+ lim = buf + needed;
+ xo_open_container("route-table");
+ xo_open_list("rt-family");
+ for (next = buf; next < lim; next += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)next;
+ if (rtm->rtm_version != RTM_VERSION)
+ continue;
+ /*
+ * Peek inside header to determine AF
+ */
+ sa = (struct sockaddr *)(rtm + 1);
+ /* Only print family first time. */
+ if (fam != sa->sa_family) {
+ if (need_table_close) {
+ xo_close_list("rt-entry");
+ xo_close_instance("rt-family");
+ }
+ need_table_close = true;
+ fam = sa->sa_family;
+ set_wid(fam);
+ xo_open_instance("rt-family");
+ pr_family(fam);
+ xo_open_list("rt-entry");
+
+ pr_rthdr(fam);
+ }
+ p_rtentry_sysctl("rt-entry", rtm);
+ }
+ if (need_table_close) {
+ xo_close_list("rt-entry");
+ xo_close_instance("rt-family");
+ }
+ xo_close_list("rt-family");
+ xo_close_container("route-table");
+ free(buf);
+}
+
+static void
+p_rtentry_sysctl(const char *name, struct rt_msghdr *rtm)
+{
+ struct sockaddr *sa, *addr[RTAX_MAX];
+ char buffer[128];
+ char prettyname[128];
+ int i, protrusion;
+
+ xo_open_instance(name);
+ sa = (struct sockaddr *)(rtm + 1);
+ for (i = 0; i < RTAX_MAX; i++) {
+ if (rtm->rtm_addrs & (1 << i)) {
+ addr[i] = sa;
+ sa = (struct sockaddr *)((char *)sa + SA_SIZE(sa));
+ }
+ }
+
+ protrusion = p_sockaddr("destination", addr[RTAX_DST],
+ addr[RTAX_NETMASK],
+ rtm->rtm_flags, wid.dst);
+ protrusion = p_sockaddr("gateway", addr[RTAX_GATEWAY], NULL, RTF_HOST,
+ wid.gw - protrusion);
+ snprintf(buffer, sizeof(buffer), "{[:-%d}{:flags/%%s}{]:} ",
+ wid.flags - protrusion);
+ p_flags(rtm->rtm_flags, buffer);
+ /* Output path weight as non-visual property */
+ xo_emit("{e:weight/%u}", rtm->rtm_rmx.rmx_weight);
+ if (Wflag) {
+ /* XXX: use=0? */
+ xo_emit("{t:nhop/%*lu} ", wid.mtu, rtm->rtm_rmx.rmx_nhidx);
+
+ if (rtm->rtm_rmx.rmx_mtu != 0)
+ xo_emit("{t:mtu/%*lu} ", wid.mtu, rtm->rtm_rmx.rmx_mtu);
+ else
+ xo_emit("{P:/%*s} ", wid.mtu, "");
+ }
+
+ memset(prettyname, 0, sizeof(prettyname));
+ if (rtm->rtm_index < ifmap_size) {
+ strlcpy(prettyname, ifmap[rtm->rtm_index].ifname,
+ sizeof(prettyname));
+ if (*prettyname == '\0')
+ strlcpy(prettyname, "---", sizeof(prettyname));
+ }
+
+ if (Wflag)
+ xo_emit("{t:interface-name/%*s}", wid.iface, prettyname);
+ else
+ xo_emit("{t:interface-name/%*.*s}", wid.iface, wid.iface,
+ prettyname);
+ if (rtm->rtm_rmx.rmx_expire) {
+ time_t expire_time;
+
+ if ((expire_time = rtm->rtm_rmx.rmx_expire - uptime.tv_sec) > 0)
+ xo_emit(" {:expire-time/%*d}", wid.expire,
+ (int)expire_time);
+ }
+
+ xo_emit("\n");
+ xo_close_instance(name);
+}
+#endif
+
+int
+p_sockaddr(const char *name, struct sockaddr *sa, struct sockaddr *mask,
+ int flags, int width)
+{
+ const char *cp;
+ char buf[128];
+ int protrusion;
+
+ cp = fmt_sockaddr(sa, mask, flags);
+
+ if (width < 0) {
+ snprintf(buf, sizeof(buf), "{:%s/%%s} ", name);
+ xo_emit(buf, cp);
+ protrusion = 0;
+ } else {
+ if (Wflag != 0 || numeric_addr) {
+ snprintf(buf, sizeof(buf), "{[:%d}{:%s/%%s}{]:} ",
+ -width, name);
+ xo_emit(buf, cp);
+ protrusion = strlen(cp) - width;
+ if (protrusion < 0)
+ protrusion = 0;
+ } else {
+ snprintf(buf, sizeof(buf), "{[:%d}{:%s/%%-.*s}{]:} ",
+ -width, name);
+ xo_emit(buf, width, cp);
+ protrusion = 0;
+ }
+ }
+ return (protrusion);
+}
+
+const char *
+fmt_sockaddr(struct sockaddr *sa, struct sockaddr *mask, int flags)
+{
+ static char buf[128];
+ const char *cp;
+
+ if (sa == NULL)
+ return ("null");
+
+ switch(sa->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ /*
+ * The sa6->sin6_scope_id must be filled here because
+ * this sockaddr is extracted from kmem(4) directly
+ * and has KAME-specific embedded scope id in
+ * sa6->sin6_addr.s6_addr[2].
+ */
+ in6_fillscopeid(satosin6(sa));
+ /* FALLTHROUGH */
+#endif /*INET6*/
+ case AF_INET:
+ if (flags & RTF_HOST)
+ cp = routename(sa, numeric_addr);
+ else if (mask)
+ cp = netname(sa, mask);
+ else
+ cp = netname(sa, NULL);
+ break;
+ case AF_NETGRAPH:
+ {
+ strlcpy(buf, ((struct sockaddr_ng *)sa)->sg_data,
+ sizeof(buf));
+ cp = buf;
+ break;
+ }
+ case AF_LINK:
+ {
+#if 0
+ struct sockaddr_dl *sdl = (struct sockaddr_dl *)sa;
+
+ /* Interface route. */
+ if (sdl->sdl_nlen)
+ cp = sdl->sdl_data;
+ else
+#endif
+ cp = routename(sa, 1);
+ break;
+ }
+ default:
+ {
+ u_char *s = (u_char *)sa->sa_data, *slim;
+ char *cq, *cqlim;
+
+ cq = buf;
+ slim = sa->sa_len + (u_char *) sa;
+ cqlim = cq + sizeof(buf) - sizeof(" ffff");
+ snprintf(cq, sizeof(buf), "(%d)", sa->sa_family);
+ cq += strlen(cq);
+ while (s < slim && cq < cqlim) {
+ snprintf(cq, sizeof(" ff"), " %02x", *s++);
+ cq += strlen(cq);
+ if (s < slim) {
+ snprintf(cq, sizeof("ff"), "%02x", *s++);
+ cq += strlen(cq);
+ }
+ }
+ cp = buf;
+ }
+ }
+
+ return (cp);
+}
+
+void
+p_flags(int f, const char *format)
+{
+
+ print_flags_generic(f, rt_bits, format, "flags_pretty");
+}
+
+
+char *
+routename(struct sockaddr *sa, int flags)
+{
+ static char line[NI_MAXHOST];
+ int error, f;
+
+ f = (flags) ? NI_NUMERICHOST : 0;
+ error = getnameinfo(sa, sa->sa_len, line, sizeof(line),
+ NULL, 0, f);
+ if (error) {
+ const void *src;
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ src = &satosin(sa)->sin_addr;
+ break;
+#endif /* INET */
+#ifdef INET6
+ case AF_INET6:
+ src = &satosin6(sa)->sin6_addr;
+ break;
+#endif /* INET6 */
+ default:
+ return(line);
+ }
+ inet_ntop(sa->sa_family, src, line, sizeof(line) - 1);
+ return (line);
+ }
+ trimdomain(line, strlen(line));
+
+ return (line);
+}
+
+#define NSHIFT(m) ( \
+ (m) == IN_CLASSA_NET ? IN_CLASSA_NSHIFT : \
+ (m) == IN_CLASSB_NET ? IN_CLASSB_NSHIFT : \
+ (m) == IN_CLASSC_NET ? IN_CLASSC_NSHIFT : \
+ 0)
+
+static void
+domask(char *dst, size_t buflen, u_long mask)
+{
+ int b, i;
+
+ if (mask == 0) {
+ *dst = '\0';
+ return;
+ }
+ i = 0;
+ for (b = 0; b < 32; b++)
+ if (mask & (1 << b)) {
+ int bb;
+
+ i = b;
+ for (bb = b+1; bb < 32; bb++)
+ if (!(mask & (1 << bb))) {
+ i = -1; /* noncontig */
+ break;
+ }
+ break;
+ }
+ if (i == -1)
+ snprintf(dst, buflen, "&0x%lx", mask);
+ else
+ snprintf(dst, buflen, "/%d", 32-i);
+}
+
+/*
+ * Return the name of the network whose address is given.
+ */
+const char *
+netname(struct sockaddr *sa, struct sockaddr *mask)
+{
+ switch (sa->sa_family) {
+ case AF_INET:
+ if (mask != NULL)
+ return (netname4(satosin(sa)->sin_addr.s_addr,
+ satosin(mask)->sin_addr.s_addr));
+ else
+ return (netname4(satosin(sa)->sin_addr.s_addr,
+ INADDR_ANY));
+ break;
+#ifdef INET6
+ case AF_INET6:
+ return (netname6(satosin6(sa), satosin6(mask)));
+#endif /* INET6 */
+ default:
+ return (NULL);
+ }
+}
+
+static const char *
+netname4(in_addr_t in, in_addr_t mask)
+{
+ char *cp = 0;
+ static char line[MAXHOSTNAMELEN + sizeof("&0xffffffff")];
+ char nline[INET_ADDRSTRLEN];
+ struct netent *np = 0;
+ in_addr_t i;
+
+ if (numeric_addr < 2 && in == INADDR_ANY && mask == 0) {
+ strlcpy(line, "default", sizeof(line));
+ return (line);
+ }
+
+ /* It is ok to supply host address. */
+ in &= mask;
+
+ i = ntohl(in);
+ if (!numeric_addr && i) {
+ np = getnetbyaddr(i >> NSHIFT(ntohl(mask)), AF_INET);
+ if (np != NULL) {
+ cp = np->n_name;
+ trimdomain(cp, strlen(cp));
+ }
+ }
+ if (cp != NULL)
+ strlcpy(line, cp, sizeof(line));
+ else {
+ inet_ntop(AF_INET, &in, nline, sizeof(nline));
+ strlcpy(line, nline, sizeof(line));
+ domask(line + strlen(line), sizeof(line) - strlen(line), ntohl(mask));
+ }
+
+ return (line);
+}
+
+#undef NSHIFT
+
+#ifdef INET6
+void
+in6_fillscopeid(struct sockaddr_in6 *sa6)
+{
+#if defined(__KAME__)
+ /*
+ * XXX: This is a special workaround for KAME kernels.
+ * sin6_scope_id field of SA should be set in the future.
+ */
+ if (IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr) ||
+ IN6_IS_ADDR_MC_NODELOCAL(&sa6->sin6_addr) ||
+ IN6_IS_ADDR_MC_LINKLOCAL(&sa6->sin6_addr)) {
+ if (sa6->sin6_scope_id == 0)
+ sa6->sin6_scope_id =
+ ntohs(*(u_int16_t *)&sa6->sin6_addr.s6_addr[2]);
+ sa6->sin6_addr.s6_addr[2] = sa6->sin6_addr.s6_addr[3] = 0;
+ }
+#endif
+}
+
+/* Mask to length table. To check an invalid value, (length + 1) is used. */
+static const u_char masktolen[256] = {
+ [0xff] = 8 + 1,
+ [0xfe] = 7 + 1,
+ [0xfc] = 6 + 1,
+ [0xf8] = 5 + 1,
+ [0xf0] = 4 + 1,
+ [0xe0] = 3 + 1,
+ [0xc0] = 2 + 1,
+ [0x80] = 1 + 1,
+ [0x00] = 0 + 1,
+};
+
+static const char *
+netname6(struct sockaddr_in6 *sa6, struct sockaddr_in6 *mask)
+{
+ static char line[NI_MAXHOST + sizeof("/xxx") - 1];
+ struct sockaddr_in6 addr;
+ char nline[NI_MAXHOST];
+ char maskbuf[sizeof("/xxx")];
+ u_char *p, *lim;
+ u_char masklen;
+ int i;
+ bool illegal = false;
+
+ if (mask) {
+ p = (u_char *)&mask->sin6_addr;
+ for (masklen = 0, lim = p + 16; p < lim; p++) {
+ if (masktolen[*p] > 0) {
+ /* -1 is required. */
+ masklen += (masktolen[*p] - 1);
+ } else
+ illegal = true;
+ }
+ if (illegal)
+ xo_error("illegal prefixlen\n");
+
+ memcpy(&addr, sa6, sizeof(addr));
+ for (i = 0; i < 16; ++i)
+ addr.sin6_addr.s6_addr[i] &=
+ mask->sin6_addr.s6_addr[i];
+ sa6 = &addr;
+ }
+ else
+ masklen = 128;
+
+ if (numeric_addr < 2 && masklen == 0 &&
+ IN6_IS_ADDR_UNSPECIFIED(&sa6->sin6_addr))
+ return("default");
+
+ getnameinfo((struct sockaddr *)sa6, sa6->sin6_len, nline, sizeof(nline),
+ NULL, 0, NI_NUMERICHOST);
+ if (numeric_addr)
+ strlcpy(line, nline, sizeof(line));
+ else
+ getnameinfo((struct sockaddr *)sa6, sa6->sin6_len, line,
+ sizeof(line), NULL, 0, 0);
+ if (numeric_addr || strcmp(line, nline) == 0) {
+ snprintf(maskbuf, sizeof(maskbuf), "/%d", masklen);
+ strlcat(line, maskbuf, sizeof(line));
+ }
+
+ return (line);
+}
+#endif /*INET6*/
+
+/*
+ * Print routing statistics
+ */
+void
+rt_stats(void)
+{
+ struct rtstat rtstat;
+
+ if (fetch_stats("net.route.stats", nl[N_RTSTAT].n_value, &rtstat,
+ sizeof(rtstat), kread_counters) != 0)
+ return;
+
+ xo_emit("{T:routing}:\n");
+
+#define p(f, m) if (rtstat.f || sflag <= 1) \
+ xo_emit(m, rtstat.f, plural(rtstat.f))
+
+ p(rts_badredirect, "\t{:bad-redirects/%ju} "
+ "{N:/bad routing redirect%s}\n");
+ p(rts_dynamic, "\t{:dynamically-created/%ju} "
+ "{N:/dynamically created route%s}\n");
+ p(rts_newgateway, "\t{:new-gateways/%ju} "
+ "{N:/new gateway%s due to redirects}\n");
+ p(rts_unreach, "\t{:unreachable-destination/%ju} "
+ "{N:/destination%s found unreachable}\n");
+ p(rts_wildcard, "\t{:wildcard-uses/%ju} "
+ "{N:/use%s of a wildcard route}\n");
+#undef p
+}
diff --git a/usr.bin/netstat/route_netlink.c b/usr.bin/netstat/route_netlink.c
new file mode 100644
index 000000000000..e7b2a1964602
--- /dev/null
+++ b/usr.bin/netstat/route_netlink.c
@@ -0,0 +1,340 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1988, 1993
+ * The Regents of the University of California. 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.
+ * 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. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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/protosw.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <netlink/netlink.h>
+#include <netlink/netlink_route.h>
+#include <netlink/netlink_snl.h>
+#include <netlink/netlink_snl_route.h>
+#include <netlink/netlink_snl_route_parsers.h>
+#include <netlink/netlink_snl_route_compat.h>
+
+#include <netinet/in.h>
+#include <netgraph/ng_socket.h>
+
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <libutil.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+#include "common.h"
+#include "nl_defs.h"
+
+
+static void p_rtentry_netlink(struct snl_state *ss, const char *name, struct nlmsghdr *hdr);
+
+static struct ifmap_entry *ifmap;
+static size_t ifmap_size;
+
+/* Generate ifmap using netlink */
+static struct ifmap_entry *
+prepare_ifmap_netlink(struct snl_state *ss, size_t *pifmap_size)
+{
+ struct {
+ struct nlmsghdr hdr;
+ struct ifinfomsg ifmsg;
+ } msg = {
+ .hdr.nlmsg_type = RTM_GETLINK,
+ .hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+ .hdr.nlmsg_seq = snl_get_seq(ss),
+ };
+ msg.hdr.nlmsg_len = sizeof(msg);
+
+ if (!snl_send_message(ss, &msg.hdr))
+ return (NULL);
+
+ struct ifmap_entry *ifmap = NULL;
+ uint32_t ifmap_size = 0;
+ struct nlmsghdr *hdr;
+ struct snl_errmsg_data e = {};
+
+ while ((hdr = snl_read_reply_multi(ss, msg.hdr.nlmsg_seq, &e)) != NULL) {
+ struct snl_parsed_link_simple link = {};
+
+ if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_link_parser_simple, &link))
+ continue;
+ if (link.ifi_index >= ifmap_size) {
+ size_t size = roundup2(link.ifi_index + 1, 32) * sizeof(struct ifmap_entry);
+ if ((ifmap = realloc(ifmap, size)) == NULL)
+ xo_errx(EX_OSERR, "realloc(%zu) failed", size);
+ memset(&ifmap[ifmap_size], 0,
+ size - ifmap_size *
+ sizeof(struct ifmap_entry));
+ ifmap_size = roundup2(link.ifi_index + 1, 32);
+ }
+ if (*ifmap[link.ifi_index].ifname != '\0')
+ continue;
+ strlcpy(ifmap[link.ifi_index].ifname, link.ifla_ifname, IFNAMSIZ);
+ ifmap[link.ifi_index].mtu = link.ifla_mtu;
+ }
+ *pifmap_size = ifmap_size;
+ return (ifmap);
+}
+
+static void
+ip6_writemask(struct in6_addr *addr6, uint8_t mask)
+{
+ uint32_t *cp;
+
+ for (cp = (uint32_t *)addr6; mask >= 32; mask -= 32)
+ *cp++ = 0xFFFFFFFF;
+ if (mask > 0)
+ *cp = htonl(mask ? ~((1 << (32 - mask)) - 1) : 0);
+}
+
+static void
+gen_mask(int family, int plen, struct sockaddr *sa)
+{
+ if (family == AF_INET6) {
+ struct sockaddr_in6 sin6 = {
+ .sin6_family = AF_INET6,
+ .sin6_len = sizeof(struct sockaddr_in6),
+ };
+ ip6_writemask(&sin6.sin6_addr, plen);
+ *((struct sockaddr_in6 *)sa) = sin6;
+ } else if (family == AF_INET) {
+ struct sockaddr_in sin = {
+ .sin_family = AF_INET,
+ .sin_len = sizeof(struct sockaddr_in),
+ .sin_addr.s_addr = htonl(plen ? ~((1 << (32 - plen)) - 1) : 0),
+ };
+ *((struct sockaddr_in *)sa) = sin;
+ }
+}
+
+static void
+add_scopeid(struct sockaddr *sa, int ifindex)
+{
+ if (sa->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
+ sin6->sin6_scope_id = ifindex;
+ }
+}
+
+static void
+p_path(struct snl_parsed_route *rt, bool is_mpath)
+{
+ struct sockaddr_in6 mask6;
+ struct sockaddr *pmask = (struct sockaddr *)&mask6;
+ char buffer[128];
+ char prettyname[128];
+ int protrusion;
+
+ gen_mask(rt->rtm_family, rt->rtm_dst_len, pmask);
+ add_scopeid(rt->rta_dst, rt->rta_oif);
+ add_scopeid(rt->rta_gw, rt->rta_oif);
+ protrusion = p_sockaddr("destination", rt->rta_dst, pmask, rt->rta_rtflags, wid.dst);
+ protrusion = p_sockaddr("gateway", rt->rta_gw, NULL, RTF_HOST,
+ wid.gw - protrusion);
+ snprintf(buffer, sizeof(buffer), "{[:-%d}{:flags/%%s}{]:} ",
+ wid.flags - protrusion);
+ p_flags(rt->rta_rtflags | RTF_UP, buffer);
+ /* Output path weight as non-visual property */
+ xo_emit("{e:weight/%u}", rt->rtax_weight);
+ if (is_mpath)
+ xo_emit("{e:nhg-kidx/%u}", rt->rta_knh_id);
+ else
+ xo_emit("{e:nhop-kidx/%u}", rt->rta_knh_id);
+ if (rt->rta_nh_id != 0) {
+ if (is_mpath)
+ xo_emit("{e:nhg-uidx/%u}", rt->rta_nh_id);
+ else
+ xo_emit("{e:nhop-uidx/%u}", rt->rta_nh_id);
+ }
+
+ memset(prettyname, 0, sizeof(prettyname));
+ if (rt->rta_oif < ifmap_size) {
+ strlcpy(prettyname, ifmap[rt->rta_oif].ifname,
+ sizeof(prettyname));
+ if (*prettyname == '\0')
+ strlcpy(prettyname, "---", sizeof(prettyname));
+ if (rt->rtax_mtu == 0)
+ rt->rtax_mtu = ifmap[rt->rta_oif].mtu;
+ }
+
+ if (Wflag) {
+ /* XXX: use=0? */
+ xo_emit("{t:nhop/%*lu} ", wid.mtu, is_mpath ? 0 : rt->rta_knh_id);
+
+ if (rt->rtax_mtu != 0)
+ xo_emit("{t:mtu/%*lu} ", wid.mtu, rt->rtax_mtu);
+ else {
+ /* use interface mtu */
+ xo_emit("{P:/%*s} ", wid.mtu, "");
+ }
+
+ }
+
+ if (Wflag)
+ xo_emit("{t:interface-name/%*s}", wid.iface, prettyname);
+ else
+ xo_emit("{t:interface-name/%*.*s}", wid.iface, wid.iface,
+ prettyname);
+ if (rt->rta_expires > 0) {
+ xo_emit(" {:expire-time/%*u}", wid.expire, rt->rta_expires);
+ }
+}
+
+static void
+p_rtentry_netlink(struct snl_state *ss, const char *name, struct nlmsghdr *hdr)
+{
+
+ struct snl_parsed_route rt = {};
+ if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_route_parser, &rt))
+ return;
+ if (rt.rtax_weight == 0)
+ rt.rtax_weight = rt_default_weight;
+
+ if (rt.rta_multipath.num_nhops != 0) {
+ uint32_t orig_rtflags = rt.rta_rtflags;
+ uint32_t orig_mtu = rt.rtax_mtu;
+ for (uint32_t i = 0; i < rt.rta_multipath.num_nhops; i++) {
+ struct rta_mpath_nh *nhop = rt.rta_multipath.nhops[i];
+
+ rt.rta_gw = nhop->gw;
+ rt.rta_oif = nhop->ifindex;
+ rt.rtax_weight = nhop->rtnh_weight;
+ rt.rta_rtflags = nhop->rta_rtflags ? nhop->rta_rtflags : orig_rtflags;
+ rt.rtax_mtu = nhop->rtax_mtu ? nhop->rtax_mtu : orig_mtu;
+
+ xo_open_instance(name);
+ p_path(&rt, true);
+ xo_emit("\n");
+ xo_close_instance(name);
+ }
+ return;
+ }
+
+ struct sockaddr_dl sdl_gw = {
+ .sdl_family = AF_LINK,
+ .sdl_len = sizeof(struct sockaddr_dl),
+ .sdl_index = rt.rta_oif,
+ };
+ if (rt.rta_gw == NULL)
+ rt.rta_gw = (struct sockaddr *)&sdl_gw;
+
+ xo_open_instance(name);
+ p_path(&rt, false);
+ xo_emit("\n");
+ xo_close_instance(name);
+}
+
+bool
+p_rtable_netlink(int fibnum, int af)
+{
+ int fam = AF_UNSPEC;
+ int need_table_close = false;
+ struct nlmsghdr *hdr;
+ struct snl_errmsg_data e = {};
+ struct snl_state ss = {};
+
+ if (!snl_init(&ss, NETLINK_ROUTE))
+ return (false);
+
+ ifmap = prepare_ifmap_netlink(&ss, &ifmap_size);
+ if (ifmap == NULL) {
+ snl_free(&ss);
+ return (false);
+ }
+
+ struct {
+ struct nlmsghdr hdr;
+ struct rtmsg rtmsg;
+ struct nlattr nla_fibnum;
+ uint32_t fibnum;
+ } msg = {
+ .hdr.nlmsg_type = RTM_GETROUTE,
+ .hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+ .hdr.nlmsg_seq = snl_get_seq(&ss),
+ .rtmsg.rtm_family = af,
+ .nla_fibnum.nla_len = sizeof(struct nlattr) + sizeof(uint32_t),
+ .nla_fibnum.nla_type = RTA_TABLE,
+ .fibnum = fibnum,
+ };
+ msg.hdr.nlmsg_len = sizeof(msg);
+
+ if (!snl_send_message(&ss, &msg.hdr)) {
+ snl_free(&ss);
+ return (false);
+ }
+
+ xo_open_container("route-table");
+ xo_open_list("rt-family");
+ while ((hdr = snl_read_reply_multi(&ss, msg.hdr.nlmsg_seq, &e)) != NULL) {
+ struct rtmsg *rtm = (struct rtmsg *)(hdr + 1);
+ /* Only print family first time. */
+ if (fam != rtm->rtm_family) {
+ if (need_table_close) {
+ xo_close_list("rt-entry");
+ xo_close_instance("rt-family");
+ }
+ need_table_close = true;
+ fam = rtm->rtm_family;
+ set_wid(fam);
+ xo_open_instance("rt-family");
+ pr_family(fam);
+ xo_open_list("rt-entry");
+ pr_rthdr(fam);
+ }
+ p_rtentry_netlink(&ss, "rt-entry", hdr);
+ snl_clear_lb(&ss);
+ }
+ if (need_table_close) {
+ xo_close_list("rt-entry");
+ xo_close_instance("rt-family");
+ }
+ xo_close_list("rt-family");
+ xo_close_container("route-table");
+ snl_free(&ss);
+ return (true);
+}
+
+
diff --git a/usr.bin/netstat/sctp.c b/usr.bin/netstat/sctp.c
new file mode 100644
index 000000000000..08cfc31c12c9
--- /dev/null
+++ b/usr.bin/netstat/sctp.c
@@ -0,0 +1,841 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, by Weongyo Jeong. All rights reserved.
+ * Copyright (c) 2011, by Michael Tuexen. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * b) 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.
+ *
+ * c) Neither the name of Cisco Systems, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT OWNER OR CONTRIBUTORS 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/queue.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+#include <sys/protosw.h>
+
+#include <netinet/in.h>
+#include <netinet/sctp.h>
+#include <netinet/sctp_constants.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <libutil.h>
+#include <netdb.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include "netstat.h"
+#include <libxo/xo.h>
+
+#ifdef SCTP
+
+static void sctp_statesprint(uint32_t state);
+
+#define NETSTAT_SCTP_STATES_CLOSED 0x0
+#define NETSTAT_SCTP_STATES_BOUND 0x1
+#define NETSTAT_SCTP_STATES_LISTEN 0x2
+#define NETSTAT_SCTP_STATES_COOKIE_WAIT 0x3
+#define NETSTAT_SCTP_STATES_COOKIE_ECHOED 0x4
+#define NETSTAT_SCTP_STATES_ESTABLISHED 0x5
+#define NETSTAT_SCTP_STATES_SHUTDOWN_SENT 0x6
+#define NETSTAT_SCTP_STATES_SHUTDOWN_RECEIVED 0x7
+#define NETSTAT_SCTP_STATES_SHUTDOWN_ACK_SENT 0x8
+#define NETSTAT_SCTP_STATES_SHUTDOWN_PENDING 0x9
+
+static const char *sctpstates[] = {
+ "CLOSED",
+ "BOUND",
+ "LISTEN",
+ "COOKIE_WAIT",
+ "COOKIE_ECHOED",
+ "ESTABLISHED",
+ "SHUTDOWN_SENT",
+ "SHUTDOWN_RECEIVED",
+ "SHUTDOWN_ACK_SENT",
+ "SHUTDOWN_PENDING"
+};
+
+static LIST_HEAD(xladdr_list, xladdr_entry) xladdr_head;
+struct xladdr_entry {
+ struct xsctp_laddr *xladdr;
+ LIST_ENTRY(xladdr_entry) xladdr_entries;
+};
+
+static LIST_HEAD(xraddr_list, xraddr_entry) xraddr_head;
+struct xraddr_entry {
+ struct xsctp_raddr *xraddr;
+ LIST_ENTRY(xraddr_entry) xraddr_entries;
+};
+
+static void
+sctp_print_address(const char *container, union sctp_sockstore *address,
+ int port, int num_port)
+{
+ struct servent *sp = 0;
+ char line[80], *cp;
+ int width;
+ size_t alen, plen;
+
+ if (container)
+ xo_open_container(container);
+
+ switch (address->sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ snprintf(line, sizeof(line), "%.*s.",
+ Wflag ? 39 : 16, inetname(&address->sin.sin_addr));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ snprintf(line, sizeof(line), "%.*s.",
+ Wflag ? 39 : 16, inet6name(&address->sin6.sin6_addr));
+ break;
+#endif
+ default:
+ snprintf(line, sizeof(line), "%.*s.",
+ Wflag ? 39 : 16, "");
+ break;
+ }
+ alen = strlen(line);
+ cp = line + alen;
+ if (!num_port && port)
+ sp = getservbyport((int)port, "sctp");
+ if (sp || port == 0)
+ snprintf(cp, sizeof(line) - alen,
+ "%.15s ", sp ? sp->s_name : "*");
+ else
+ snprintf(cp, sizeof(line) - alen,
+ "%d ", ntohs((u_short)port));
+ width = Wflag ? 45 : 22;
+ xo_emit("{d:target/%-*.*s} ", width, width, line);
+
+ plen = strlen(cp) - 1;
+ alen--;
+ xo_emit("{e:address/%*.*s}{e:port/%*.*s}", alen, alen, line, plen,
+ plen, cp);
+
+ if (container)
+ xo_close_container(container);
+}
+
+static int
+sctp_skip_xinpcb_ifneed(char *buf, const size_t buflen, size_t *offset)
+{
+ int exist_tcb = 0;
+ struct xsctp_tcb *xstcb;
+ struct xsctp_raddr *xraddr;
+ struct xsctp_laddr *xladdr;
+
+ while (*offset < buflen) {
+ xladdr = (struct xsctp_laddr *)(buf + *offset);
+ *offset += sizeof(struct xsctp_laddr);
+ if (xladdr->last == 1)
+ break;
+ }
+
+ while (*offset < buflen) {
+ xstcb = (struct xsctp_tcb *)(buf + *offset);
+ *offset += sizeof(struct xsctp_tcb);
+ if (xstcb->last == 1)
+ break;
+
+ exist_tcb = 1;
+
+ while (*offset < buflen) {
+ xladdr = (struct xsctp_laddr *)(buf + *offset);
+ *offset += sizeof(struct xsctp_laddr);
+ if (xladdr->last == 1)
+ break;
+ }
+
+ while (*offset < buflen) {
+ xraddr = (struct xsctp_raddr *)(buf + *offset);
+ *offset += sizeof(struct xsctp_raddr);
+ if (xraddr->last == 1)
+ break;
+ }
+ }
+
+ /*
+ * If Lflag is set, we don't care about the return value.
+ */
+ if (Lflag)
+ return 0;
+
+ return exist_tcb;
+}
+
+static void
+sctp_process_tcb(struct xsctp_tcb *xstcb,
+ char *buf, const size_t buflen, size_t *offset, int *indent)
+{
+ int i, xl_total = 0, xr_total = 0, x_max;
+ struct xsctp_raddr *xraddr;
+ struct xsctp_laddr *xladdr;
+ struct xladdr_entry *prev_xl = NULL, *xl = NULL, *xl_tmp;
+ struct xraddr_entry *prev_xr = NULL, *xr = NULL, *xr_tmp;
+
+ LIST_INIT(&xladdr_head);
+ LIST_INIT(&xraddr_head);
+
+ /*
+ * Make `struct xladdr_list' list and `struct xraddr_list' list
+ * to handle the address flexibly.
+ */
+ while (*offset < buflen) {
+ xladdr = (struct xsctp_laddr *)(buf + *offset);
+ *offset += sizeof(struct xsctp_laddr);
+ if (xladdr->last == 1)
+ break;
+
+ prev_xl = xl;
+ xl = malloc(sizeof(struct xladdr_entry));
+ if (xl == NULL) {
+ xo_warnx("malloc %lu bytes",
+ (u_long)sizeof(struct xladdr_entry));
+ goto out;
+ }
+ xl->xladdr = xladdr;
+ if (prev_xl == NULL)
+ LIST_INSERT_HEAD(&xladdr_head, xl, xladdr_entries);
+ else
+ LIST_INSERT_AFTER(prev_xl, xl, xladdr_entries);
+ xl_total++;
+ }
+
+ while (*offset < buflen) {
+ xraddr = (struct xsctp_raddr *)(buf + *offset);
+ *offset += sizeof(struct xsctp_raddr);
+ if (xraddr->last == 1)
+ break;
+
+ prev_xr = xr;
+ xr = malloc(sizeof(struct xraddr_entry));
+ if (xr == NULL) {
+ xo_warnx("malloc %lu bytes",
+ (u_long)sizeof(struct xraddr_entry));
+ goto out;
+ }
+ xr->xraddr = xraddr;
+ if (prev_xr == NULL)
+ LIST_INSERT_HEAD(&xraddr_head, xr, xraddr_entries);
+ else
+ LIST_INSERT_AFTER(prev_xr, xr, xraddr_entries);
+ xr_total++;
+ }
+
+ /*
+ * Let's print the address infos.
+ */
+ xo_open_list("address");
+ xl = LIST_FIRST(&xladdr_head);
+ xr = LIST_FIRST(&xraddr_head);
+ x_max = MAX(xl_total, xr_total);
+ for (i = 0; i < x_max; i++) {
+ xo_open_instance("address");
+
+ if (((*indent == 0) && i > 0) || *indent > 0)
+ xo_emit("{P:/%-12s} ", " ");
+
+ if (xl != NULL) {
+ sctp_print_address("local", &(xl->xladdr->address),
+ htons(xstcb->local_port), numeric_port);
+ } else {
+ if (Wflag) {
+ xo_emit("{P:/%-45s} ", " ");
+ } else {
+ xo_emit("{P:/%-22s} ", " ");
+ }
+ }
+
+ if (xr != NULL && !Lflag) {
+ sctp_print_address("remote", &(xr->xraddr->address),
+ htons(xstcb->remote_port), numeric_port);
+ }
+
+ if (xl != NULL)
+ xl = LIST_NEXT(xl, xladdr_entries);
+ if (xr != NULL)
+ xr = LIST_NEXT(xr, xraddr_entries);
+
+ if (i == 0 && !Lflag)
+ sctp_statesprint(xstcb->state);
+
+ if (i < x_max)
+ xo_emit("\n");
+ xo_close_instance("address");
+ }
+
+out:
+ /*
+ * Free the list which be used to handle the address.
+ */
+ xl = LIST_FIRST(&xladdr_head);
+ while (xl != NULL) {
+ xl_tmp = LIST_NEXT(xl, xladdr_entries);
+ free(xl);
+ xl = xl_tmp;
+ }
+
+ xr = LIST_FIRST(&xraddr_head);
+ while (xr != NULL) {
+ xr_tmp = LIST_NEXT(xr, xraddr_entries);
+ free(xr);
+ xr = xr_tmp;
+ }
+}
+
+static void
+sctp_process_inpcb(struct xsctp_inpcb *xinpcb,
+ char *buf, const size_t buflen, size_t *offset)
+{
+ int indent = 0, xladdr_total = 0, is_listening = 0;
+ static int first = 1;
+ const char *tname, *pname;
+ struct xsctp_tcb *xstcb;
+ struct xsctp_laddr *xladdr;
+ size_t offset_laddr;
+ int process_closed;
+
+ if (xinpcb->maxqlen > 0)
+ is_listening = 1;
+
+ if (first) {
+ if (!Lflag) {
+ xo_emit("Active SCTP associations");
+ if (aflag)
+ xo_emit(" (including servers)");
+ } else
+ xo_emit("Current listen queue sizes (qlen/maxqlen)");
+ xo_emit("\n");
+ if (Lflag)
+ xo_emit("{T:/%-6.6s} {T:/%-5.5s} {T:/%-8.8s} "
+ "{T:/%-22.22s}\n",
+ "Proto", "Type", "Listen", "Local Address");
+ else
+ if (Wflag)
+ xo_emit("{T:/%-6.6s} {T:/%-5.5s} {T:/%-45.45s} "
+ "{T:/%-45.45s} {T:/%s}\n",
+ "Proto", "Type",
+ "Local Address", "Foreign Address",
+ "(state)");
+ else
+ xo_emit("{T:/%-6.6s} {T:/%-5.5s} {T:/%-22.22s} "
+ "{T:/%-22.22s} {T:/%s}\n",
+ "Proto", "Type",
+ "Local Address", "Foreign Address",
+ "(state)");
+ first = 0;
+ }
+ xladdr = (struct xsctp_laddr *)(buf + *offset);
+ if ((!aflag && is_listening) ||
+ (Lflag && !is_listening)) {
+ sctp_skip_xinpcb_ifneed(buf, buflen, offset);
+ return;
+ }
+
+ if (xinpcb->flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ /* Can't distinguish between sctp46 and sctp6 */
+ pname = "sctp46";
+ } else {
+ pname = "sctp4";
+ }
+
+ if (xinpcb->flags & SCTP_PCB_FLAGS_TCPTYPE)
+ tname = "1to1";
+ else if (xinpcb->flags & SCTP_PCB_FLAGS_UDPTYPE)
+ tname = "1toN";
+ else
+ tname = "????";
+
+ if (Lflag) {
+ char buf1[22];
+
+ snprintf(buf1, sizeof buf1, "%u/%u",
+ xinpcb->qlen, xinpcb->maxqlen);
+ xo_emit("{:protocol/%-6.6s/%s} {:type/%-5.5s/%s} ",
+ pname, tname);
+ xo_emit("{d:queues/%-8.8s}{e:queue-len/%hu}"
+ "{e:max-queue-len/%hu} ",
+ buf1, xinpcb->qlen, xinpcb->maxqlen);
+ }
+
+ offset_laddr = *offset;
+ process_closed = 0;
+
+ xo_open_list("local-address");
+retry:
+ while (*offset < buflen) {
+ xladdr = (struct xsctp_laddr *)(buf + *offset);
+ *offset += sizeof(struct xsctp_laddr);
+ if (xladdr->last) {
+ if (aflag && !Lflag && (xladdr_total == 0) && process_closed) {
+ xo_open_instance("local-address");
+
+ xo_emit("{:protocol/%-6.6s/%s} "
+ "{:type/%-5.5s/%s} ", pname, tname);
+ if (Wflag) {
+ xo_emit("{P:/%-91.91s/%s} "
+ "{:state/CLOSED}", " ");
+ } else {
+ xo_emit("{P:/%-45.45s/%s} "
+ "{:state/CLOSED}", " ");
+ }
+ xo_close_instance("local-address");
+ }
+ if (process_closed || is_listening) {
+ xo_emit("\n");
+ }
+ break;
+ }
+
+ if (!Lflag && !is_listening && !process_closed)
+ continue;
+
+ xo_open_instance("local-address");
+
+ if (xladdr_total == 0) {
+ if (!Lflag) {
+ xo_emit("{:protocol/%-6.6s/%s} "
+ "{:type/%-5.5s/%s} ", pname, tname);
+ }
+ } else {
+ xo_emit("\n");
+ xo_emit(Lflag ? "{P:/%-21.21s} " : "{P:/%-12.12s} ",
+ " ");
+ }
+ sctp_print_address("local", &(xladdr->address),
+ htons(xinpcb->local_port), numeric_port);
+ if (aflag && !Lflag && xladdr_total == 0) {
+ if (Wflag) {
+ if (process_closed) {
+ xo_emit("{P:/%-45.45s} "
+ "{:state/CLOSED}", " ");
+ } else {
+ xo_emit("{P:/%-45.45s} "
+ "{:state/LISTEN}", " ");
+ }
+ } else {
+ if (process_closed) {
+ xo_emit("{P:/%-22.22s} "
+ "{:state/CLOSED}", " ");
+ } else {
+ xo_emit("{P:/%-22.22s} "
+ "{:state/LISTEN}", " ");
+ }
+ }
+ }
+ xladdr_total++;
+ xo_close_instance("local-address");
+ }
+
+ xstcb = (struct xsctp_tcb *)(buf + *offset);
+ *offset += sizeof(struct xsctp_tcb);
+ if (aflag && (xladdr_total == 0) && xstcb->last && !process_closed) {
+ process_closed = 1;
+ *offset = offset_laddr;
+ goto retry;
+ }
+ while (xstcb->last == 0 && *offset < buflen) {
+ xo_emit("{:protocol/%-6.6s/%s} {:type/%-5.5s/%s} ",
+ pname, tname);
+ sctp_process_tcb(xstcb, buf, buflen, offset, &indent);
+ indent++;
+ xstcb = (struct xsctp_tcb *)(buf + *offset);
+ *offset += sizeof(struct xsctp_tcb);
+ }
+
+ xo_close_list("local-address");
+}
+
+/*
+ * Print a summary of SCTP connections related to an Internet
+ * protocol.
+ */
+void
+sctp_protopr(u_long off __unused,
+ const char *name __unused, int af1 __unused, int proto)
+{
+ char *buf;
+ const char *mibvar = "net.inet.sctp.assoclist";
+ size_t offset = 0;
+ size_t len = 0;
+ struct xsctp_inpcb *xinpcb;
+
+ if (proto != IPPROTO_SCTP)
+ return;
+
+ if (sysctlbyname(mibvar, 0, &len, 0, 0) < 0) {
+ if (errno != ENOENT)
+ xo_warn("sysctl: %s", mibvar);
+ return;
+ }
+ if ((buf = malloc(len)) == NULL) {
+ xo_warnx("malloc %lu bytes", (u_long)len);
+ return;
+ }
+ if (sysctlbyname(mibvar, buf, &len, 0, 0) < 0) {
+ xo_warn("sysctl: %s", mibvar);
+ free(buf);
+ return;
+ }
+
+ xinpcb = (struct xsctp_inpcb *)(buf + offset);
+ offset += sizeof(struct xsctp_inpcb);
+ while (xinpcb->last == 0 && offset < len) {
+ sctp_process_inpcb(xinpcb, buf, (const size_t)len,
+ &offset);
+
+ xinpcb = (struct xsctp_inpcb *)(buf + offset);
+ offset += sizeof(struct xsctp_inpcb);
+ }
+
+ free(buf);
+}
+
+static void
+sctp_statesprint(uint32_t state)
+{
+ int idx;
+
+ switch (state) {
+ case SCTP_CLOSED:
+ idx = NETSTAT_SCTP_STATES_CLOSED;
+ break;
+ case SCTP_BOUND:
+ idx = NETSTAT_SCTP_STATES_BOUND;
+ break;
+ case SCTP_LISTEN:
+ idx = NETSTAT_SCTP_STATES_LISTEN;
+ break;
+ case SCTP_COOKIE_WAIT:
+ idx = NETSTAT_SCTP_STATES_COOKIE_WAIT;
+ break;
+ case SCTP_COOKIE_ECHOED:
+ idx = NETSTAT_SCTP_STATES_COOKIE_ECHOED;
+ break;
+ case SCTP_ESTABLISHED:
+ idx = NETSTAT_SCTP_STATES_ESTABLISHED;
+ break;
+ case SCTP_SHUTDOWN_SENT:
+ idx = NETSTAT_SCTP_STATES_SHUTDOWN_SENT;
+ break;
+ case SCTP_SHUTDOWN_RECEIVED:
+ idx = NETSTAT_SCTP_STATES_SHUTDOWN_RECEIVED;
+ break;
+ case SCTP_SHUTDOWN_ACK_SENT:
+ idx = NETSTAT_SCTP_STATES_SHUTDOWN_ACK_SENT;
+ break;
+ case SCTP_SHUTDOWN_PENDING:
+ idx = NETSTAT_SCTP_STATES_SHUTDOWN_PENDING;
+ break;
+ default:
+ xo_emit("UNKNOWN {:state/0x%08x}", state);
+ return;
+ }
+
+ xo_emit("{:state/%s}", sctpstates[idx]);
+}
+
+/*
+ * Dump SCTP statistics structure.
+ */
+void
+sctp_stats(u_long off, const char *name, int af1 __unused, int proto __unused)
+{
+ struct sctpstat sctpstat;
+
+ if (fetch_stats("net.inet.sctp.stats", off, &sctpstat,
+ sizeof(sctpstat), kread) != 0)
+ return;
+
+ xo_open_container(name);
+ xo_emit("{T:/%s}:\n", name);
+
+#define p(f, m) if (sctpstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)sctpstat.f, plural(sctpstat.f))
+#define p1a(f, m) if (sctpstat.f || sflag <= 1) \
+ xo_emit(m, (uintmax_t)sctpstat.f)
+
+ /*
+ * input statistics
+ */
+ p(sctps_recvpackets, "\t{:received-packets/%ju} "
+ "{N:/input packet%s}\n");
+ p(sctps_recvdatagrams, "\t\t{:received-datagrams/%ju} "
+ "{N:/datagram%s}\n");
+ p(sctps_recvpktwithdata, "\t\t{:received-with-data/%ju} "
+ "{N:/packet%s that had data}\n");
+ p(sctps_recvsacks, "\t\t{:received-sack-chunks/%ju} "
+ "{N:/input SACK chunk%s}\n");
+ p(sctps_recvdata, "\t\t{:received-data-chunks/%ju} "
+ "{N:/input DATA chunk%s}\n");
+ p(sctps_recvdupdata, "\t\t{:received-duplicate-data-chunks/%ju} "
+ "{N:/duplicate DATA chunk%s}\n");
+ p(sctps_recvheartbeat, "\t\t{:received-hb-chunks/%ju} "
+ "{N:/input HB chunk%s}\n");
+ p(sctps_recvheartbeatack, "\t\t{:received-hb-ack-chunks/%ju} "
+ "{N:/HB-ACK chunk%s}\n");
+ p(sctps_recvecne, "\t\t{:received-ecne-chunks/%ju} "
+ "{N:/input ECNE chunk%s}\n");
+ p(sctps_recvauth, "\t\t{:received-auth-chunks/%ju} "
+ "{N:/input AUTH chunk%s}\n");
+ p(sctps_recvauthmissing, "\t\t{:dropped-missing-auth/%ju} "
+ "{N:/chunk%s missing AUTH}\n");
+ p(sctps_recvivalhmacid, "\t\t{:dropped-invalid-hmac/%ju} "
+ "{N:/invalid HMAC id%s received}\n");
+ p(sctps_recvivalkeyid, "\t\t{:dropped-invalid-secret/%ju} "
+ "{N:/invalid secret id%s received}\n");
+ p1a(sctps_recvauthfailed, "\t\t{:dropped-auth-failed/%ju} "
+ "{N:/auth failed}\n");
+ p1a(sctps_recvexpress, "\t\t{:received-fast-path/%ju} "
+ "{N:/fast path receives all one chunk}\n");
+ p1a(sctps_recvexpressm, "\t\t{:receives-fast-path-multipart/%ju} "
+ "{N:/fast path multi-part data}\n");
+ p1a(sctps_recvswcrc, "\t\t{:performed-receive-crc32c-computation/%ju} "
+ "{N:/performed receive crc32c computation}\n");
+ p1a(sctps_recvhwcrc, "\t\t{:performed-receive-crc32c-offloading/%ju} "
+ "{N:/performed receive crc32c offloading}\n");
+
+ /*
+ * output statistics
+ */
+ p(sctps_sendpackets, "\t{:sent-packets/%ju} "
+ "{N:/output packet%s}\n");
+ p(sctps_sendsacks, "\t\t{:sent-sacks/%ju} "
+ "{N:/output SACK%s}\n");
+ p(sctps_senddata, "\t\t{:sent-data-chunks/%ju} "
+ "{N:/output DATA chunk%s}\n");
+ p(sctps_sendretransdata, "\t\t{:sent-retransmitted-data-chunks/%ju} "
+ "{N:/retransmitted DATA chunk%s}\n");
+ p(sctps_sendfastretrans, "\t\t"
+ "{:sent-fast-retransmitted-data-chunks/%ju} "
+ "{N:/fast retransmitted DATA chunk%s}\n");
+ p(sctps_sendmultfastretrans, "\t\t"
+ "{:sent-fast-retransmitted-data-chunk-multiple-times/%ju} "
+ "{N:/FR'%s that happened more than once to same chunk}\n");
+ p(sctps_sendheartbeat, "\t\t{:sent-hb-chunks/%ju} "
+ "{N:/output HB chunk%s}\n");
+ p(sctps_sendecne, "\t\t{:sent-ecne-chunks/%ju} "
+ "{N:/output ECNE chunk%s}\n");
+ p(sctps_sendauth, "\t\t{:sent-auth-chunks/%ju} "
+ "{N:/output AUTH chunk%s}\n");
+ p1a(sctps_senderrors, "\t\t{:send-errors/%ju} "
+ "{N:/ip_output error counter}\n");
+ p1a(sctps_sendswcrc, "\t\t{:performed-receive-crc32c-computation/%ju} "
+ "{N:/performed transmit crc32c computation}\n");
+ p1a(sctps_sendhwcrc, "\t\t{:performed-transmit-crc32c-offloading/%ju} "
+ "{N:/performed transmit crc32c offloading}\n");
+
+ /*
+ * PCKDROPREP statistics
+ */
+ xo_emit("\t{T:Packet drop statistics}:\n");
+ xo_open_container("drop-statistics");
+ p1a(sctps_pdrpfmbox, "\t\t{:middle-box/%ju} "
+ "{N:/from middle box}\n");
+ p1a(sctps_pdrpfehos, "\t\t{:end-host/%ju} "
+ "{N:/from end host}\n");
+ p1a(sctps_pdrpmbda, "\t\t{:with-data/%ju} "
+ "{N:/with data}\n");
+ p1a(sctps_pdrpmbct, "\t\t{:non-data/%ju} "
+ "{N:/non-data, non-endhost}\n");
+ p1a(sctps_pdrpbwrpt, "\t\t{:non-endhost/%ju} "
+ "{N:/non-endhost, bandwidth rep only}\n");
+ p1a(sctps_pdrpcrupt, "\t\t{:short-header/%ju} "
+ "{N:/not enough for chunk header}\n");
+ p1a(sctps_pdrpnedat, "\t\t{:short-data/%ju} "
+ "{N:/not enough data to confirm}\n");
+ p1a(sctps_pdrppdbrk, "\t\t{:chunk-break/%ju} "
+ "{N:/where process_chunk_drop said break}\n");
+ p1a(sctps_pdrptsnnf, "\t\t{:tsn-not-found/%ju} "
+ "{N:/failed to find TSN}\n");
+ p1a(sctps_pdrpdnfnd, "\t\t{:reverse-tsn/%ju} "
+ "{N:/attempt reverse TSN lookup}\n");
+ p1a(sctps_pdrpdiwnp, "\t\t{:confirmed-zero-window/%ju} "
+ "{N:/e-host confirms zero-rwnd}\n");
+ p1a(sctps_pdrpdizrw, "\t\t{:middle-box-no-space/%ju} "
+ "{N:/midbox confirms no space}\n");
+ p1a(sctps_pdrpbadd, "\t\t{:bad-data/%ju} "
+ "{N:/data did not match TSN}\n");
+ p(sctps_pdrpmark, "\t\t{:tsn-marked-fast-retransmission/%ju} "
+ "{N:/TSN'%s marked for Fast Retran}\n");
+ xo_close_container("drop-statistics");
+
+ /*
+ * Timeouts
+ */
+ xo_emit("\t{T:Timeouts}:\n");
+ xo_open_container("timeouts");
+ p(sctps_timoiterator, "\t\t{:iterator/%ju} "
+ "{N:/iterator timer%s fired}\n");
+ p(sctps_timodata, "\t\t{:t3-data/%ju} "
+ "{N:/T3 data time out%s}\n");
+ p(sctps_timowindowprobe, "\t\t{:window-probe/%ju} "
+ "{N:/window probe (T3) timer%s fired}\n");
+ p(sctps_timoinit, "\t\t{:init-timer/%ju} "
+ "{N:/INIT timer%s fired}\n");
+ p(sctps_timosack, "\t\t{:sack-timer/%ju} "
+ "{N:/sack timer%s fired}\n");
+ p(sctps_timoshutdown, "\t\t{:shutdown-timer/%ju} "
+ "{N:/shutdown timer%s fired}\n");
+ p(sctps_timoheartbeat, "\t\t{:heartbeat-timer/%ju} "
+ "{N:/heartbeat timer%s fired}\n");
+ p1a(sctps_timocookie, "\t\t{:cookie-timer/%ju} "
+ "{N:/a cookie timeout fired}\n");
+ p1a(sctps_timosecret, "\t\t{:endpoint-changed-cookie/%ju} "
+ "{N:/an endpoint changed its cook}ie"
+ "secret\n");
+ p(sctps_timopathmtu, "\t\t{:pmtu-timer/%ju} "
+ "{N:/PMTU timer%s fired}\n");
+ p(sctps_timoshutdownack, "\t\t{:shutdown-ack-timer/%ju} "
+ "{N:/shutdown ack timer%s fired}\n");
+ p(sctps_timoshutdownguard, "\t\t{:shutdown-guard-timer/%ju} "
+ "{N:/shutdown guard timer%s fired}\n");
+ p(sctps_timostrmrst, "\t\t{:stream-reset-timer/%ju} "
+ "{N:/stream reset timer%s fired}\n");
+ p(sctps_timoearlyfr, "\t\t{:early-fast-retransmission-timer/%ju} "
+ "{N:/early FR timer%s fired}\n");
+ p1a(sctps_timoasconf, "\t\t{:asconf-timer/%ju} "
+ "{N:/an asconf timer fired}\n");
+ p1a(sctps_timoautoclose, "\t\t{:auto-close-timer/%ju} "
+ "{N:/auto close timer fired}\n");
+ p(sctps_timoassockill, "\t\t{:asoc-free-timer/%ju} "
+ "{N:/asoc free timer%s expired}\n");
+ p(sctps_timoinpkill, "\t\t{:input-free-timer/%ju} "
+ "{N:/inp free timer%s expired}\n");
+ xo_close_container("timeouts");
+
+#if 0
+ /*
+ * Early fast retransmission counters
+ */
+ p(sctps_earlyfrstart, "\t%ju TODO:sctps_earlyfrstart\n");
+ p(sctps_earlyfrstop, "\t%ju TODO:sctps_earlyfrstop\n");
+ p(sctps_earlyfrmrkretrans, "\t%ju TODO:sctps_earlyfrmrkretrans\n");
+ p(sctps_earlyfrstpout, "\t%ju TODO:sctps_earlyfrstpout\n");
+ p(sctps_earlyfrstpidsck1, "\t%ju TODO:sctps_earlyfrstpidsck1\n");
+ p(sctps_earlyfrstpidsck2, "\t%ju TODO:sctps_earlyfrstpidsck2\n");
+ p(sctps_earlyfrstpidsck3, "\t%ju TODO:sctps_earlyfrstpidsck3\n");
+ p(sctps_earlyfrstpidsck4, "\t%ju TODO:sctps_earlyfrstpidsck4\n");
+ p(sctps_earlyfrstrid, "\t%ju TODO:sctps_earlyfrstrid\n");
+ p(sctps_earlyfrstrout, "\t%ju TODO:sctps_earlyfrstrout\n");
+ p(sctps_earlyfrstrtmr, "\t%ju TODO:sctps_earlyfrstrtmr\n");
+#endif
+
+ /*
+ * Others
+ */
+ p1a(sctps_hdrops, "\t{:dropped-too-short/%ju} "
+ "{N:/packet shorter than header}\n");
+ p1a(sctps_badsum, "\t{:dropped-bad-checksum/%ju} "
+ "{N:/checksum error}\n");
+ p1a(sctps_noport, "\t{:dropped-no-endpoint/%ju} "
+ "{N:/no endpoint for port}\n");
+ p1a(sctps_badvtag, "\t{:dropped-bad-v-tag/%ju} "
+ "{N:/bad v-tag}\n");
+ p1a(sctps_badsid, "\t{:dropped-bad-sid/%ju} "
+ "{N:/bad SID}\n");
+ p1a(sctps_nomem, "\t{:dropped-no-memory/%ju} "
+ "{N:/no memory}\n");
+ p1a(sctps_fastretransinrtt, "\t{:multiple-fast-retransmits-in-rtt/%ju} "
+ "{N:/number of multiple FR in a RT}T window\n");
+#if 0
+ p(sctps_markedretrans, "\t%ju TODO:sctps_markedretrans\n");
+#endif
+ p1a(sctps_naglesent, "\t{:rfc813-sent/%ju} "
+ "{N:/RFC813 allowed sending}\n");
+ p1a(sctps_naglequeued, "\t{:rfc813-queued/%ju} "
+ "{N:/RFC813 does not allow sending}\n");
+ p1a(sctps_maxburstqueued, "\t{:max-burst-queued/%ju} "
+ "{N:/times max burst prohibited sending}\n");
+ p1a(sctps_ifnomemqueued, "\t{:no-memory-in-interface/%ju} "
+ "{N:/look ahead tells us no memory in interface}\n");
+ p(sctps_windowprobed, "\t{:sent-window-probes/%ju} "
+ "{N:/number%s of window probes sent}\n");
+ p(sctps_lowlevelerr, "\t{:low-level-err/%ju} "
+ "{N:/time%s an output error to clamp down on next user send}\n");
+ p(sctps_lowlevelerrusr, "\t{:low-level-user-error/%ju} "
+ "{N:/time%s sctp_senderrors were caused from a user}\n");
+ p(sctps_datadropchklmt, "\t{:dropped-chunk-limit/%ju} "
+ "{N:/number of in data drop%s due to chunk limit reached}\n");
+ p(sctps_datadroprwnd, "\t{:dropped-rwnd-limit/%ju} "
+ "{N:/number of in data drop%s due to rwnd limit reached}\n");
+ p(sctps_ecnereducedcwnd, "\t{:ecn-reduced-cwnd/%ju} "
+ "{N:/time%s a ECN reduced the cwnd}\n");
+ p1a(sctps_vtagexpress, "\t{:v-tag-express-lookup/%ju} "
+ "{N:/used express lookup via vtag}\n");
+ p1a(sctps_vtagbogus, "\t{:v-tag-collision/%ju} "
+ "{N:/collision in express lookup}\n");
+ p(sctps_primary_randry, "\t{:sender-ran-dry/%ju} "
+ "{N:/time%s the sender ran dry of user data on primary}\n");
+ p1a(sctps_cmt_randry, "\t{:cmt-ran-dry/%ju} "
+ "{N:/same for above}\n");
+ p(sctps_slowpath_sack, "\t{:slow-path-sack/%ju} "
+ "{N:/sack%s the slow way}\n");
+ p(sctps_wu_sacks_sent, "\t{:sent-window-update-only-sack/%ju} "
+ "{N:/window update only sack%s sent}\n");
+ p(sctps_sends_with_flags, "\t{:sent-with-sinfo/%ju} "
+ "{N:/send%s with sinfo_flags !=0}\n");
+ p(sctps_sends_with_unord, "\t{:sent-with-unordered/%ju} "
+ "{N:/unordered send%s}\n");
+ p(sctps_sends_with_eof, "\t{:sent-with-eof/%ju} "
+ "{N:/send%s with EOF flag set}\n");
+ p(sctps_sends_with_abort, "\t{:sent-with-abort/%ju} "
+ "{N:/send%s with ABORT flag set}\n");
+ p(sctps_protocol_drain_calls, "\t{:protocol-drain-called/%ju} "
+ "{N:/time%s protocol drain called}\n");
+ p(sctps_protocol_drains_done, "\t{:protocol-drain/%ju} "
+ "{N:/time%s we did a protocol drain}\n");
+ p(sctps_read_peeks, "\t{:read-with-peek/%ju} "
+ "{N:/time%s recv was called with peek}\n");
+ p(sctps_cached_chk, "\t{:cached-chunks/%ju} "
+ "{N:/cached chunk%s used}\n");
+ p1a(sctps_cached_strmoq, "\t{:cached-output-queue-used/%ju} "
+ "{N:/cached stream oq's used}\n");
+ p(sctps_left_abandon, "\t{:messages-abandoned/%ju} "
+ "{N:/unread message%s abandonded by close}\n");
+ p1a(sctps_send_burst_avoid, "\t{:send-burst-avoidance/%ju} "
+ "{N:/send burst avoidance, already max burst inflight to net}\n");
+ p1a(sctps_send_cwnd_avoid, "\t{:send-cwnd-avoidance/%ju} "
+ "{N:/send cwnd full avoidance, already max burst inflight "
+ "to net}\n");
+ p(sctps_fwdtsn_map_over, "\t{:tsn-map-overruns/%ju} "
+ "{N:/number of map array over-run%s via fwd-tsn's}\n");
+
+#undef p
+#undef p1a
+ xo_close_container(name);
+}
+
+#endif /* SCTP */
diff --git a/usr.bin/netstat/unix.c b/usr.bin/netstat/unix.c
new file mode 100644
index 000000000000..ca9671e812ac
--- /dev/null
+++ b/usr.bin/netstat/unix.c
@@ -0,0 +1,320 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1988, 1993
+ * The Regents of the University of California. 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.
+ * 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. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+/*
+ * Display protocol blocks in the unix domain.
+ */
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/protosw.h>
+#include <sys/socket.h>
+#define _WANT_SOCKET
+#include <sys/socketvar.h>
+#include <sys/mbuf.h>
+#include <sys/sysctl.h>
+#include <sys/un.h>
+#define _WANT_UNPCB
+#include <sys/unpcb.h>
+
+#include <netinet/in.h>
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <strings.h>
+#include <kvm.h>
+#include <libxo/xo.h>
+#include "netstat.h"
+
+static void unixdomainpr(struct xunpcb *, struct xsocket *);
+
+static const char *const socktype[] =
+ { "#0", "stream", "dgram", "raw", "rdm", "seqpacket" };
+
+static int
+pcblist_sysctl(int type, char **bufp)
+{
+ char *buf;
+ size_t len;
+ char mibvar[sizeof "net.local.seqpacket.pcblist"];
+
+ snprintf(mibvar, sizeof(mibvar), "net.local.%s.pcblist", socktype[type]);
+
+ len = 0;
+ if (sysctlbyname(mibvar, 0, &len, 0, 0) < 0) {
+ if (errno != ENOENT)
+ xo_warn("sysctl: %s", mibvar);
+ return (-1);
+ }
+ if ((buf = malloc(len)) == NULL) {
+ xo_warnx("malloc %lu bytes", (u_long)len);
+ return (-2);
+ }
+ if (sysctlbyname(mibvar, buf, &len, 0, 0) < 0) {
+ xo_warn("sysctl: %s", mibvar);
+ free(buf);
+ return (-2);
+ }
+ *bufp = buf;
+ return (0);
+}
+
+static int
+pcblist_kvm(u_long count_off, u_long gencnt_off, u_long head_off, char **bufp)
+{
+ struct unp_head head;
+ struct unpcb *unp, unp0, unp_conn;
+ u_char sun_len;
+ struct socket so;
+ struct xunpgen xug;
+ struct xunpcb xu;
+ unp_gen_t unp_gencnt;
+ u_int unp_count;
+ char *buf, *p;
+ size_t len;
+
+ if (count_off == 0 || gencnt_off == 0)
+ return (-2);
+ if (head_off == 0)
+ return (-1);
+ kread(count_off, &unp_count, sizeof(unp_count));
+ len = 2 * sizeof(xug) + (unp_count + unp_count / 8) * sizeof(xu);
+ if ((buf = malloc(len)) == NULL) {
+ xo_warnx("malloc %lu bytes", (u_long)len);
+ return (-2);
+ }
+ p = buf;
+
+#define COPYOUT(obj, size) do { \
+ if (len < (size)) { \
+ xo_warnx("buffer size exceeded"); \
+ goto fail; \
+ } \
+ bcopy((obj), p, (size)); \
+ len -= (size); \
+ p += (size); \
+} while (0)
+
+#define KREAD(off, buf, len) do { \
+ if (kread((uintptr_t)(off), (buf), (len)) != 0) \
+ goto fail; \
+} while (0)
+
+ /* Write out header. */
+ kread(gencnt_off, &unp_gencnt, sizeof(unp_gencnt));
+ xug.xug_len = sizeof xug;
+ xug.xug_count = unp_count;
+ xug.xug_gen = unp_gencnt;
+ xug.xug_sogen = 0;
+ COPYOUT(&xug, sizeof xug);
+
+ /* Walk the PCB list. */
+ xu.xu_len = sizeof xu;
+ KREAD(head_off, &head, sizeof(head));
+ LIST_FOREACH(unp, &head, unp_link) {
+ xu.xu_unpp = (uintptr_t)unp;
+ KREAD(unp, &unp0, sizeof (*unp));
+ unp = &unp0;
+
+ if (unp->unp_gencnt > unp_gencnt)
+ continue;
+ if (unp->unp_addr != NULL) {
+ KREAD(unp->unp_addr, &sun_len, sizeof(sun_len));
+ KREAD(unp->unp_addr, &xu.xu_addr, sun_len);
+ }
+ if (unp->unp_conn != NULL) {
+ KREAD(unp->unp_conn, &unp_conn, sizeof(unp_conn));
+ if (unp_conn.unp_addr != NULL) {
+ KREAD(unp_conn.unp_addr, &sun_len,
+ sizeof(sun_len));
+ KREAD(unp_conn.unp_addr, &xu.xu_caddr, sun_len);
+ }
+ }
+ KREAD(unp->unp_socket, &so, sizeof(so));
+ if (sotoxsocket(&so, &xu.xu_socket) != 0)
+ goto fail;
+ COPYOUT(&xu, sizeof(xu));
+ }
+
+ /* Reread the counts and write the footer. */
+ kread(count_off, &unp_count, sizeof(unp_count));
+ kread(gencnt_off, &unp_gencnt, sizeof(unp_gencnt));
+ xug.xug_count = unp_count;
+ xug.xug_gen = unp_gencnt;
+ COPYOUT(&xug, sizeof xug);
+
+ *bufp = buf;
+ return (0);
+
+fail:
+ free(buf);
+ return (-1);
+#undef COPYOUT
+#undef KREAD
+}
+
+void
+unixpr(u_long count_off, u_long gencnt_off, u_long dhead_off, u_long shead_off,
+ u_long sphead_off, bool *first)
+{
+ char *buf;
+ int ret, type;
+ struct xsocket *so;
+ struct xunpgen *xug, *oxug;
+ struct xunpcb *xunp;
+ u_long head_off;
+
+ buf = NULL;
+ for (type = SOCK_STREAM; type <= SOCK_SEQPACKET; type++) {
+ if (live)
+ ret = pcblist_sysctl(type, &buf);
+ else {
+ head_off = 0;
+ switch (type) {
+ case SOCK_STREAM:
+ head_off = shead_off;
+ break;
+
+ case SOCK_DGRAM:
+ head_off = dhead_off;
+ break;
+
+ case SOCK_SEQPACKET:
+ head_off = sphead_off;
+ break;
+ }
+ ret = pcblist_kvm(count_off, gencnt_off, head_off,
+ &buf);
+ }
+ if (ret == -1)
+ continue;
+ if (ret < 0)
+ return;
+
+ oxug = xug = (struct xunpgen *)buf;
+ for (xug = (struct xunpgen *)((char *)xug + xug->xug_len);
+ xug->xug_len > sizeof(struct xunpgen);
+ xug = (struct xunpgen *)((char *)xug + xug->xug_len)) {
+ xunp = (struct xunpcb *)xug;
+ so = &xunp->xu_socket;
+
+ /* Ignore PCBs which were freed during copyout. */
+ if (xunp->unp_gencnt > oxug->xug_gen)
+ continue;
+ if (*first) {
+ xo_open_list("socket");
+ *first = false;
+ }
+ xo_open_instance("socket");
+ unixdomainpr(xunp, so);
+ xo_close_instance("socket");
+ }
+ if (xug != oxug && xug->xug_gen != oxug->xug_gen) {
+ if (oxug->xug_count > xug->xug_count) {
+ xo_emit("Some {:type/%s} sockets may have "
+ "been {:action/deleted}.\n",
+ socktype[type]);
+ } else if (oxug->xug_count < xug->xug_count) {
+ xo_emit("Some {:type/%s} sockets may have "
+ "been {:action/created}.\n",
+ socktype[type]);
+ } else {
+ xo_emit("Some {:type/%s} sockets may have "
+ "been {:action/created or deleted}",
+ socktype[type]);
+ }
+ }
+ free(buf);
+ }
+}
+
+static void
+unixdomainpr(struct xunpcb *xunp, struct xsocket *so)
+{
+ struct sockaddr_un *sa;
+ static int first = 1;
+ char buf1[33];
+ static const char *titles[2] = {
+ "{T:/%-8.8s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-6.6s} {T:/%8.8s} "
+ "{T:/%8.8s} {T:/%8.8s} {T:/%8.8s} {T:Addr}\n",
+ "{T:/%-16.16s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-6.6s} {T:/%16.16s} "
+ "{T:/%16.16s} {T:/%16.16s} {T:/%16.16s} {T:Addr}\n"
+ };
+ static const char *format[2] = {
+ "{q:address/%8lx} {t:type/%-6.6s} "
+ "{:receive-bytes-waiting/%6u} "
+ "{:send-bytes-waiting/%6u} "
+ "{q:vnode/%8lx} {q:connection/%8lx} "
+ "{q:first-reference/%8lx} {q:next-reference/%8lx}",
+ "{q:address/%16lx} {t:type/%-6.6s} "
+ "{:receive-bytes-waiting/%6u} "
+ "{:send-bytes-waiting/%6u} "
+ "{q:vnode/%16lx} {q:connection/%16lx} "
+ "{q:first-reference/%16lx} {q:next-reference/%16lx}"
+ };
+ int fmt = (sizeof(void *) == 8) ? 1 : 0;
+
+ sa = (xunp->xu_addr.sun_family == AF_UNIX) ? &xunp->xu_addr : NULL;
+
+ if (first && !Lflag) {
+ xo_emit("{T:Active UNIX domain sockets}\n");
+ xo_emit(titles[fmt],
+ "Address", "Type", "Recv-Q", "Send-Q",
+ "Inode", "Conn", "Refs", "Nextref");
+ first = 0;
+ }
+
+ if (Lflag && so->so_qlimit == 0)
+ return;
+
+ if (Lflag) {
+ snprintf(buf1, sizeof buf1, "%u/%u/%u", so->so_qlen,
+ so->so_incqlen, so->so_qlimit);
+ xo_emit("unix {d:socket/%-32.32s}{e:queue-length/%u}"
+ "{e:incomplete-queue-length/%u}{e:queue-limit/%u}",
+ buf1, so->so_qlen, so->so_incqlen, so->so_qlimit);
+ } else {
+ xo_emit(format[fmt],
+ (long)so->so_pcb, socktype[so->so_type], so->so_rcv.sb_cc,
+ so->so_snd.sb_cc, (long)xunp->unp_vnode,
+ (long)xunp->unp_conn, (long)xunp->xu_firstref,
+ (long)xunp->xu_nextref);
+ }
+ if (sa)
+ xo_emit(" {:path/%.*s}",
+ (int)(sa->sun_len - offsetof(struct sockaddr_un, sun_path)),
+ sa->sun_path);
+ xo_emit("\n");
+}