diff options
Diffstat (limited to 'usr.bin/netstat')
-rw-r--r-- | usr.bin/netstat/Makefile | 72 | ||||
-rw-r--r-- | usr.bin/netstat/Makefile.depend | 21 | ||||
-rw-r--r-- | usr.bin/netstat/Makefile.depend.options | 7 | ||||
-rw-r--r-- | usr.bin/netstat/bpf.c | 167 | ||||
-rw-r--r-- | usr.bin/netstat/common.c | 136 | ||||
-rw-r--r-- | usr.bin/netstat/common.h | 90 | ||||
-rw-r--r-- | usr.bin/netstat/if.c | 745 | ||||
-rw-r--r-- | usr.bin/netstat/inet.c | 1549 | ||||
-rw-r--r-- | usr.bin/netstat/inet6.c | 1359 | ||||
-rw-r--r-- | usr.bin/netstat/ipsec.c | 433 | ||||
-rw-r--r-- | usr.bin/netstat/main.c | 935 | ||||
-rw-r--r-- | usr.bin/netstat/mbuf.c | 354 | ||||
-rw-r--r-- | usr.bin/netstat/mroute.c | 462 | ||||
-rw-r--r-- | usr.bin/netstat/mroute6.c | 274 | ||||
-rw-r--r-- | usr.bin/netstat/netgraph.c | 141 | ||||
-rw-r--r-- | usr.bin/netstat/netisr.c | 506 | ||||
-rw-r--r-- | usr.bin/netstat/netstat.1 | 978 | ||||
-rw-r--r-- | usr.bin/netstat/netstat.h | 168 | ||||
-rw-r--r-- | usr.bin/netstat/nhgrp.c | 352 | ||||
-rw-r--r-- | usr.bin/netstat/nhops.c | 477 | ||||
-rw-r--r-- | usr.bin/netstat/nlist_symbols | 50 | ||||
-rw-r--r-- | usr.bin/netstat/pfkey.c | 200 | ||||
-rw-r--r-- | usr.bin/netstat/route.c | 724 | ||||
-rw-r--r-- | usr.bin/netstat/route_netlink.c | 340 | ||||
-rw-r--r-- | usr.bin/netstat/sctp.c | 841 | ||||
-rw-r--r-- | usr.bin/netstat/unix.c | 320 |
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"); +} |